axafrance oidc-client

repository·main·Indexed 20 days ago

https://github.com/axafrance/oidc-client

A lightweight and secure JavaScript library for managing OpenID Connect (OIDC) and OAuth2 authentication. It features Service Worker integration and DPoP to protect tokens from XSRF attacks and provides a suite of React tools including the OidcProvider, useOidc hook, OidcSecure guard, and specialized hooks for token and user profile access. It supports integration with Next.js via custom history synchronization.

Tokens
35.1K
Snippets
119
Records
136
Agent score
71%

What's inside @axa-fr/react-oidc

  1. Understand the Service Worker Message envelope

    main

    Every message sent to the service worker must follow the ServiceWorkerMessage interface. Messages are communicated via a MessageChannel, and the service worker responds on port2 exactly once. Responses include a configurationName and may include an error field if the request fails.

    Message Structure:

    interface ServiceWorkerMessage {
      type: ServiceWorkerMessageTypeValue; // The wire value (e.g., 'init', 'getState')
      configurationName: string;           // OIDC configuration identifier
      data: object | null;                 // Payload (varies by type)
      tabId?: string;                      // Optional tab ID (defaults to "default")
    }
    interface ServiceWorkerMessage {
      type: ServiceWorkerMessageTypeValue;
      configurationName: string;
      data: object | null;
      tabId?: string;
    }
  2. Ensure Silent Signing, Single Logout, and Monitor Session work

    main

    Due to browser restrictions on third-party cookies (especially in Safari), Silent Signing, Single Logout, and Monitor Session require the OIDC provider and the client application to reside on the same domain.

    Silent signing uses cookies from the OIDC provider to restore sessions via a background IFrame. If they are on different domains, the browser will block these cookies, causing these features to fail.

    Example of working domains:

    • Provider: https://oidc-provider.axa.fr
    • Client: https://my-app.axa.fr
  3. How Service Worker mode works in @axa-fr/oidc-client

    main

    When using the Service Worker mode, the access_token and refresh_token are captured and managed by the Service Worker. This ensures that the tokens are never accessible to the main JavaScript client code, providing a significant security layer against XSS attacks.

    When this mode is active, you do not need to manually inject the access_token into every fetch request; instead, you configure the OidcTrustedDomains.js file to allow the Service Worker to handle requests to authorized domains.

  4. How token placeholders work

    main

    To maintain security, the service worker never returns actual secret values (access tokens, refresh tokens, nonces, etc.) to the page. Instead, it returns stable placeholder strings. When the page makes a fetch request to a trusted endpoint, these placeholders are used to identify the required credentials.

    Placeholder Formats:

    • ACCESS_TOKEN_SECURED_BY_OIDC_SERVICE_WORKER_<configurationName>#tabId=<tabId>
    • REFRESH_TOKEN_SECURED_BY_OIDC_SERVICE_WORKER_<configurationName>#tabId=<tabId>
    • NONCE_SECURED_BY_OIDC_SERVICE_WORKER_<configurationName>#tabId=<tabId>
    • CODE_VERIFIER_SECURED_BY_OIDC_SERVICE_WORKER_<configurationName>#tabId=<tabId>
    • DPOP_SECURED_BY_OIDC_SERVICE_WORKER_<configurationName>#tabId=<tabId>

    Use the provided helpers to construct these strings correctly:

    • buildSecuredTokenPlaceholder(TOKEN_PLACEHOLDERS.<KIND>, configurationName, tabId)
    • buildDpopSecuredPlaceholder(configurationName, tabId)
    // Example of constructing a placeholder
    const placeholder = buildSecuredTokenPlaceholder(
      TOKEN_PLACEHOLDERS.ACCESS_TOKEN,
      'my-config',
      'my-tab-id'
    );
  5. Integrate @axa-fr/react-oidc with Next.js

    main

    When using @axa-fr/react-oidc with Next.js, you must provide a withCustomHistory function to the OidcProvider. This ensures that the OIDC client's state changes are synchronized with the Next.js router instead of using the default browser history API.

    Additionally, wrap your application (typically in layout.js or _app.js) with the OidcProvider component, passing in your OIDC configuration and an onEvent handler for logging/monitoring.

    import { OidcProvider } from '@axa-fr/react-oidc';
    import { useRouter } from 'next/router';
    
    const configuration = {
      client_id: 'interactive.public.short',
      redirect_uri: 'http://localhost:3001/#authentication/callback',
      silent_redirect_uri: 'http://localhost:3001/#authentication/silent-callback', // Optional: enables silent-signin via cookies
      scope: 'openid profile email api offline_access',
      authority: 'https://demo.duendesoftware.com',
      par: 'auto',
    };
    
    const onEvent = (configurationName, eventName, data) => {
      console.log(`oidc:${configurationName}:${eventName}`, data);
    };
    
    export default function Layout({ children }) {
      const router = useRouter();
    
      // Required for Next.js integration to sync history
      const withCustomHistory = () => {
        return {
          replaceState: url => {
            router
              .replace({
                pathname: url,
              })
              .then(() => {
                window.dispatchEvent(new Event('popstate'));
              });
          },
        };
      };
    
      return (
        <>
          <OidcProvider
            configuration={configuration}
            onEvent={onEvent}
            withCustomHistory={withCustomHistory}
          >
            <main>{children}</main>
          </OidcProvider>
        </>
      );
    }
  6. Install @axa-fr/oidc-client

    main

    To use the core pure JavaScript OIDC library, install it via npm.

    If you intend to use the Service Worker mode for enhanced security (where tokens are not accessible to the main JavaScript thread), you must also copy the required Service Worker files to your public directory. It is highly recommended to add this as a postinstall script in your package.json to ensure the Service Worker stays in sync with the library version during updates.

    npm install @axa-fr/oidc-client --save
    
    # Copy Service Worker files to your public folder
    node ./node_modules/@axa-fr/oidc-client/bin/copy-service-worker-files.mjs public
  7. Update OidcProvider error component prop for v5

    main

    In version 5, the OidcProvider component has renamed its error component prop. If you are migrating from v4, you must update the prop name used to provide a custom error component during the authentication process.

    Changes in OidcProvider:

    • Replace callbackErrorComponent with authenticatingErrorComponent.
    // In v5, use authenticatingErrorComponent instead of callbackErrorComponent
    <OidcProvider authenticatingErrorComponent={MyErrorComponent} ... />
  8. Manage the oidc-client-demo project with npm scripts

    main

    This project is built using Create React App. You can manage the development lifecycle using the following npm commands:

    • Development: Use npm start to run the application in development mode. The app will be available at http://localhost:3000 and will automatically reload on file changes.
    • Testing: Use npm test to launch the test runner in interactive watch mode.
    • Production Build: Use npm run build to create an optimized, minified production build in the build folder, ready for deployment.
    • Ejecting: Use npm run eject to expose the underlying build configuration (webpack, Babel, etc.). Warning: This is a one-way operation and cannot be undone.
    npm start
    npm test
    npm run build
    npm run eject
  9. Prevent login race conditions in multi-tab environments without Service Worker

    main

    If you are not using a Service Worker and use storage: localStorage, multiple tabs initiating login simultaneously can overwrite each other's authorization state (state, code_verifier, etc.), leading to state mismatch errors.

    Recommended Fix: Use login_state_storage: sessionStorage. This isolates the authorization flow state to the specific tab while allowing tokens to persist across tabs via localStorage.

    export const configuration = {
      client_id: 'interactive.public.short',
      redirect_uri: window.location.origin + '/authentication/callback',
      scope: 'openid profile email api offline_access',
      authority: 'https://demo.duendesoftware.com',
      service_worker_only: false,
      storage: localStorage,         // tokens persist across tabs
      login_state_storage: sessionStorage, // authorization state is isolated per tab
    };
  10. Migrate useOidcUser hook from v4 to v5

    main

    When migrating from version 4 to version 5, the useOidcUser hook has undergone property renaming. Specifically, the isLogged and isOidcUserLoading properties are replaced by a single state object.

    Changes in useOidcUser:

    • Remove isLogged.
    • Remove isOidcUserLoading.
    • Add oidcUserLoadingState.
    // old v4
    const { oidcUser, isOidcUserLoading, isLogged } = useOidcUser();
    
    // in v5 becomes
    const { oidcUser, oidcUserLoadingState } = useOidcUser();
  11. Migrate useOidc hook from v4 to v5

    main

    When migrating from version 4 to version 5, the useOidc hook has undergone property renaming. Specifically, the isLogged property is replaced by isAuthenticated.

    Changes in useOidc:

    • Rename isLogged to isAuthenticated.
    // old v4
    const { login, logout, isLogged } = useOidc();
    
    // in v5 becomes
    const { login, logout, isAuthenticated } = useOidc();