{"id":19154887,"url":"https://github.com/authts/sample-keycloak-react-oidc-context","last_synced_at":"2025-04-09T17:25:17.413Z","repository":{"id":235187397,"uuid":"790250586","full_name":"authts/sample-keycloak-react-oidc-context","owner":"authts","description":"Sample keycloak project using react-oidc-context","archived":false,"fork":false,"pushed_at":"2025-03-25T15:02:11.000Z","size":1277,"stargazers_count":61,"open_issues_count":3,"forks_count":17,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-04-02T16:06:43.285Z","etag":null,"topics":["keycloak","oidc-client","react","reactjs","sample"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/authts.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-04-22T14:39:07.000Z","updated_at":"2025-04-01T19:30:14.000Z","dependencies_parsed_at":"2024-04-22T16:16:05.539Z","dependency_job_id":"70efc72c-a33f-4d2b-a809-3bcaedd975b5","html_url":"https://github.com/authts/sample-keycloak-react-oidc-context","commit_stats":null,"previous_names":["authts/sample-react-oidc-context-keycloak"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/authts%2Fsample-keycloak-react-oidc-context","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/authts%2Fsample-keycloak-react-oidc-context/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/authts%2Fsample-keycloak-react-oidc-context/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/authts%2Fsample-keycloak-react-oidc-context/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/authts","download_url":"https://codeload.github.com/authts/sample-keycloak-react-oidc-context/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248075850,"owners_count":21043654,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["keycloak","oidc-client","react","reactjs","sample"],"created_at":"2024-11-09T08:28:43.767Z","updated_at":"2025-04-09T17:25:17.392Z","avatar_url":"https://github.com/authts.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# sample-keycloak-react-oidc-context\n\nMinimal, reproducible example for Keycloak + React.\n\n## Table of contents\n\n\u003c!-- toc --\u003e\n\n- [Screenshots](#screenshots)\n- [Why](#why)\n- [Setup](#setup)\n  * [Docker Compose](#docker-compose)\n  * [OPTIONAL: Run React app outside of Docker Compose](#optional-run-react-app-outside-of-docker-compose)\n- [Config](#config)\n- [Links](#links)\n  * [React app](#react-app)\n  * [Keycloak account console](#keycloak-account-console)\n  * [Keycloak admin console](#keycloak-admin-console)\n  * [Mailhog UI](#mailhog-ui)\n- [Scenarios](#scenarios)\n  * [Cross-tab login/logout](#cross-tab-loginlogout)\n  * [API requests with and without token](#api-requests-with-and-without-token)\n  * [Register new user](#register-new-user)\n- [High-level summary of common flows](#high-level-summary-of-common-flows)\n  * [Logging into the React app](#logging-into-the-react-app)\n  * [Making an API request](#making-an-api-request)\n- [Seeded data](#seeded-data)\n  * [Clients](#clients)\n  * [Login](#login)\n  * [Tokens](#tokens)\n  * [Email](#email)\n- [Sharing](#sharing)\n- [Disclaimers](#disclaimers)\n- [History](#history)\n- [Contributing](#contributing)\n- [Releases](#releases)\n\n\u003c!-- tocstop --\u003e\n\n## Screenshots\n\n\u003cdetails\u003e\n\n\u003csummary\u003eScreenshots\u003c/summary\u003e\n\n**Login**:\n![](assets/screencapture-login.png)\n\n**Home**:\n![](assets/screencapture-home.png)\n\n**Playground**:\n![](assets/screencapture-playground.png)\n\n\u003c/details\u003e\n\n## Why\n\nI often see questions in the [Keycloak forums](https://keycloak.discourse.group) on how to use it with React. On the other hand, I often see questions in the [react-oidc-context repo](https://github.com/authts/react-oidc-context) on how to use it with Keycloak.\n\nSo, 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.\n\n\u003e The noblest pleasure is the joy of understanding. - Leonardo da Vinci\n\n## Setup\n\nIn one terminal, run the Postgres database, Keycloak server, Mailhog server, Express API, and React app via Docker Compose.\n\n**OPTIONALLY**, in another terminal, you may run the React app outside of Docker Compose.\n\n### Docker Compose\n\n1. Install [Docker](https://docs.docker.com/get-docker/)\n1. Copy file `.env.sample` to file `.env`\n\n        cp .env.sample .env\n\n1. Build images\n\n        docker compose build\n\n1. Run containers\n\n        docker compose up\n\n1. Wait until you see a message from the Keycloak server like this: _Running the server in development mode. DO NOT use this configuration in production._\n1. See [links](#links) for username and password\n\n### OPTIONAL: Run React app outside of Docker Compose\n\n\u003cdetails\u003e\n\n\u003csummary\u003eOptional steps\u003c/summary\u003e\n\n1. Stop the React app container\n\n        docker compose stop react\n\n1. Install [Node.js](https://nodejs.org/en)\n1. Change to `react` folder\n\n        cd react\n\n1. Create file `.env.local` with contents\n\n        VITE_PORT=5173\n        VITE_AUTHORITY=http://localhost:8080/realms/master\n        VITE_CLIENT_ID=react\n        VITE_API_BASE_URL=http://localhost:5174\n\n1. Install packages\n\n        npm install\n\n1. Start dev server\n\n        npm run dev\n\n1. See [links](#links) for username and password\n\n\u003c/details\u003e\n\n## Config\n\nThe Docker Compose config should work as-is. If you need to customize it, then edit file `.env`\n\n## Links\n\n### React app\n\n- **Link**: http://localhost:5173\n- **Username**:\n\n      admin@example.com\n\n- **Password**:\n\n      juggle-prance-shallot-wireless-outlet\n\n### Keycloak account console\n\n- **Link**: http://localhost:8080/realms/master/account/\n- **Credentials**: _same as React app_\n\n### Keycloak admin console\n\n- **Link**: http://localhost:8080/admin/master/console/\n- **Credentials**: _same as React app_\n\n### Mailhog UI\n\n- **Link**: http://localhost:8025\n\n## Scenarios\n\nHere are some scenarios you can play with:\n\n### Cross-tab login/logout\n\n1. Login to the React app\n1. Copy-paste the link into another browser tab\n1. Notice how you're automatically logged in\n1. Logout in one of the browser tabs\n1. Notice how you're automatically logged out of both browser tabs\n\n### API requests with and without token\n\n1. Login to the React app\n1. Open your browser **DevTools**, go to the **Network** tab, and filter requests by **Fetch/XHR**\n1. Go to the **Playground** page in the React app\n1. Notice how the request **without** a Bearer token gets a 401, but the request **with** a Bearer token gets a 200\n\n### Register new user\n\n1. Go to the login page\n1. Click **Register**\n1. Fill out the fields for a fake user\n1. Click **Register**\n1. Open the Mailhog UI\n1. Click the email from `no-reply@example.com` with subject **Verify email**\n1. Click the **Link to e-mail address verification**\n1. Notice how you're automatically logged into the React app with your newly created user\n\n## High-level summary of common flows\n\n### Logging into the React app\n\nThankfully the [react-oidc-context](https://github.com/authts/react-oidc-context) and [oidc-client-ts](https://github.com/authts/oidc-client-ts) libraries do the heavy lifting for us.\n\nThis flow is known as [Authorization Code Grant with Proof Key for Code Exchange (PKCE)](https://github.com/authts/oidc-client-ts/blob/main/docs/protocols/authorization-code-grant-with-pkce.md).\n\nYou 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**.\n\nI'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.\n\n1. Go to the React app\n    - **Request URL**:\n\n          GET http://localhost:5173/\n\n1. The OpenID config is fetched\n    - **Request URL**:\n\n          GET http://localhost:8080/realms/master/.well-known/openid-configuration\n\n    - **Response body**: _Omitted for brevity. Go to the request URL to see it_\n1. You're not logged in, so you're redirected to the Keycloak login page\n    - **Request URL**:\n\n          GET http://localhost:8080/realms/master/protocol/openid-connect/auth\n\n    - **Request query params**:\n\n          client_id: react\n          redirect_uri: http://localhost:5173/\n          response_type: code\n          scope: openid\n          state: 391fb773c982414baed2250583113efc\n          code_challenge: K-Hp16LIAH8r6QSjOmfYh7zJtySFjWFRsVN-4s72st0\n          code_challenge_method: S256\n\n1. Submit your username and password\n    - **Request URL**:\n\n          POST http://localhost:8080/realms/master/login-actions/authenticate\n\n    - **Request query params**:\n\n          session_code: NSBRN5i4WCHlUNM-Fr_7sVGv_luCqlcuj-dYvRgPGbg\n          execution: 0c75cc68-1058-49a7-bdaa-d0f8ec69c1b8\n          client_id: react\n          tab_id: 1x0hcfo9RVk\n\n    - **Request form data**:\n\n          username: admin@example.com\n          password: juggle-prance-shallot-wireless-outlet\n          credentialId:\n\n1. On success, you're redirected to the React app\n    - **Request URL**:\n\n          GET http://localhost:5173/\n\n    - **Request query params**:\n\n          state: 391fb773c982414baed2250583113efc\n          session_state: 683043bb-2209-47ff-b0a5-2c0197ab2507\n          iss: http://localhost:8080/realms/master\n          code: 44891f5f-be56-4fc4-b970-8f1dd0d88fbe.683043bb-2209-47ff-b0a5-2c0197ab2507.acc4f3dc-25c9-4716-bfa5-cde9f19c8c32\n\n1. The token is fetched\n    - **Request URL**:\n\n          GET http://localhost:8080/realms/master/protocol/openid-connect/token\n\n    - **Request form data**:\n\n          grant_type: authorization_code\n          redirect_uri: http://localhost:5173/\n          code: 44891f5f-be56-4fc4-b970-8f1dd0d88fbe.683043bb-2209-47ff-b0a5-2c0197ab2507.acc4f3dc-25c9-4716-bfa5-cde9f19c8c32\n          code_verifier: d8df056c73d6437fa810e3a85f1784761ac3a8001b734dc593fef211ffdba501c0c82b6abeab4b989eb03fae34ad36c4\n          client_id: react\n\n    - **Response body**: _`access_token`, `refresh_token`, and `id_token` values truncated for brevity_\n\n          {\n              \"access_token\": \"eyJ...\",\n              \"expires_in\": 300,\n              \"refresh_expires_in\": 1800,\n              \"refresh_token\": \"eyJ...\",\n              \"token_type\": \"Bearer\",\n              \"id_token\": \"eyJ...\",\n              \"not-before-policy\": 0,\n              \"session_state\": \"683043bb-2209-47ff-b0a5-2c0197ab2507\",\n              \"scope\": \"openid email profile\"\n          }\n\n### Making an API request\n\n1. Go to the React app, login, then click the **Playground** page\n1. An API request is made, which the Vite dev server proxies to the correct location on the API server\n    - **Request URL**:\n\n          GET http://localhost:5173/api/payload\n\n    - **Request headers**: _Only relevant headers are included. The access token value is truncated for brevity_\n\n          authorization: Bearer eyJ...\n\n1. The API server receives the request. The token is parsed from the `authorization` header\n1. The API server requests the JSON web key set\n    - **Request URL**:\n\n          GET http://localhost:8080/realms/master/protocol/openid-connect/certs\n\n    - **Response body**: _Omitted for brevity. Go to the request URL to see it_\n1. The [jose](https://github.com/panva/jose) library consumes the JSON web key set, then uses it to verify the token\n1. The API server responds to the React app\n\n## Seeded data\n\nThe `db/init` folder contains SQL which is copied into the Postgres image and runs on container startup.\n\nI didn't write this SQL by hand. Instead, I customized the `master` realm a tad, then dumped the data. See `compose.jobs.yml`\n\n### Clients\n\nCreate a public client with:\n\n- **General settings** \u003e set **Client ID** to `react`\n- **Access settings** \u003e set **Valid redirect URIs** to `http://localhost:5173/*`\n- **Access settings** \u003e set **Valid post logout redirect URIs** to `http://localhost:5173/*`\n- **Access settings** \u003e set **Web origins** to `*`\n- **Authentication flow** \u003e  check **Standard flow**\n- **Authentication flow** \u003e check **Direct access grants**\n- **Logout settings** \u003e check **Front channel logout**\n- **Logout settings** \u003e check **Backchannel logout session required**\n\n### Login\n\n- **Realm settings** \u003e **Login** \u003e check **User registration**\n- **Realm settings** \u003e **Login** \u003e check **Forgot password**\n- **Realm settings** \u003e **Login** \u003e check **Remember me**\n- **Realm settings** \u003e **Login** \u003e check **Email as username**\n- **Realm settings** \u003e **Login** \u003e check **Login with email**\n- **Realm settings** \u003e **Login** \u003e check **Verify email**\n\n### Tokens\n\n- **Realm settings** \u003e **Tokens** \u003e set **Access Token Lifespan** to `5 mins`\n\n### Email\n\n- **Realm settings** \u003e **Email** \u003e set **From** to `no-reply@example.com`\n- **Realm settings** \u003e **Email** \u003e set **Host** to `mailhog`\n- **Realm settings** \u003e **Email** \u003e set **Port** to `1025`\n\n## Sharing\n\nPlaces where this project has been shared.\n\n- The [Keycloak forums](https://keycloak.discourse.group/t/minimal-reproducible-example-for-keycloak-react/25664)\n- The [react-oidc-context repo](https://github.com/authts/react-oidc-context/issues/1208)\n\n## Disclaimers\n\n- The `Dockerfile` for each service is optimized for local development mode. **DO NOT** use this configuration in production\n- 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\n\n## History\n\nThis repo originally lived at [zach-betz-hln/mre-keycloak-react](https://github.com/zach-betz-hln/mre-keycloak-react). For context, see this [issue](https://github.com/authts/react-oidc-context/issues/1208).\n\n## Contributing\n\n1. Create a branch from `main`, or a fork of this repo\n1. Make your changes\n1. Run through the **Setup** steps in this doc from scratch and confirm everything works\n1. Run the dump job\n\n        docker compose -f compose.yml -f compose.jobs.yml run dump\n\n1. PR your changes to be reviewed\n\n## Releases\n\n1. Create a branch from `main`\n1. Increment the [semantic version](https://docs.npmjs.com/about-semantic-versioning). `{semver}` should be one of: `major` | `minor` | `patch`\n\n        npm --no-git-tag-version version {semver}\n\n1. Update `CHANGELOG.md` with a new section\n1. PR your changes to be reviewed. Put the new semantic version in the PR title\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fauthts%2Fsample-keycloak-react-oidc-context","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fauthts%2Fsample-keycloak-react-oidc-context","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fauthts%2Fsample-keycloak-react-oidc-context/lists"}