Skip to content

authts/sample-keycloak-react-oidc-context

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

sample-keycloak-react-oidc-context

Minimal, reproducible example for Keycloak + React.

Table of contents

Why

I often see questions in the Keycloak forums on how to use it with React. On the other hand, I often see questions in the react-oidc-context repo on how to use it with Keycloak.

So, I thought it'd be cool to make a little project that glues these tools together, in hopes that others may play with it and get ideas for their own implementations.

The noblest pleasure is the joy of understanding. - Leonardo da Vinci

Setup

In one terminal, run the Postgres database, Keycloak server, Mailhog server, and Express API via Docker Compose.

In another terminal, run the React app.

Docker Compose

  1. Install Docker

  2. Copy file .env.sample to file .env

     cp .env.sample .env
    
  3. Build images

     docker compose build
    
  4. Run containers

     docker compose up
    

React app

  1. Install Node.js

  2. Change to react folder

     cd react
    
  3. Install packages

     npm install
    
  4. Start dev server

     npm run dev
    
  5. See links for username and password

Config

The Docker Compose config should work as-is. If you need to customize it, like changing what port a service runs on, then edit file .env

The React config should work as-is as well. If you need to customize it, then edit file react/.env

Links

React app

  • Link: http://localhost:5173

  • Username:

    admin@example.com
    
  • Password:

    juggle-prance-shallot-wireless-outlet
    

Keycloak account console

Keycloak admin console

Mailhog UI

Scenarios

Here are some scenarios you can play with:

Cross-tab login/logout

  1. Login to the React app
  2. Copy-paste the link into another browser tab
  3. Notice how you're automatically logged in
  4. Logout in one of the browser tabs
  5. Notice how you're automatically logged out of both browser tabs

API requests with and without token

  1. Login to the React app
  2. Open your browser DevTools, go to the Network tab, and filter requests by Fetch/XHR
  3. Go to the Playground page in the React app
  4. Notice how the request without a Bearer token gets a 401, but the request with a Bearer token gets a 200

Register new user

  1. Go to the login page
  2. Click Register
  3. Fill out the fields for a fake user
  4. Click Register
  5. Open the Mailhog UI
  6. Click the email from no-reply@example.com with subject Verify email
  7. Click the Link to e-mail address verification
  8. Notice how you're automatically logged into the React app with your newly created user

High-level summary of common flows

Logging into the React app

Thankfully the react-oidc-context and oidc-client-ts libraries do the heavy lifting for us.

This flow is known as Authorization Code Grant with Proof Key for Code Exchange (PKCE).

You can see this happen for yourself by doing: Open your browser DevTools, go to the Network tab, check Preserve log, and filter requests by Doc and Fetch/XHR.

I've copied the request query params and form data directly from DevTools for education purposes. Keep in mind that these values will likely be different for you.

  1. Go to the React app

    • Request URL:

      GET http://localhost:5173/
      
  2. The OpenID config is fetched

    • Request URL:

      GET http://localhost:8080/realms/master/.well-known/openid-configuration
      
    • Response body: Omitted for brevity. Go to the request URL to see it

  3. You're not logged in, so you're redirected to the Keycloak login page

    • Request URL:

      GET http://localhost:8080/realms/master/protocol/openid-connect/auth
      
    • Request query params:

      client_id: react
      redirect_uri: http://localhost:5173/
      response_type: code
      scope: openid
      state: 391fb773c982414baed2250583113efc
      code_challenge: K-Hp16LIAH8r6QSjOmfYh7zJtySFjWFRsVN-4s72st0
      code_challenge_method: S256
      
  4. Submit your username and password

    • Request URL:

      POST http://localhost:8080/realms/master/login-actions/authenticate
      
    • Request query params:

      session_code: NSBRN5i4WCHlUNM-Fr_7sVGv_luCqlcuj-dYvRgPGbg
      execution: 0c75cc68-1058-49a7-bdaa-d0f8ec69c1b8
      client_id: react
      tab_id: 1x0hcfo9RVk
      
    • Request form data:

      username: admin@example.com
      password: juggle-prance-shallot-wireless-outlet
      credentialId:
      
  5. On success, you're redirected to the React app

    • Request URL:

      GET http://localhost:5173/
      
    • Request query params:

      state: 391fb773c982414baed2250583113efc
      session_state: 683043bb-2209-47ff-b0a5-2c0197ab2507
      iss: http://localhost:8080/realms/master
      code: 44891f5f-be56-4fc4-b970-8f1dd0d88fbe.683043bb-2209-47ff-b0a5-2c0197ab2507.acc4f3dc-25c9-4716-bfa5-cde9f19c8c32
      
  6. The token is fetched

    • Request URL:

      GET http://localhost:8080/realms/master/protocol/openid-connect/token
      
    • Request form data:

      grant_type: authorization_code
      redirect_uri: http://localhost:5173/
      code: 44891f5f-be56-4fc4-b970-8f1dd0d88fbe.683043bb-2209-47ff-b0a5-2c0197ab2507.acc4f3dc-25c9-4716-bfa5-cde9f19c8c32
      code_verifier: d8df056c73d6437fa810e3a85f1784761ac3a8001b734dc593fef211ffdba501c0c82b6abeab4b989eb03fae34ad36c4
      client_id: react
      
    • Response body: access_token, refresh_token, and id_token values truncated for brevity

      {
          "access_token": "eyJ...",
          "expires_in": 300,
          "refresh_expires_in": 1800,
          "refresh_token": "eyJ...",
          "token_type": "Bearer",
          "id_token": "eyJ...",
          "not-before-policy": 0,
          "session_state": "683043bb-2209-47ff-b0a5-2c0197ab2507",
          "scope": "openid email profile"
      }
      

Making an API request

  1. Go to the React app, login, then click the Playground page

  2. An API request is made, which the Vite dev server proxies to the correct location on the API server

    • Request URL:

      GET http://localhost:5173/api/payload
      
    • Request headers: Only relevant headers are included. The access token value is truncated for brevity

      authorization: Bearer eyJ...
      
  3. The API server receives the request. The token is parsed from the authorization header

  4. The API server requests the JSON web key set

    • Request URL:

      GET http://localhost:8080/realms/master/protocol/openid-connect/certs
      
    • Response body: Omitted for brevity. Go to the request URL to see it

  5. The jose library consumes the JSON web key set, then uses it to verify the token

  6. The API server responds to the React app

Seeded data

The db/init folder contains SQL which is copied into the Postgres image and runs on container startup.

I didn't write this SQL by hand. Instead, I customized the master realm a tad, then dumped the data. See script task_dump_kc_data.sh

Clients

Create a public client with:

  • General settings > set Client ID to react
  • Access settings > set Valid redirect URIs to http://localhost:5173/*
  • Access settings > set Valid post logout redirect URIs to http://localhost:5173/*
  • Access settings > set Web origins to *
  • Authentication flow > check Standard flow
  • Authentication flow > check Direct access grants
  • Logout settings > check Front channel logout
  • Logout settings > check Backchannel logout session required

Login

  • Realm settings > Login > check User registration
  • Realm settings > Login > check Forgot password
  • Realm settings > Login > check Remember me
  • Realm settings > Login > check Email as username
  • Realm settings > Login > check Login with email
  • Realm settings > Login > check Verify email

Tokens

  • Realm settings > Tokens > set Access Token Lifespan to 5 mins

Email

  • Realm settings > Email > set From to no-reply@example.com
  • Realm settings > Email > set Host to mailhog
  • Realm settings > Email > set Port to 1025

Sharing

Places where this project has been shared.

Disclaimers

  • The Dockerfile for each service is optimized for local development mode. DO NOT use this configuration in production
  • For convenience, the admin user is also used for regular app logins. In production, the admin account would be locked down, and you'd have regular, non-admin users

History

This repo originally lived at zach-betz-hln/mre-keycloak-react. For context, see this issue.