You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
|
|
6 months ago | |
|---|---|---|
| .nicegui | 6 months ago | |
| middlewares | 6 months ago | |
| pages | 6 months ago | |
| services | 6 months ago | |
| static | 6 months ago | |
| tests | 6 months ago | |
| .gitignore | 6 months ago | |
| README.md | 6 months ago | |
| app.py | 6 months ago | |
| requirements.txt | 6 months ago | |
README.md
NiceGUI OIDC Authentication Demo
A modern authentication system built with NiceGUI and FastAPI, demonstrating how to implement OpenID Connect (OIDC) authentication with session management and route protection.
🚀 What This App Does
This application demonstrates:
- OIDC Authentication: OpenID Connect integration with external identity providers
- JWT Token Validation: Proper signature verification using JWKS
- Session Management: Secure user sessions with automatic token refresh
- Route Protection: Middleware that restricts access to authenticated users only
- Automatic Redirects: Redirects users to OIDC provider and back to intended pages
- Access Logging: Comprehensive request logging with user context
📁 Project Structure
authentication/
├── services/
│ ├── auth/
│ │ └── oidc.py # OIDC configuration and token handling
│ └── log/
│ └── __init__.py # Logging configuration
├── pages/
│ ├── main_page.py # Main/home page
│ ├── subpage.py # Example protected page
│ ├── login.py # OIDC login initiation
│ └── auth_callback.py # OIDC callback handler
├── middlewares/
│ ├── auth_middleware.py # OIDC authentication middleware
│ └── access_middleware.py # Request logging middleware
├── routes/
│ └── logout.py # Logout functionality
├── tests/
│ └── test_authentication.py # Test suite
├── app.py # Main application
├── requirements.txt # Dependencies
└── README.md
🔧 How to Run
-
Install dependencies:
pip install -r requirements.txt -
Configure OIDC settings in
services/auth/oidc.py:self.client_id = "your-client-id" self.client_secret = "your-client-secret" self.discovery_url = "https://your-provider/.well-known/openid-configuration" self.redirect_uri = "http://127.0.0.1:8080/auth/callback" -
Run the application:
python app.py -
Access the app:
- Open your browser to
http://localhost:8080 - You'll be redirected to
/login - Click "Log in with OIDC" to authenticate
- Open your browser to
🧪 Running Tests
# Run all tests
pytest tests/
# Run with verbose output
pytest tests/ -v
🏗️ How to Build a Similar OIDC App
Step 1: OIDC Configuration
class OIDCConfig:
def __init__(self):
self.client_id = "your-client-id"
self.client_secret = "your-client-secret"
self.discovery_url = "https://provider/.well-known/openid-configuration"
self.redirect_uri = "http://localhost:8080/auth/callback"
self.scope = "openid profile email"
Step 2: Authentication Middleware
class OIDCAuthMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
excluded_paths = {'/login', '/auth/callback'}
if request.url.path not in excluded_paths:
if not app.storage.user.get('authenticated', False):
app.storage.user['redirect_to'] = str(request.url.path)
return RedirectResponse('/login')
# Validate current token
access_token = app.storage.user.get('access_token')
if access_token and not oidc_config.validate_token(access_token):
app.storage.user.clear()
return RedirectResponse('/login')
return await call_next(request)
Step 3: Login Page
@ui.page('/login')
def login():
if app.storage.user.get('authenticated', False):
return RedirectResponse('/')
def initiate_oidc_login():
state = str(uuid.uuid4())
app.storage.user['oidc_state'] = state
auth_url = oidc_config.get_authorization_url(state)
ui.navigate.to(auth_url, new_tab=False)
with ui.card().classes('absolute-center'):
ui.label('Authentication Required')
ui.button('Log in with OIDC', on_click=initiate_oidc_login)
Step 4: OIDC Callback Handler
@ui.page('/auth/callback')
async def auth_callback(code: str = None, state: str = None):
# Verify state parameter
stored_state = app.storage.user.get('oidc_state')
if not stored_state or stored_state != state:
ui.notify('Invalid state parameter', color='negative')
return
# Exchange code for tokens
tokens = await oidc_config.exchange_code_for_tokens(code, oidc_config.redirect_uri)
user_info = oidc_config.validate_token(tokens['id_token'])
# Store user session
app.storage.user.update({
'username': user_info.get('preferred_username'),
'email': user_info.get('email'),
'authenticated': True,
'access_token': tokens['access_token'],
'refresh_token': tokens.get('refresh_token')
})
redirect_to = app.storage.user.get('redirect_to', '/')
ui.navigate.to(redirect_to)
Step 5: Token Validation with JWKS
def validate_token(self, token: str) -> Optional[Dict]:
try:
unverified_header = jwt.get_unverified_header(token)
kid = unverified_header.get('kid')
jwks = self.get_jwks()
public_key = None
for key in jwks.get('keys', []):
if key.get('kid') == kid:
public_key = RSAAlgorithm.from_jwk(key)
break
payload = jwt.decode(
token, public_key, algorithms=['RS256'],
audience=self.client_id, options={"verify_signature": True}
)
return payload
except Exception as e:
logger.error(f"Token validation error: {e}")
return None
🔐 Security Features
✅ Production-ready security:
- JWT signature verification using JWKS from identity provider
- State parameter protection against CSRF attacks
- Secure token storage in encrypted sessions
- Automatic token refresh to maintain sessions
- Proper logout with identity provider notification
- Request logging with user context and IP tracking
- HTTPS enforcement for token endpoints
📚 Key Features Explained
OIDC Flow
- User accesses protected resource → redirected to
/login - Login page redirects to OIDC provider with state parameter
- User authenticates with identity provider
- Provider redirects back to
/auth/callbackwith authorization code - App exchanges code for access/ID tokens
- ID token is validated using provider's public keys
- User session is established with token storage
Token Management
- Access tokens for API authorization
- ID tokens for user identity claims
- Refresh tokens for automatic session renewal
- JWKS validation ensures token authenticity
Access Logging
Every request is logged with:
- Unique request ID
- Client IP address (proxy-aware)
- User information
- Response time and status
🛠️ Supported OIDC Providers
This implementation works with any standard OIDC provider:
- Keycloak
- Auth0
- Azure AD
- Google Identity
- Okta
- Custom OIDC providers
🤝 Contributing
Feel free to submit issues, feature requests, or pull requests to improve this OIDC authentication example!
📄 License
This example is provided as-is for educational purposes.