Only this pageAll pages
Powered by GitBook
1 of 14

v3

Getting started

Loading...

Loading...

Loading...

Loading...

Example setups

Loading...

Loading...

API Reference

Loading...

Loading...

React API

createOidcProvider

createUseOidc

Installation

Let's install oidc-spa in your project:

npm install --save oidc-spa
yarn add oidc-spa
pnpm add oidc-spa
bun add oidc-spa

Create the following file in your public directory:

public/silent-sso.html
<html>
    <body>
        <script>
            parent.postMessage(location.href, location.origin);
        </script>
    </body>
</html>

Tanstack Router

You can see a live preview of this example

here

Usage

Let's get your App authenticated!

In this guide, we assume that you have an OIDC-enabled authentication server in place, such as Keycloak.

In the realm of web development, there are two prevailing philosophies. One camp of developers advocates for a strict separation of concerns, maintaining a clear distinction between the core logic of their application and their UI components. Conversely, others prefer to embed the logic of their application directly within their components, minimizing the conceptual gap between related elements.

Regardless of your preference, we've got you covered. In addition to the base API, we offer a React Context/Hook API designed for seamless integration within your component body, ensuring flexibility and ease of use no matter your development style.

If you are coding in vanilla or implementing the OIDC-SPA library in an application with a strict separation of concerns between your core logic and UI components, follow these steps:

// Import the necessary functions from oidc-spa library
import { createOidc, decodeJwt } from "oidc-spa";

// Initialize OIDC client with configuration options
const oidc = await createOidc({
    issuerUri: "https://auth.your-domain.net/auth/realms/myrealm",
    clientId: "myclient",
    /**
     * Optional, you can modify the url before redirection to the identity 
     * server. Alternatively you can use: 
     * getExtraQueryParams: ()=> ({ ui_locales: "fr" })
     */
    transformUrlBeforeRedirect: url => `${url}&ui_locales=fr`
    /**
     * This parameter have to be provided if your App is not hosted at the 
     * origin of the domain.
     * For example if your site is accessed by navigating to 
     * https://www.example.com you don't have to provide this parameter.
     * On the other end if your site is accessed by navigating to 
     * https://www.example.com/my-app
     * Then you want to set publicUrl to `/my-app` 
     * (or `https://www.example.com/my-app`).
     
     * If you are using Vite: `publicUrl: import.meta.env.BASE_URL`
     * If you using Create React App: `publicUrl: process.env.PUBLIC_URL`
     *
     * Be mindful that `${window.location.origin}${publicUrl}/silent-sso.html` 
     * must return the `silent-sso.html` that you have created in the previous
     * step.
     */
    //publicUrl: import.meta.env.BASE_URL
});

if (!oidc.isUserLoggedIn) {
    // This return a promise that never resolve. 
    // The user will be redirected to the identity server.
    oidc.login({
         /** 
          * doesCurrentHrefRequiresAuth determines the behavior when a user 
          * gives up on loggin in and navigate back.
          * When it happens we don't want to send him back to a page that
          * can't be accessed without authentication.
          *
          * If you are calling login() as a result of the user clicking
          * on a 'login' button (like here) you should set 
          * doesCurrentHrefRequiresAuth to false.
          *
          * When you are calling login because your user navigated to a path 
          * that requires authentication you should set 
          * doesCurrentHrefRequiresAuth to true
          */
         doesCurrentHrefRequiresAuth: false
         /** Optionally, you can add some extra parameter 
          *  to be added on the login url.
          */
         //extraQueryParams: { kc_idp_hint: "google" }
    });
} else {
    const {
        // The accessToken is what you'll use as a Bearer token to 
        // authenticate to your APIs
        accessToken,
        // You can parse the idToken as a JWT to get some information 
        // about the user.
        idToken
    } = oidc.getTokens();

    // NOTE: In most application you do not need to look into the JWT of the 
    // idToken on the frontend, you usually obtain the user info by querying 
    // a GET /user endpoint with a authorization header like 
    // `Bearer <accessToken>`.
    const user = decodeJwt(idToken) as {
        // Use https://jwt.io/ to tell what's in your idToken
        sub: string;
        preferred_username: string;
    };

    console.log(`Hello ${user.preferred_username}`);

    // To call when the user click on logout.
    // You can also redirect to a custom url with 
    // { redirectTo: "specific url", url: `${location.origin}/bye` }
    oidc.logout({ redirectTo: "home" });
}

This piece of code should give you the necessary information to understand how oidc-spa can be used inside your react components. To go further you can refer to the examples setup to see how to integrate oidc-spa with your routing library:

import { createOidcProvider, createUseOidc } from "oidc-spa/react";
import { z } from "zod";

const { OidcProvider } = createOidcProvider({
    issuerUri: "https://auth.your-domain.net/auth/realms/myrealm",
    clientId: "myclient",
    /**
     * Optional, you can modify the url before redirection to the identity 
     * server. Alternatively you can use: 
     * getExtraQueryParams: ()=> ({ ui_locales: "fr" })
     */
    transformUrlBeforeRedirect: url => `${url}&ui_locales=fr`
    /**
     * This parameter have to be provided if your App is not hosted at the 
     * origin of the domain.
     * For example if your site is accessed by navigating to 
     * https://www.example.com you don't have to provide this parameter.
     * On the other end if your site is accessed by navigating to 
     * https://www.example.com/my-app
     * Then you want to set publicUrl to `/my-app` 
     * (or `https://www.example.com/my-app`).
     
     * If you are using Vite: `publicUrl: import.meta.env.BASE_URL`
     * If you using Create React App: `publicUrl: process.env.PUBLIC_URL`
     *
     * Be mindful that `${window.location.origin}${publicUrl}/silent-sso.html` 
     * must return the `silent-sso.html` that you have created in the previous
     * step.
     */
    //publicUrl: import.meta.env.BASE_URL
});

const { useOidc } = createUseOidc({
    /**
     * This parameter is optional.
     * It allows you to validate the shape of the idToken so that you
     * can trust that oidcTokens.decodedIdToken is of the expected shape
     * when the user is logged in.
     * What is actually inside the idToken is defined by the OIDC server
     * you are using.
     * If you are not sure, you can copy the content of oidcTokens.idToken
     * and paste it on https://jwt.io/ to see what is inside.
     *
     * The usage of zod here is just an example, you can use any other schema
     * validation library or write your own validation function.
     *
     * If you want to specify the type of the decodedIdToken but do not care
     * about validating the shape of the decoded idToken at runtime you can
     * call `createUseOidc<DecodedIdToken>()` without passing any parameter.
     *
     * Note however that in most webapp you do not need to look into the JWT
     * of the idToken on the frontend side, you usually obtain the user info
     * by querying a GET /user endpoint with a authorization header
     * like `Bearer <accessToken>`.
     * If you don't use the decodedIdToken just do:
     * `export const { useOidc } = createUseOidc()`
     */
    decodedIdTokenSchema: z.object({
        sub: z.string(),
        preferred_username: z.string()
    })
});

ReactDOM.render(
    <OidcProvider
        // Optional, it's usually so fast that a fallback is really not required.
        fallback={<>Checking authentication ⌛️</>}
    >
        <App />
    </OidcProvider>,
    document.getElementById("root")
);

function App() {

    const { isUserLoggedIn, login, logout, oidcTokens } = useOidc();

    return (
        isUserLoggedIn ? (
            <>
                <span>Hello {oidcTokens.decodedIdToken.preferred_username}</span>
                <button onClick={() => logout({ redirectTo: "home" })}>
                  Logout
                </button>
            </>
        ) : (
            <button onClick={() => login({ 
              /** 
               * doesCurrentHrefRequiresAuth determines the behavior when a user 
               * gives up on loggin in and navigate back.
               * When it happens we don't want to send him back to a page that
               * can't be accessed without authentication.
               *
               * If you are calling login() as a result of the user clicking
               * on a 'login' button (like here) you should set 
               * doesCurrentHrefRequiresAuth to false.
               *
               * When you are calling login because your user navigated to a path 
               * that requires authentication you should set 
               * doesCurrentHrefRequiresAuth to true
               */
              doesCurrentHrefRequiresAuth: false
              /** Optionally, you can add some extra parameter 
               *  to be added on the login url.
               */
              //extraQueryParams: { kc_idp_hint: "google" }
            })} >
              Login
            </button>
        )
    );
}

Refreshing token

The token refresh is handled automatically for you, however you can manually trigger a token refresh with oidc.renewTokens().(or const { renewTokens } = useOidc({ assertUserLoggedIn: true });

What happens if the OIDC server is down?

If the OIDC server is down or misconfigured an error get printed in the console, everything continues as normal with the user unauthenticated. If the user tries to login an alert saying that authentication is not available at the moment is displayed and nothing happens. This enable your the part of your app that do not requires authentication to remain up even when your identities server is facing issues.

If you have not yet set up such a server, please refer to .

our guide for instructions on how to provision and configure a Keycloak server
react-router-dom example setup
@tanstack/react-router example setup

Overview

A solution to implement user authentication in your webapplication

In straightforward terms, this library is ideal for those seeking to enable user login/registration in their web application. When used in conjunction with Keycloak (for example), it enables you to offer a modern and secure authentication experience with minimal coding effort. This includes options for signing in via Google, X, GitHub, or other social media platforms. We provide comprehensive guidance from beginning to end.

  • 🎓 Accessible to all skill levels; no need to be an OIDC expert.

  • 🛠️ Easy to set up; eliminates the need for creating special /login /logout routes.

  • 🎛️ Minimal API surface for ease of use.

  • ✨ Robust yet optional React integration.

  • 📖 Comprehensive documentation and project examples: End-to-end solutions for authenticating your app.

  • ✅ Best in class type safety: Enhanced API response types based on usage context.

Compared to alternatives

While oidc-client-ts serves as a comprehensive toolkit, our library aims to provide a simplified, ready-to-use adapter that will pass any security audit and that will just work out of the box on any browser. We utilize oidc-client-ts internally but abstract away most of its intricacies.

Our library takes a modular approach to OIDC and React, treating them as separate concerns that don't necessarily have to be intertwined. At its core, oidc-spa is a straightforward adapter that isn't tied to any specific UI framework, making it suitable for projects that enforce a strict separation of concerns between the core logic of the application and the UI. Additionally, we provide adapters for React and starter projects for integration with react-router-dom or @tanstack/react-router.

Notable project using the library

Onyxia:

The French Interministerial Base of Free Software:

An OIDC client tailored for Single Page Applications, particularly suitable for projects. This library is intended for scenarios such as integrating your application with .

Beside the fact that this lib only works with Keycloak .

createOidc()

The createOidc() function is a function that assists in creating an OpenID Connect (OIDC) client, which can be utilized in your application for authentication purposes.

Parameters

params: An object containing the following properties:

  • issuerUri: string The URI of the OpenID Connect issuer.

  • clientId: string The client ID assigned to your application.

  • transformUrlBeforeRedirect?: (url:string) => string (Optional), A function that transforms the string URL before redirection.

  • getExtraQueryParams?: () => Record<string,string> (Optional), A function that returns extra query parameters.

  • publicUrl?: string (Optional), The public URL of your application, useful when your app is not hosted at the origin of the domain.

Returns

A Promise that resolves to an object of type Oidc, which can be either Oidc.LoggedIn or Oidc.NotLoggedIn.

Oidc.LoggedIn

Represents a logged-in state.

  • isUserLoggedIn: true.

  • renewTokens: () => Promise<void> A function to renew tokens.

  • getTokens: () => Tokens: A function that returns the current tokens.

    type Tokens = {
            accessToken: string;
            accessTokenExpirationTime: number;
            idToken: string;
            refreshToken: string;
            refreshTokenExpirationTime: number;
        };
  • subscribeToTokensChange A function to subscribe to token changes.

    • Params

      • onTokenChange: () => void A function to execute when token changes

    • Returns

      • unsubscribe: () => void

  • logout A function to log out.

    • Params

      • redirectTo: "home" | "current page" to be redirected after logout to home or current page

      • OR redirectTo: "specific url", url: string to be redirected to specific url

    • Returns

      • Promise<never>

Oidc.NotLoggedIn

Represents a not-logged-in state.

  • isUserLoggedIn: false.

  • loginA function to initiate the login process.

    • Params

      • doesCurrentHrefRequiresAuth (boolean): Whether the current href requires authentication.

      • extraQueryParams (optional, object): Extra query parameters for login.

    • Returns

      • Promise<never>This promise never resolves because we are redirected to oidc web service

Example

import { createOidc } from 'oidc-spa';

const oidc = await createOidc({
  issuerUri: 'https://your-issuer.com',
  clientId: 'your-client-id',
  transformUrlBeforeRedirect: (url) => `${url}&ui_locales=fr`,
  getExtraQueryParams: () => {kc_idp_hint: "idp1" }/* your extra query params */,
  publicUrl: '/my-app',
});
Vite
Keycloak
oidc-client-ts
react-oidc-context
keycloak-js
it is also likely to be deprecated
Source code
Public instance
Source code
Deployment of the website
LogoOidc-spa Tanstack Router Example - StackBlitzStackBlitz

Vanilla API

Usage with Keycloak

Let's spin up a Keycloak server and configure it for your webapp!

Provisioning a Keycloak server

If you already have access to a Keycloak server you can skip this section.

Configuring your Keycloak server

Let's configure your Keycloak server with good default for an SPA.

Connect to the admin panel of your Keycloak server (we assumes it's https://auth.my-domain.net/auth)

  • Create a realm called "myrealm" (or something else), go to Realm settings

    1. On the tab General

      1. User Profile Enabled: On

    2. On the tab login

      1. User registration: On

      2. Forgot password: On

      3. Remember me: On

      1. From: noreply@my-domain.net

      2. Host: email-smtp.us-east-2.amazonaws.com

      3. Port: 465

      4. Authentication: enabled

      5. Username: **************

      6. Password: ***************************************

      7. When clicking "save" you'll be asked for a test email, you have to provide one that correspond to a pre-existing user or you will get a silent error and the credentials won't be saved.

    3. On the tab Themes

      1. Login theme: keycloak (see next section to see how to create a theme for your app)

      2. Email theme: keycloak

    4. On the tab Localization

      1. Internationalization: Enabled

      2. Supported locales: <Select the languages you wish to support>

    5. On the tab Sessions

      1. SSO Session Idle: 14 days - This setting and the following two are so that when the user click "remember me" when he logs in, he doesn't have to login again for the next two weeks.

      2. SSO Session Idle Remember Me: 14 days

      3. SSO Session Max Remember Me: 14 days

  • Create a new OpenID Connect client called "myclient" (or something else) by accessing Clients -> Create Client

    1. Root URL: https://your-domain.net (or something else, your app does not need to be on the

    the same domain as your Keycloak).

    1. Valid redirect URIs: https://onyxia.my-domain.net/*, http://localhost* (for testing in local)

    2. Web origins: *

    3. Login theme: keycloak (or your theme if you have one)

  • (OPTIONAL) In Authentication (on the left panel) -> Tab Required Actions enable and set as default action Therms and Conditions. (You can use Keycloakify to specify your therme and condition, see next section)

  • (OPTIONAL) On the left pannel you can go to identity provider to enable login via Google, GitHub, Instagram, ect...

Now the parameter that you will have to provide to oidc-spa are:

    issuerUri: "https://auth.your-domain.net/auth/realms/myrealm",
    clientId: "myclient"

Replace your-domain.net, myrealm and myclient by what you actually used in the configuration process.

Customizing the login and register page

By default, Keycloak comes with very generic login and register pages, you might want to customize them to match the design system of your App. We've createad a lib that let you create Keycloak theme using React!

Follow one of the following guides:

Don't want to deploy and maintain a own Keycloak server yourself?

Choosing Keycloak as a Service through a cloud IAM provider can offload the complexities of management and maintenance. It ensures that your system is always up-to-date with the latest security patches and features without the direct overhead of server upkeep. This is especially beneficial for teams prioritizing development and innovation over infrastructure management, offering robust support and service level agreements to guarantee smooth operation.

On the tab email, we give an example with , if you don't have a SMTP server at hand you can skip this by going to Authentication (on the left panel) -> Tab Required Actions -> Uncheck "set as default action" Verify Email. Be aware that with email verification disable, anyone will be able to sign up to your service.

AWS SES

React Router

You can see a live preview of this example

here
LogoOpenJDK - Keycloak
Get started with Keycloak on bare metal
LogoDocker - Keycloak
Get started with Keycloak on Docker
LogoKubernetes - Keycloak
Get started with Keycloak on Kubernetes
LogoOpenShift - Keycloak
Get started with Keycloak on OpenShift
LogoPodman - Keycloak
Get started with Keycloak on Podman
LogoCloud IAM - Keycloak Identity and Access Management as a Service
LogoKeycloakify
LogoOidc-spa React Router Example - StackBlitzStackBlitz