react-spa-template/docs/authentication.md
2025-04-19 19:26:03 +02:00

209 lines
8.1 KiB
Markdown

# 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:
```json
{
"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:
```typescript
{
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 tokens
- `automaticSilentRenew` is enabled to automatically refresh tokens in the background
- `revokeTokensOnSignout` ensures tokens are properly revoked when logging out
- `monitorSession` enables session monitoring to detect changes
### Authentication Flow
#### Initial Authentication
1. The `AuthProvider` component initializes the `UserManager` from `oidc-client-ts` using the configuration fetched from the API endpoint `/oidc.json`.
2. It checks if the user is already authenticated by calling `userManager.getUser()`.
3. If a user is found but the token is expired or about to expire, it attempts a silent token renewal.
4. If no user is found and the user has not manually logged out (tracked in localStorage), the app automatically initiates the login process.
5. The login process first tries silent login (`userManager.signinSilent()`).
6. If silent login fails, it falls back to redirect login (`userManager.signinRedirect()`).
7. After successful authentication at the provider, the user is redirected back to the application's callback URL (`/auth/callback`).
8. The `AuthCallback` component handles the redirect, processes the response, and stores the user session.
#### Token Renewal
1. The application uses `automaticSilentRenew` to automatically refresh tokens in the background.
2. When a token is about to expire, the OIDC client attempts a silent renewal.
3. 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:
1. **First Visit or Regular Visit**: The app automatically initiates the login process.
2. **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:
```jsx
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.
```jsx
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:
1. Sets the `manuallyLoggedOut` flag to `true` in localStorage
2. Clears the user from the store
3. 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.
```jsx
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
1. **Authentication configuration error**
- Check that the API endpoint `/oidc.json` is accessible
- Verify that the API returns the correct JSON structure with `authorityUrl` and `clientId` fields
- Ensure the `CLIENT_API_URL` environment variable is correctly set
2. **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
3. **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
4. **Auto-login not working**
- Check if the `auth_manually_logged_out` flag in localStorage is set to `true`
- 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
5. **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:
1. **Check localStorage**: The application stores authentication state in localStorage:
- `auth_manually_logged_out`: Tracks if the user has manually logged out
- `oidc.user:[authority]:[client_id]`: Contains the user session data
2. **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
3. **Enable Debug Mode**: For even more detailed logging, enable debug mode by setting the `CLIENT_DEBUG` environment variable to `true` in your `.env` file:
```
CLIENT_DEBUG=true
```
This enables additional logging in various parts of the application, including more detailed OIDC client logs.