diff --git a/TODO_AUTH_REFACTOR.md b/TODO_AUTH_REFACTOR.md new file mode 100644 index 0000000..ecf07d2 --- /dev/null +++ b/TODO_AUTH_REFACTOR.md @@ -0,0 +1,86 @@ +# Auth Flow Refactor: Invisible Token Exchange + +> Research note for future implementation + +## Problem + +The `/callback?code=...` URL is visible in the browser during token refresh. This happens every time tokens need refreshing, not just on initial login. The current implementation appears to re-run the full OIDC redirect flow (`prompt=none`) instead of using the refresh_token. + +## Current Behavior + +``` +Token expires → Redirect to Authentik (prompt=none) → +Redirect to /callback?code=xxx → Exchange code → Continue +``` + +User sees URL flicker to `/callback` repeatedly. + +## Desired Behavior + +``` +Token expires → Show overlay (lock/lightning icon) → +XHR refresh request → Hide overlay → Continue +``` + +No URL changes. No redirects. Just a brief visual indicator. + +## Key Insight + +**Only the initial authorization MUST redirect** (user needs to see Authentik login UI). + +Everything else can be XHR: + +| Operation | Current | Should Be | +|-----------|---------|-----------| +| Initial login | Redirect | Redirect (unavoidable) | +| Token exchange (code → tokens) | Redirect to /callback | XHR POST | +| Token refresh | Full OIDC with prompt=none | XHR POST with refresh_token | +| Session expired | Redirect | Redirect (unavoidable) | + +## Token Refresh via XHR + +```dart +final response = await dio.post( + 'https://authentik.schweitz.net/application/o/token/', + data: { + 'grant_type': 'refresh_token', + 'refresh_token': storedRefreshToken, + 'client_id': clientId, + }, + options: Options( + contentType: Headers.formUrlEncodedContentType, + ), +); +// Returns new access_token, refresh_token, expires_in +``` + +## Potential Blocker: CORS + +Authentik's token endpoint may block browser XHR. Solutions: + +1. **Configure Authentik CORS** - Allow `home.schweitz.net` origin +2. **Proxy through core-api** (recommended) + - Flutter → `POST /auth/refresh` → core-api → Authentik + - Keeps client_secret server-side + - No CORS issues + +## Implementation Steps + +1. [ ] Verify Authentik is issuing refresh_tokens (check token response) +2. [ ] Check if refresh_token is being stored (SharedPreferences) +3. [ ] Test XHR to token endpoint (check CORS) +4. [ ] If CORS blocked, add `/auth/refresh` endpoint to core-api +5. [ ] Refactor `AuthProvider` to use XHR refresh instead of full OIDC flow +6. [ ] Add refresh overlay UI (lock icon + brief animation) +7. [ ] Remove `prompt=none` redirect logic for refresh cases + +## Files to Investigate + +- `lib/core/auth/auth_provider.dart` - Main auth state management +- `lib/core/auth/oidc_service_web.dart` - OIDC implementation +- `lib/core/api/api_interceptors.dart` - Token refresh trigger point + +## References + +- CHANGELOG entries v1.1.3-v1.1.6 document the current auth architecture +- AuthController runs in `main()` before `runApp()` (v1.1.6 pattern)