Authentication system
Introduction#
Extension auth: React hook, browser OAuth via identity, token refresh, logout, and session persistence with the backend.
Project structure#
The authentication system spans the extension’s side panel (React + browser APIs) and the backend (FastAPI). The key files are:
- Hook for authentication state and token lifecycle
- Login UI component
- Application shell that renders the login screen or settings based on auth state
- Settings UI that displays tokens and exposes manual refresh/logout actions
- Backend API surface for token exchange and refresh
graph TB subgraph "Extension Side Panel" A_App["App.tsx"] A_SignIn["SignInScreen.tsx"] A_Hook["useAuth.ts"] A_Profile["ProfileSidebar.tsx"] A_Settings["UnifiedSettingsMenu.tsx"] end subgraph "Backend API" B_API["main.py"] B_Router["routers/github.py"] B_Service["services/github_service.py"] end A_App --> A_SignIn A_App --> A_Settings A_App --> A_Profile A_App --> A_Hook A_Hook --> B_API A_Settings --> A_Hook A_Profile --> A_Hook B_API --> B_Router B_Router --> B_Service
Core components#
- useAuth hook
- Initializes auth state from browser local storage
- Detects token age and refreshes automatically when appropriate
- Exposes login via browser.identity OAuth, GitHub demo login, logout, and manual refresh
- Provides helpers to compute token age and expiry
- SignInScreen component
- Renders two login options: Google OAuth and GitHub demo
- Uses styled buttons and animations for UX
- App shell
- Renders SignInScreen when unauthenticated or UnifiedSettingsMenu when authenticated
- Handles first-time setup redirection flag
- Settings UI
- Displays token info and exposes manual refresh and logout actions
Architecture overview#
The authentication flow integrates browser identity APIs, a backend service, and local storage. The diagram below maps the actual code paths.
sequenceDiagram
participant UI as "SignInScreen.tsx"
participant Hook as "useAuth.ts"
participant Browser as "browser.identity"
participant Backend as "main.py<br/>routers/github.py"
participant Store as "browser.storage.local"
UI->>Hook : "handleLogin()"
Hook->>Browser : "launchWebAuthFlow(authUrl)"
Browser-->>Hook : "redirectResponse(code)"
Hook->>Backend : "POST /exchange-code {code, redirect_uri}"
Backend-->>Hook : "{access_token, refresh_token, expires_in}"
Hook->>Store : "set googleUser (token, timestamps)"
Store-->>Hook : "onChange(googleUser)"
Hook-->>UI : "user state updated"
Hook->>Hook : "checkAndRefreshToken()"
Hook->>Backend : "POST /refresh-token {refresh_token}"
Backend-->>Hook : "{access_token, expires_in}"
Hook->>Store : "update googleUser"Detailed component analysis#
useAuth hook#
The hook encapsulates:
- Initialization from browser storage
- Automatic token refresh based on token age
- Manual refresh and logout
- Token age/expiry computation
- First-time setup flag handling
Key behaviors:
- On mount, reads stored user data and triggers token refresh checks
- Uses a threshold to decide whether to refresh the access token
- Persists updated tokens back to storage and updates React state
- Exposes handlers for Google OAuth login, GitHub demo login, logout, and manual refresh
- Provides UI helpers to display token age and expiry
flowchart TD
Start(["initAuth()"]) --> Load["Load 'googleUser' from storage"]
Load --> HasUser{"User exists?"}
HasUser --> |No| DoneInit["Set authLoading=false"]
HasUser --> |Yes| Check["checkAndRefreshToken(userData)"]
Check --> Age["Compute tokenAge = now - tokenTimestamp"]
Age --> NeedsRefresh{"tokenAge > threshold<br/>AND has refreshToken?"}
NeedsRefresh --> |Yes| Refresh["refreshAccessToken(refreshToken)"]
Refresh --> RefreshOK{"Refresh OK?"}
RefreshOK --> |Yes| Save["Update googleUser in storage<br/>and set user state"]
RefreshOK --> |No| StatusFail["Set status: re-authenticate"]
NeedsRefresh --> |No| Expired{"tokenAge > expires_in<br/>AND no refreshToken?"}
Expired --> |Yes| StatusExpire["Set status: expired"]
Expired --> |No| StatusValid["Set status: valid"]
Save --> DoneInit
StatusFail --> DoneInit
StatusExpire --> DoneInit
StatusValid --> DoneInitSignInScreen component#
The component renders:
- A hero section with animated visuals
- Two login buttons:
- Continue with Google (OAuth)
- Continue with GitHub (demo bypass)
- Responsive styling and hover effects
classDiagram
class SignInScreen {
+props : onLogin(), onGitHubLogin()
+renders : "Continue with Google"
+renders : "Continue with GitHub"
}App shell integration#
The App component:
- Consumes useAuth to determine whether to render SignInScreen or UnifiedSettingsMenu
- Handles first-time setup redirection based on a storage flag
- Subscribes to storage changes to stay in sync with the hook
sequenceDiagram
participant App as "App.tsx"
participant Hook as "useAuth.ts"
participant Store as "browser.storage.local"
App->>Hook : "subscribe to auth state"
Hook-->>App : "user, authLoading, tokenStatus"
App->>Store : "listen to 'local' changes"
Store-->>Hook : "onChange('googleUser')"
Hook-->>App : "updated user state"
App->>App : "render SignInScreen or Settings"Settings UI (tokens and actions)#
The settings UI surfaces:
- Access token visibility toggle
- Refresh token visibility toggle (blurred)
- Manual refresh button (when refresh token present)
- Logout button
classDiagram
class UnifiedSettingsMenu {
+props : user, tokenStatus, handleManualRefresh, handleLogout
+displays : "Access Token"
+displays : "Refresh Token"
+button : "Refresh Token Manually"
+button : "Logout"
}
class ProfileSidebar {
+props : user, handleManualRefresh, handleLogout
+displays : "Access Token"
+displays : "Refresh Token"
+button : "Refresh Token Manually"
+button : "Logout"
}Dependency analysis#
- Frontend-to-backend dependencies
- useAuth.ts calls backend endpoints for token exchange and refresh
- App.tsx depends on useAuth for rendering decisions
- Settings components depend on useAuth for token display and actions
- Backend routing
- main.py registers routers under various prefixes
- routers/github.py defines a GitHub endpoint used by services
- services/github_service.py orchestrates GitHub-related operations
graph LR Hook["useAuth.ts"] --> Backend["main.py"] Backend --> Routers["routers/github.py"] Routers --> Service["services/github_service.py"] App["App.tsx"] --> Hook Settings["UnifiedSettingsMenu.tsx"] --> Hook Sidebar["ProfileSidebar.tsx"] --> Hook
Performance considerations#
- Token refresh threshold
- The hook refreshes tokens before they reach a configured age threshold, reducing latency during requests
- Local storage synchronization
- Storage change subscriptions keep the UI in sync across sessions
- UI responsiveness
- Loading states and alerts provide feedback during long-running operations like OAuth and network requests
Troubleshooting guide#
Common issues and resolutions:
- Authentication cancelled or denied
- The hook detects cancellation/denial keywords and shows a plain alert
- Backend service not running
- Errors during token exchange or refresh trigger alerts instructing to verify backend availability
- Token expired or refresh failed
- The hook sets explicit status messages and advises re-authentication when refresh fails
- Manual refresh unavailable
- If no refresh token is present, the UI disables manual refresh and prompts re-login
Security considerations#
- Token storage
- Tokens are stored in browser local storage; consider encrypting sensitive fields for production
- Token exposure
- The settings UI supports toggling token visibility; use caution when sharing screens
- Refresh token handling
- Refresh tokens renew the session; keep transport and storage locked down
- OAuth consent
- The Google OAuth flow requests offline access and broad scopes; review and minimize scopes as needed
Conclusion#
OAuth in the browser, refresh on expiry, clear storage on logout. The hook is the only API the UI should talk to for session state.