# NiceGUI OIDC Authentication Demo A modern authentication system built with [NiceGUI](https://nicegui.io/) 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 1. **Install dependencies**: ```bash pip install -r requirements.txt ``` 2. **Configure OIDC settings** in `services/auth/oidc.py`: ```python 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" ``` 3. **Run the application**: ```bash python app.py ``` 4. **Access the app**: - Open your browser to `http://localhost:8080` - You'll be redirected to `/login` - Click "Log in with OIDC" to authenticate ## ๐Ÿงช Running Tests ```bash # Run all tests pytest tests/ # Run with verbose output pytest tests/ -v ``` ## ๐Ÿ—๏ธ How to Build a Similar OIDC App ### Step 1: OIDC Configuration ```python 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 ```python 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 ```python @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 ```python @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 ```python 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 1. User accesses protected resource โ†’ redirected to `/login` 2. Login page redirects to OIDC provider with state parameter 3. User authenticates with identity provider 4. Provider redirects back to `/auth/callback` with authorization code 5. App exchanges code for access/ID tokens 6. ID token is validated using provider's public keys 7. 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.