8.1 KiB
Authentication Documentation
This document explains the authentication setup for the App.
OIDC Authentication
The application uses OpenID Connect (OIDC) for authentication with the following features:
- Dynamic configuration loading from API endpoint
/oidc.json
- Token storage in localStorage via WebStorageStateStore
- Automatic token refresh with silent renewal
- Refresh token support via offline_access scope
- Protected routes with role-based access control
- Smart authentication flow with auto-login and manual logout tracking
Configuration
The OIDC configuration is fetched from the API endpoint /oidc.json
. This endpoint should return a JSON object with the following structure:
{
"authorityUrl": "https://your-oidc-provider.com",
"clientId": "your-client-id"
}
The application requires the CLIENT_API_URL
environment variable to be set to the base URL of the API server. The OIDC configuration is then fetched from ${CLIENT_API_URL}/oidc.json
using the OidcService.
OIDC Client Configuration
The application configures the OIDC client with the following settings:
{
authority: config.authorityUrl,
client_id: config.clientId,
redirect_uri: `${window.location.origin}/auth/callback`,
response_type: "code",
scope: "openid profile email offline_access", // offline_access for refresh tokens
post_logout_redirect_uri: window.location.origin,
userStore: new WebStorageStateStore({ store: window.localStorage }),
loadUserInfo: true,
automaticSilentRenew: true,
revokeTokensOnSignout: true,
monitorSession: true,
}
Key configuration details:
offline_access
scope is requested to enable refresh tokensautomaticSilentRenew
is enabled to automatically refresh tokens in the backgroundrevokeTokensOnSignout
ensures tokens are properly revoked when logging outmonitorSession
enables session monitoring to detect changes
Authentication Flow
Initial Authentication
- The
AuthProvider
component initializes theUserManager
fromoidc-client-ts
using the configuration fetched from the API endpoint/oidc.json
. - It checks if the user is already authenticated by calling
userManager.getUser()
. - If a user is found but the token is expired or about to expire, it attempts a silent token renewal.
- If no user is found and the user has not manually logged out (tracked in localStorage), the app automatically initiates the login process.
- The login process first tries silent login (
userManager.signinSilent()
). - If silent login fails, it falls back to redirect login (
userManager.signinRedirect()
). - After successful authentication at the provider, the user is redirected back to the application's callback URL (
/auth/callback
). - The
AuthCallback
component handles the redirect, processes the response, and stores the user session.
Token Renewal
- The application uses
automaticSilentRenew
to automatically refresh tokens in the background. - When a token is about to expire, the OIDC client attempts a silent renewal.
- If silent renewal fails, the application will detect this during API calls and can redirect to login if needed.
Manual vs. Auto Login
The application implements a smart authentication flow that distinguishes between first visits and visits after manual logout:
- First Visit or Regular Visit: The app automatically initiates the login process.
- After Manual Logout: The app requires the user to click the "Sign in" button to log in again.
This behavior is controlled by a manuallyLoggedOut
flag that is:
- Stored in localStorage to persist across page refreshes and browser sessions
- Set to
true
when the user manually logs out - Reset to
false
when the user successfully logs in or manually clicks the login button
Protected Routes
Routes that require authentication can be protected using the ProtectedRoute
component:
import { ProtectedRoute } from '@domains/auth'; // Adjusted path based on current structure
// Basic protection
<ProtectedRoute>
<YourComponent />
</ProtectedRoute>
// With role-based protection
<ProtectedRoute requiredRoles={['admin']}>
<AdminComponent />
</ProtectedRoute>
Making Authenticated API Calls
Use the useApiService
hook (now located in src/shared/lib/api/apiService.ts
) to make API calls.
import { useApiService } from '@shared/lib/api'; // Use the alias
function YourComponent() {
const api = useApiService();
const fetchData = async () => {
const response = await api.get('/your-endpoint');
const data = await response.json();
// Process data
};
// ...
}
The current simplified useApiService
hook:
- Checks if the user is authenticated using
useAuth
. - Does not automatically add the access token to requests (this would need to be added if required by the backend).
- Does not handle token refresh or expiration explicitly. It relies on the underlying
oidc-client-ts
library's session management. - Logs an error if a 401 Unauthorized response is received.
Logout
To log a user out, use the logout
function returned by the useAuth
hook. This function triggers the logoutFx
effect, which performs the following steps:
- Sets the
manuallyLoggedOut
flag totrue
in localStorage - Clears the user from the store
- Initiates the OIDC sign-out redirect flow via
userManager.signoutRedirect()
The logout process is designed to ensure that the user remains logged out even after page refreshes or browser restarts, until they explicitly choose to log in again.
import { useAuth } from '@domains/auth';
function LogoutButton() {
const { logout, user } = useAuth();
return (
<button
onClick={logout}
className="px-4 py-2 bg-[#653cee] hover:bg-[#4e2eb8] text-white rounded-md"
>
Logout
</button>
);
}
The logoutFx
effect is configured to revoke tokens on signout using the revokeTokensOnSignout: true
setting in the OIDC client configuration. This ensures that tokens are properly invalidated when logging out.
Troubleshooting
Common Issues
-
Authentication configuration error
- Check that the API endpoint
/oidc.json
is accessible - Verify that the API returns the correct JSON structure with
authorityUrl
andclientId
fields - Ensure the
CLIENT_API_URL
environment variable is correctly set
- Check that the API endpoint
-
Token refresh failures
- Check that the OIDC server is configured to allow refresh tokens
- Verify that the
offline_access
scope is included in the OIDC configuration - Check browser console for silent renewal errors
-
CORS errors
- Ensure the OIDC server has the correct origins configured
- Check that redirect URIs are properly set up
- Verify that the OIDC server allows the application's origin
-
Auto-login not working
- Check if the
auth_manually_logged_out
flag in localStorage is set totrue
- Clear the flag by setting it to
false
or removing it from localStorage - Verify that the user doesn't have an existing session that's invalid
- Check if the
-
Logout not working properly
- Check browser console for errors during the logout process
- Verify that the
revokeTokensOnSignout
setting is enabled - Check if the OIDC server is properly configured to handle logout requests
Debugging
The application includes extensive logging to help diagnose authentication issues:
-
Check localStorage: The application stores authentication state in localStorage:
auth_manually_logged_out
: Tracks if the user has manually logged outoidc.user:[authority]:[client_id]
: Contains the user session data
-
Console Logging: The application logs detailed information about the authentication process:
- Authentication initialization and configuration
- Token validation and renewal attempts
- Login and logout operations
- State changes in the authentication store
-
Enable Debug Mode: For even more detailed logging, enable debug mode by setting the
CLIENT_DEBUG
environment variable totrue
in your.env
file:
CLIENT_DEBUG=true
This enables additional logging in various parts of the application, including more detailed OIDC client logs.