|
| 1 | +# OIDC Implementation Summary |
| 2 | + |
| 3 | +This document summarizes the OIDC authentication implementation for the Subscription Tracker application. |
| 4 | + |
| 5 | +## Files Modified |
| 6 | + |
| 7 | +### 1. **app/models.py** |
| 8 | +- Added `OIDCSettings` model to store OIDC configuration: |
| 9 | + - `display_name`: Name shown on the login button |
| 10 | + - `client_id`: OAuth2 client ID |
| 11 | + - `client_secret`: OAuth2 client secret |
| 12 | + - `discovery_endpoint`: OIDC discovery URL |
| 13 | + - `user_mapping_field`: Field to use for user matching (username, email, or custom) |
| 14 | + - `custom_attribute`: Custom OIDC claim name (if custom mapping selected) |
| 15 | + - `is_enabled`: Enable/disable OIDC (also controlled by environment variable) |
| 16 | + |
| 17 | +### 2. **app/forms.py** |
| 18 | +- Added `OIDCSettingsForm` with validation for: |
| 19 | + - Display name |
| 20 | + - Client credentials |
| 21 | + - Discovery endpoint URL format |
| 22 | + - User mapping configuration |
| 23 | + - Custom attribute requirement when using custom mapping |
| 24 | + |
| 25 | +### 3. **app/routes.py** |
| 26 | +- Updated imports to include `OIDCSettings` model and `OIDCSettingsForm` |
| 27 | +- Modified `login()` route to: |
| 28 | + - Check if OIDC is enabled via environment variable |
| 29 | + - Show OIDC login button when enabled |
| 30 | + - Hide normal login form when OIDC is active |
| 31 | + - Pass OIDC configuration to template |
| 32 | +- Added `oidc_settings()` route for admin configuration page |
| 33 | +- Added `oidc_login()` route to initiate OIDC authentication flow |
| 34 | +- Added `oidc_callback()` route to handle OIDC authentication callback with user mapping |
| 35 | + |
| 36 | +### 4. **app/__init__.py** |
| 37 | +- Added Migration 4 to `migrate_database()` function: |
| 38 | + - Automatically creates `oidc_settings` table on startup |
| 39 | + - Supports PostgreSQL, MySQL, and SQLite |
| 40 | + |
| 41 | +### 5. **app/templates/login.html** |
| 42 | +- Added conditional rendering: |
| 43 | + - Shows "Login with {OIDC_NAME}" button when OIDC is enabled |
| 44 | + - Shows normal username/password form when OIDC is disabled |
| 45 | + - Displays informational message when using OIDC |
| 46 | + |
| 47 | +### 6. **app/templates/oidc_settings.html** (New File) |
| 48 | +- Admin-only OIDC configuration page |
| 49 | +- Form fields for all OIDC settings |
| 50 | +- JavaScript to show/hide custom attribute field |
| 51 | +- Displays callback URL for configuring in IdP |
| 52 | +- Informational alerts about OIDC behavior |
| 53 | + |
| 54 | +### 7. **app/templates/base.html** |
| 55 | +- Added "OIDC Settings" link to Settings dropdown menu (admin only) |
| 56 | + |
| 57 | +### 8. **config.py** |
| 58 | +- Added `OIDC_ENABLED` configuration option that reads from environment variable |
| 59 | +- Defaults to `false` if not set |
| 60 | + |
| 61 | +### 9. **requirements.txt** |
| 62 | +- Added `Authlib==1.3.2` for OIDC/OAuth2 support |
| 63 | + |
| 64 | +### 10. **docker-compose.yml** |
| 65 | +- Added `OIDC_ENABLED` environment variable (defaults to `false`) |
| 66 | + |
| 67 | +### 11. **docker-compose.security.yml** |
| 68 | +- Added `OIDC_ENABLED` environment variable (defaults to `false`) |
| 69 | + |
| 70 | +### 12. **OIDC_SETUP.md** (New File) |
| 71 | +- Comprehensive documentation for OIDC setup |
| 72 | +- Configuration steps |
| 73 | +- User mapping explanation |
| 74 | +- Troubleshooting guide |
| 75 | +- Examples for Keycloak and Azure AD |
| 76 | + |
| 77 | +## How It Works |
| 78 | + |
| 79 | +### 1. Configuration Flow |
| 80 | +1. Admin logs in with normal credentials (OIDC disabled) |
| 81 | +2. Admin navigates to Settings > OIDC Settings |
| 82 | +3. Admin configures: |
| 83 | + - Display name for the login button |
| 84 | + - Client ID and secret from IdP |
| 85 | + - Discovery endpoint URL |
| 86 | + - User mapping strategy |
| 87 | +4. Admin notes the callback URL to configure in IdP |
| 88 | +5. Admin sets `OIDC_ENABLED=true` in environment |
| 89 | +6. Application restarts with OIDC enabled |
| 90 | + |
| 91 | +### 2. Authentication Flow (OIDC Enabled) |
| 92 | +1. User visits login page |
| 93 | +2. Instead of username/password form, sees "Login with {OIDC_NAME}" button |
| 94 | +3. User clicks button → redirected to IdP |
| 95 | +4. User authenticates at IdP |
| 96 | +5. IdP redirects back to `/auth/callback` with authorization code |
| 97 | +6. Application exchanges code for tokens |
| 98 | +7. Application extracts user info from ID token |
| 99 | +8. Application matches user based on configured mapping field: |
| 100 | + - Username: Match `preferred_username` or `username` claim against `username` in DB |
| 101 | + - Email: Match `email` claim against `email` in DB |
| 102 | + - Custom: Match custom claim against both `username` and `email` in DB |
| 103 | +9. If match found → user logged in successfully |
| 104 | +10. If no match → login denied with error message |
| 105 | + |
| 106 | +### 3. User Mapping Logic |
| 107 | +- **No automatic user creation** - users must exist in database |
| 108 | +- **Exact matching required** - claim must exactly match database field |
| 109 | +- **Security by design** - unknown users cannot gain access |
| 110 | +- **Flexible mapping** - supports username, email, or custom attributes |
| 111 | + |
| 112 | +## Environment Variables |
| 113 | + |
| 114 | +### OIDC_ENABLED |
| 115 | +- **Type**: Boolean |
| 116 | +- **Default**: `false` |
| 117 | +- **Values**: `true`, `1`, `yes` (case-insensitive) = enabled; anything else = disabled |
| 118 | +- **Effect**: |
| 119 | + - When `true`: Disables normal login, shows OIDC button |
| 120 | + - When `false`: Normal username/password login |
| 121 | + |
| 122 | +## Security Features |
| 123 | + |
| 124 | +1. **Admin-only configuration** - Only admin users can configure OIDC |
| 125 | +2. **No automatic provisioning** - Users must exist before OIDC login |
| 126 | +3. **Failed login stops** - Unknown users are denied access immediately |
| 127 | +4. **Client secret protection** - Stored securely in database |
| 128 | +5. **Session management** - Uses Flask session for state management |
| 129 | +6. **CSRF protection** - Standard Flask-WTF CSRF protection on forms |
| 130 | + |
| 131 | +## Testing Checklist |
| 132 | + |
| 133 | +- [ ] OIDC settings page accessible by admin |
| 134 | +- [ ] Non-admin users cannot access OIDC settings |
| 135 | +- [ ] OIDC configuration saves successfully |
| 136 | +- [ ] Callback URL displays correctly |
| 137 | +- [ ] Login page shows OIDC button when `OIDC_ENABLED=true` |
| 138 | +- [ ] Login page shows normal form when `OIDC_ENABLED=false` |
| 139 | +- [ ] OIDC login redirects to IdP |
| 140 | +- [ ] OIDC callback handles successful authentication |
| 141 | +- [ ] User mapping works correctly (username/email/custom) |
| 142 | +- [ ] Unknown users are denied access |
| 143 | +- [ ] Database migration creates `oidc_settings` table |
| 144 | +- [ ] Application works normally with OIDC disabled |
| 145 | + |
| 146 | +## Future Enhancements (Not Implemented) |
| 147 | + |
| 148 | +- Automatic user provisioning from OIDC claims |
| 149 | +- Role mapping from OIDC groups |
| 150 | +- Multiple OIDC providers |
| 151 | +- Just-in-time user attribute updates |
| 152 | +- SSO session management |
| 153 | +- SAML support |
| 154 | + |
| 155 | +## Compatibility |
| 156 | + |
| 157 | +- **Python**: 3.8+ |
| 158 | +- **Flask**: 3.0+ |
| 159 | +- **Authlib**: 1.3.2+ |
| 160 | +- **Databases**: SQLite, PostgreSQL, MySQL/MariaDB |
| 161 | +- **OIDC Providers**: Any standards-compliant OIDC provider |
0 commit comments