--- title: Keycloak --- import useBaseUrl from '@docusaurus/useBaseUrl'; import DefaultBehaviour from './\_default-behaviour.md'; import OverrideIntro from './\_override-intro.md'; import OverrideExampleIntro from './\_override-example-intro.md'; import UsingAuthNote from './\_using-auth-note.md'; import WaspFileStructureNote from './\_wasp-file-structure-note.md'; import GetUserFieldsType from './\_getuserfields-type.md'; import ApiReferenceIntro from './\_api-reference-intro.md'; import UserSignupFieldsExplainer from '../\_user-signup-fields-explainer.md'; Wasp supports Keycloak Authentication out of the box. [Keycloak](https://www.keycloak.org/) is an open-source identity and access management solution for modern applications and services. Keycloak provides both SAML and OpenID protocol solutions. It also has a very flexible and powerful administration UI. Let's walk through enabling Keycloak authentication, explain some of the default settings, and show how to override them. ## Setting up Keycloak Auth Enabling Keycloak Authentication comes down to a series of steps: 1. Enabling Keycloak authentication in the Wasp file. 1. Adding the `User` entity. 1. Creating a Keycloak client. 1. Adding the necessary Routes and Pages 1. Using Auth UI components in our Pages. ### 1. Adding Keycloak Auth to Your Wasp File Let's start by properly configuring the Auth object: ```wasp title="main.wasp" app myApp { wasp: { version: "^0.13.0" }, title: "My App", auth: { // 1. Specify the User entity (we'll define it next) // highlight-next-line userEntity: User, methods: { // 2. Enable Keycloak Auth // highlight-next-line keycloak: {} }, onAuthFailedRedirectTo: "/login" }, } ``` ```wasp title="main.wasp" app myApp { wasp: { version: "^0.13.0" }, title: "My App", auth: { // 1. Specify the User entity (we'll define it next) // highlight-next-line userEntity: User, methods: { // 2. Enable Keycloak Auth // highlight-next-line keycloak: {} }, onAuthFailedRedirectTo: "/login" }, } ``` The `userEntity` is explained in [the social auth overview](../../auth/social-auth/overview#social-login-entity). ### 2. Adding the User Entity Let's now define the `app.auth.userEntity` entity: ```wasp title="main.wasp" // ... // 3. Define the User entity // highlight-next-line entity User {=psl id Int @id @default(autoincrement()) // ... psl=} ``` ```wasp title="main.wasp" // ... // 3. Define the User entity // highlight-next-line entity User {=psl id Int @id @default(autoincrement()) // ... psl=} ``` ### 3. Creating a Keycloak Client 1. Log into your Keycloak admin console. 1. Under **Clients**, click on **Create Client**. ![Keycloak Screenshot 1](/img/auth/keycloak/1-keycloak.png) 1. Fill in the **Client ID** and choose a name for the client. ![Keycloak Screenshot 2](/img/auth/keycloak/2-keycloak.png) 1. In the next step, enable **Client Authentication**. ![Keycloak Screenshot 3](/img/auth/keycloak/3-keycloak.png) 1. Under **Valid Redirect URIs**, add `http://localhost:3001/auth/keycloak/callback` for local development. ![Keycloak Screenshot 4](/img/auth/keycloak/4-keycloak.png) - Once you know on which URL(s) your API server will be deployed, also add those URL(s). - For example: `https://my-server-url.com/auth/keycloak/callback`. 1. Click **Save**. 1. In the **Credentials** tab, copy the **Client Secret** value, which we'll use in the next step. ![Keycloak Screenshot 5](/img/auth/keycloak/5-keycloak.png) ### 4. Adding Environment Variables Add these environment variables to the `.env.server` file at the root of your project (take their values from the previous step): ```bash title=".env.server" KEYCLOAK_CLIENT_ID=your-keycloak-client-id KEYCLOAK_CLIENT_SECRET=your-keycloak-client-secret KEYCLOAK_REALM_URL=https://your-keycloak-url.com/realms/master ``` We assumed in the `KEYCLOAK_REALM_URL` env variable that you are using the `master` realm. If you are using a different realm, replace `master` with your realm name. ### 5. Adding the Necessary Routes and Pages Let's define the necessary authentication Routes and Pages. Add the following code to your `main.wasp` file: ```wasp title="main.wasp" // ... // 6. Define the routes route LoginRoute { path: "/login", to: LoginPage } page LoginPage { component: import { Login } from "@src/pages/auth.jsx" } ``` ```wasp title="main.wasp" // ... // 6. Define the routes route LoginRoute { path: "/login", to: LoginPage } page LoginPage { component: import { Login } from "@src/pages/auth.tsx" } ``` We'll define the React components for these pages in the `src/pages/auth.{jsx,tsx}` file below. ### 6. Create the Client Pages :::info We are using [Tailwind CSS](https://tailwindcss.com/) to style the pages. Read more about how to add it [here](../../project/css-frameworks). ::: Let's now create an `auth.{jsx,tsx}` file in the `src/pages`. It should have the following code: ```tsx title="src/pages/auth.jsx" import { LoginForm } from 'wasp/client/auth' export function Login() { return ( ) } // A layout component to center the content export function Layout({ children }) { return (
{children}
) } ```
```tsx title="src/pages/auth.tsx" import { LoginForm } from 'wasp/client/auth' export function Login() { return ( ) } // A layout component to center the content export function Layout({ children }: { children: React.ReactNode }) { return (
{children}
) } ```
:::info Auth UI Our pages use an automatically generated Auth UI component. Read more about Auth UI components [here](../../auth/ui). ::: ### Conclusion Yay, we've successfully set up Keycloak Auth! Running `wasp db migrate-dev` and `wasp start` should now give you a working app with authentication. To see how to protect specific pages (i.e., hide them from non-authenticated users), read the docs on [using auth](../../auth/overview). ## Default Behaviour Add `keycloak: {}` to the `auth.methods` dictionary to use it with default settings: ```wasp title=main.wasp app myApp { wasp: { version: "^0.13.0" }, title: "My App", auth: { userEntity: User, methods: { // highlight-next-line keycloak: {} }, onAuthFailedRedirectTo: "/login" }, } ``` ```wasp title=main.wasp app myApp { wasp: { version: "^0.13.0" }, title: "My App", auth: { userEntity: User, methods: { // highlight-next-line keycloak: {} }, onAuthFailedRedirectTo: "/login" }, } ``` ## Overrides ### Data Received From Keycloak We are using Keycloak's API and its `/userinfo` endpoint to fetch the user's data. ```ts title="Keycloak user data" { sub: '5adba8fc-3ea6-445a-a379-13f0bb0b6969', email_verified: true, name: 'Test User', preferred_username: 'test', given_name: 'Test', family_name: 'User', email: 'test@example.com' } ``` The fields you receive will depend on the scopes you requested. The default scope is set to `profile` only. If you want to get the user's email, you need to specify the `email` scope in the `configFn` function. For up-to-date info about the data received from Keycloak, please refer to the [Keycloak API documentation](https://www.keycloak.org/docs-api/23.0.7/javadocs/org/keycloak/representations/UserInfo.html). ### Using the Data Received From Keycloak ```wasp title="main.wasp" app myApp { wasp: { version: "^0.13.0" }, title: "My App", auth: { userEntity: User, methods: { keycloak: { // highlight-next-line configFn: import { getConfig } from "@src/auth/keycloak.js", // highlight-next-line userSignupFields: import { userSignupFields } from "@src/auth/keycloak.js" } }, onAuthFailedRedirectTo: "/login" }, } entity User {=psl id Int @id @default(autoincrement()) username String @unique displayName String psl=} // ... ``` ```js title=src/auth/keycloak.js export const userSignupFields = { username: () => "hardcoded-username", displayName: (data) => data.profile.name, } export function getConfig() { return { scopes: ['profile', 'email'], } } ``` ```wasp title="main.wasp" app myApp { wasp: { version: "^0.13.0" }, title: "My App", auth: { userEntity: User, methods: { keycloak: { // highlight-next-line configFn: import { getConfig } from "@src/auth/keycloak.js", // highlight-next-line userSignupFields: import { userSignupFields } from "@src/auth/keycloak.js" } }, onAuthFailedRedirectTo: "/login" }, } entity User {=psl id Int @id @default(autoincrement()) username String @unique displayName String psl=} // ... ``` ```ts title=src/auth/keycloak.ts import { defineUserSignupFields } from 'wasp/server/auth' export const userSignupFields = defineUserSignupFields({ username: () => "hardcoded-username", displayName: (data: any) => data.profile.name, }) export function getConfig() { return { scopes: ['profile', 'email'], } } ``` ## Using Auth ## API Reference ```wasp title="main.wasp" app myApp { wasp: { version: "^0.13.0" }, title: "My App", auth: { userEntity: User, methods: { keycloak: { // highlight-next-line configFn: import { getConfig } from "@src/auth/keycloak.js", // highlight-next-line userSignupFields: import { userSignupFields } from "@src/auth/keycloak.js" } }, onAuthFailedRedirectTo: "/login" }, } ``` ```wasp title="main.wasp" app myApp { wasp: { version: "^0.13.0" }, title: "My App", auth: { userEntity: User, methods: { keycloak: { // highlight-next-line configFn: import { getConfig } from "@src/auth/keycloak.js", // highlight-next-line userSignupFields: import { userSignupFields } from "@src/auth/keycloak.js" } }, onAuthFailedRedirectTo: "/login" }, } ``` The `keycloak` dict has the following properties: - #### `configFn: ExtImport` This function must return an object with the scopes for the OAuth provider. ```js title=src/auth/keycloak.js export function getConfig() { return { scopes: ['profile', 'email'], } } ``` ```ts title=src/auth/keycloak.ts export function getConfig() { return { scopes: ['profile', 'email'], } } ``` - #### `userSignupFields: ExtImport` Read more about the `userSignupFields` function [here](../overview#1-defining-extra-fields).