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

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 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:

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:

  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.

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.