Skip to main content

Keycloak

Create a permanent Admin account​

Navigate to the Keycloak Admin Console:

https://serendipity-identity-service.localhost/admin

And sign in using the SERENDIPITY_IDENTITY_SERVICE_ADMIN (temp-admin) and SERENDIPITY_IDENTITY_SERVICE_ADMIN_PASSWORD (secret) credentials (supplied via the .env file):

Keycloak Admin Console Sign In

You should see something like:

Keycloak Admin Console Welcome page

When you first start Keycloak you log in using the Keycloak bootstrap username and password. You should create a permanent Admin account in the master realm and delete the temporary one.

For example:

Create permanent Admin account

Don't forget to assign the admin role (role_admin) to the permanent admin account:

Assign Admin role to the permanent Admin account

And to set a password:

Set a password for the permanent Admin user

Now sign out and then sign back in using your permanent Admin account credentials.

tip

After signing back in with the permanent admin account, the Admin Console loads as before but now the top-right user menu shows the permanent admin's name. The temporary bootstrap user no longer appears — delete it from Users in the master realm.

Configuring realms​

A realm in Keycloak is analogous to a tenant. Each realm allows an administrator to create (isolated) groups of applications and users. Initially, Keycloak includes a single realm, the master realm.

The master realm should only be used to manage Keycloak.

Creating a realm​

To create a new realm, in the side menu click the dropdown menu and then click the Create realm button:

Keycloak Admin Console Create Realm

Enter a Realm name (e.g., serendipity-dev) and then click the Create button.

tip

After creation the realm selector (top-left of the Admin Console) now includes serendipity-dev alongside master. Selecting it takes you to that realm's dashboard. The realm's Realm settings page is reachable from the side menu and the Login tab shows the default login options for the realm.

To update a realm's settings, select Realm settings in the side menu:

Keycloak Admin Console Realm Settings

On the realm's settings Login tab you can control the options for users, applications, roles, and groups in the current realm:

Keycloak Admin Console Realm Settings

info

In newer versions of Keycloak (v19+ using the modern React-based Admin Console), toggles on the Login tab auto-save immediately via API requests, which is why there is no main Save button.

The new Admin Console heavily caches UI state. A hard refresh (Cmd + Shift + R on Mac, Ctrl + F5 on Windows) often reveals that the setting did save on the backend even though the frontend UI showed it toggled back off.

Managing users​

Create a user​

Verify that you are in the correct realm e.g., the Development Realm (serendipity-dev).

To create a new user, select Users in the side menu and then click the Create new user button:

Keycloak Admin Console Create User

Enable the Email verified slide toggle and enter an email address, a first name (i.e., given name), a last name (i.e., family name) and then click the Create button.

A user needs a password to sign in.

To create a password, select the Credentials tab and then click the Set password button:

Keycloak Admin Console Set Password

Enter a password, confirm the password and disable the Temporary slide toggle (so that the user does not need to update the password the first time they sign in) and then click the Save button.

Navigate to the Keycloak Account Console:

https://serendipity-identity-service.localhost:8443/realms/serendipity-dev/account

And sign in using the credentials you created:

Keycloak Account Console Sign In

You should see something like:

Keycloak Account Console Personal Info

Managing user attributes​

In Keycloak a user is associated with a set of attributes. These attributes are used to describe and identify users within Keycloak as well as to provide additional information about users to applications.

Default managed attributes​

By default, Keycloak provides a basic user profile configuration:

AttributeNotes
usernameThe primary login name — what the user types to sign in. Stable until changed. Not used for ownership/assignment comparisons (the enforcement layer uses sub).
emailThe preferred email address.
firstNameGiven name.
lastNameSurname / family name.

In Keycloak, both the username and email attributes can be used to identify, authenticate, and link user accounts.

The behavior of both username and email attributes changes accordingly to the Login settings of your realm. By default, Serendipity has enabled the Email as username setting.

Core fields (not profile attributes)​

These are tracked by Keycloak on the core user model but are not configurable profile attributes:

FieldNotes
idThe system-generated UUID. This is the value emitted as sub in tokens.
enabledWhether the account is active.
emailVerifiedWhether the email has been verified.
createdTimestampWhen the user was created.

Custom attributes​

These are added to the user's profile as custom attributes.

AttributeValueNotes
jobTitleA stringOptional profile attribute.
departmentA stringOptional profile attribute.
employeeTypeA stringOptional profile attribute.
managerThe sub (UUID) of the user's managerThe one attribute the access-control model depends on — it enables the manager hierarchy.

Manually setting the manager attribute​

The manager attribute stores the manager's sub (Keycloak UUID). To set it:

  1. Identify the manager's Keycloak sub.
    Open the (manager) user in the Admin Console and copy the id field. The id is the UUID you need.
  2. Open the direct report user in the Admin Console.
  3. Go to the Attributes tab.
  4. Set the attribute with Key = manager and Value = the manager's sub (the UUID copied in step 1).
  5. Click the Save button.
tip

The sub is not labeled "sub" in the Admin Console. It is the id field on the user's Details tab. That is the value (UUID) you copy into the manager attribute.

Managing the User Profile​

The user profile configuration is managed on a per-realm basis.

The user profile configuration is stored using a well-defined JSON schema:

{
"attributes": [
{
"name": "username",
"displayName": "${username}",
"validations": {
"length": {
"min": 3,
"max": 255
},
"username-prohibited-characters": {},
"up-username-not-idn-homograph": {}
},
"permissions": {
"view": [
"admin",
"user"
],
"edit": [
"admin",
"user"
]
},
"multivalued": false
},
{
"name": "email",
"displayName": "${email}",
"validations": {
"email": {},
"length": {
"max": 255
}
},
"required": {
"roles": [
"user"
]
},
"permissions": {
"view": [
"admin",
"user"
],
"edit": [
"admin",
"user"
]
},
"multivalued": false
},
{
"name": "firstName",
"displayName": "${firstName}",
"validations": {
"length": {
"max": 255
},
"person-name-prohibited-characters": {}
},
"required": {
"roles": [
"user"
]
},
"permissions": {
"view": [
"admin",
"user"
],
"edit": [
"admin",
"user"
]
},
"multivalued": false
},
{
"name": "lastName",
"displayName": "${lastName}",
"validations": {
"length": {
"max": 255
},
"person-name-prohibited-characters": {}
},
"required": {
"roles": [
"user"
]
},
"permissions": {
"view": [
"admin",
"user"
],
"edit": [
"admin",
"user"
]
},
"multivalued": false
},
{
"name": "jobTitle",
"displayName": "jobTitle",
"validations": {},
"annotations": {},
"permissions": {
"view": [],
"edit": [
"admin"
]
},
"multivalued": false
},
{
"name": "department",
"displayName": "department",
"validations": {},
"annotations": {},
"permissions": {
"view": [],
"edit": [
"admin"
]
},
"multivalued": false
},
{
"name": "employeeType",
"displayName": "employeeType",
"validations": {},
"annotations": {},
"permissions": {
"view": [],
"edit": [
"admin"
]
},
"multivalued": false
},
{
"name": "manager",
"displayName": "manager",
"validations": {},
"annotations": {},
"permissions": {
"view": [],
"edit": [
"admin"
]
},
"multivalued": false
}
],
"groups": [
{
"name": "user-metadata",
"displayHeader": "User metadata",
"displayDescription": "Attributes, which refer to user metadata"
}
]
}

Using external storage​

Adding a provider​

In Keycloak's Admin Console, for the serendipity-dev realm:

  1. User Federation → Add LDAP providers:

Add LDAP Provider

  1. Configure the General options:

Add LDAP Provider

  • UI display name: OpenLDAP
  • Vendor: Other
  1. Configure the Connection and authentication settings:

Add LDAP Provider

  • Connection URL: ldap://openldap:389
  • Bind DN: cn=admin,dc=shane-longman,dc=org
  • Bind credendials: secret
  1. Configure the LDAP searching and updating settings:

Add LDAP Provider

  • Edit mode: READ_ONLY if LDAP is the source of truth and Keycloak should not write back. WRITABLE if you want Keycloak to write changes back to OpenLDAP.
  • Users DN: ou=people,dc=shane-longman,dc=org
  • Username LDAP attribute: mail user sign in using their email address
  • RDN LDAP attribute: uid
  • UUID LDAP attribute: entryUUID
  • User object classes: inetOrgPerson, organizationalPerson, person
  • User LDAP filter: (objectClass=inetOrgPerson)
  1. Configure the Synchronisation settings:

Add LDAP Provider

And then click the Save button.

Standard Built-In Mappers​

Keycloak automatically creates standard mappers for core attributes when you set up an LDAP provider. Ensure their settings match your entry:

  • username: Maps Keycloak username → LDAP attribute uid.
  • first name: Maps Keycloak firstName → LDAP attribute givenName
  • last name: Maps Keycloak lastName → LDAP attribute sn
  • email: Maps Keycloak email → LDAP attribute mail

Custom User Attribute Mappers​

Mapper NameMapper TypeKeycloak AttributeLDAP Attribute
job-titleuser-attribute-ldap-mapperjobTitletitle
departmentuser-attribute-ldap-mapperdepartmentdepartmentNumber
employee-typeuser-attribute-ldap-mapperemployeeTypeemployeeType
locality-cityuser-attribute-ldap-mapperl
state-regionuser-attribute-ldap-mapperst
manager-dnuser-attribute-ldap-mappermanagermanager
info

Custom attributes mapped this way will be stored on the Keycloak User Profile under User Attributes and can be exposed to your applications via OIDC token mappers.

The manager attribute​

The default user-attribute-ldap-mapper performs a direct string copy, so it will store the raw LDAP DN string (e.g., uid=lee.wolf,ou=people,dc=shane-longman,dc=org) as the custom user attribute value.

Custom Group Attribute Mapper​

You need to configure the group-ldap-mapper if you want Keycloak to recognise group structures and assign users to them automatically. Keycloak does not read group entries or member attributes from LDAP by default — it relies on the mapper to parse your schema.

In Keycloak's Admin Console, for the serendipity-dev realm:

  1. User Federation → Open the OpenLDAP provider:

Add LDAP Provider

  1. On the Mappers tab, click the Add mapper button:

Add LDAP Provider

  1. Configure the General settings:

Add LDAP Provider

  • Name: groups-dn
  • Mapper type: group-ldap-mapper
  • LDAP Groups DN: ou=groups,dc=shane-longman,dc=org
  • Group Name LDAP Attribute: cn
  • Group Object Classes: groupOfNames
  • Preserve Group Inheritance: Off
  • Ignore Missing Groups: Off
  1. Configure the group Membership settings:

Add LDAP Provider

  • Membership LDAP Attribute: member
  • Membership Attribute Type: DN
  • Membership User LDAP Attribute: uid
  • Mode: IMPORT
  • User Groups Retrieve Strategy: LOAD_GROUPS_BY_MEMBER_ATTRIBUTE
  • Member-Of LDAP Attribute: memberOf
  • Drop non-existing groups during sync: On

Save the mapper settings, then select Sync LDAP Groups from the Action dropdown menu.

Assigning permissions using roles and groups​

Roles and groups are used to manage access to applications. Roles define specific applications permissions and typically apply to one type of user. Groups are a collection of users to which you assign one or more roles.

The Shane Longman sample organisation utilises the following roles:

TierKeycloak role nameCommon titles (the role covers all of these)Who this is in the sample
Entry-level practitioneranalystAnalyst, Associate AnalystGraduate assistants and junior staff who handle data collection, market research, financial modeling, slide deck preparation, and primary task execution.
Mid-level professionalconsultantConsultant, Senior ConsultantExperienced professionals responsible for managing specific workstreams, conducting client interviews, designing solutions, and drafting deliverables.
Day-to-day project leadermanagerManager, Engagement Manager, Project LeaderExperienced leaders who oversee day-to-day project operations, manage delivery timelines, lead consultant teams, and maintain primary client relationships.
Senior practice leadersenior-managerSenior Manager, Director, Associate PartnerSenior leaders tasked with driving multi-project delivery, leading sector or functional practice areas, and actively generating new business.
Co-owner / senior executivepartnerPartner, Principal, Managing DirectorCo-owners or senior executives of the firm focused on revenue generation, strategic client account management, firm governance, and practice development.

Creating a realm role​

Verify that you are in the correct realm e.g., the Development Realm (serendipity-dev).

To create a new role:

  1. Select Realm Roles in the side menu and then click the Create role button.
  2. Enter a Role Name, enter a Description and then click Save.

Assigning realm roles to a group​

Verify that you are in the correct realm e.g., the Development Realm (serendipity-dev).

  1. Click Groups in the left sidebar menu.
  2. Click on the name of the group you want to configure. Open the Role mapping tab.
  3. Click Assign role.
  4. Use the filter dropdown (set to Filter by realm roles) to view available realm roles.
  5. Select the checkboxes for the roles you want to grant, then click Assign.

Managing OpenID Connect and SAML Clients​

Creating an OpenID Connect client​

Verify that you are in the correct realm e.g., the Development Realm (serendipity-dev).

To create a new client, select Clients in the side menu and then click the Create client button:

Keycloak Admin Console Create Client 1

Enter a Client ID (e.g., serendipity-web-bff) and a Name (e.g., Serendipity Web BFF) and then click the Next button:

Keycloak Admin Console Create Client 2

Enable the Client authentication (i.e., OAuth 2.0 Confidential Client) slide toggle and the Standard flow (i.e., OAuth 2.0 Authorization Code Flow) checkbox and then click the Next button:

Keycloak Admin Console Create Client 3

Enter a Valid redirect URI (e.g., https://serendipity.localhost/*), a valid Web origins (e.g., *) and then click the Save button.

tip

After saving, Keycloak takes you to the client's Settings page. The Client authentication field shows On and Standard flow shows On. A Client secret is generated and visible in the Credentials tab — copy it now; Keycloak does not show it again. The client appears in the Clients list with the name you entered.

Configure the BFF​

The BFF must be configured with the client credentials the Keycloak client just generated. The OAuth2 client registration lives in backend/modules/web-bff/src/main/resources/application.yml:

security:
oauth2:
client:
registration:
keycloak:
client-id: serendipity-web-bff
client-secret: LPg1pb4vRmHLhYOrA60hiOASlMHNlnSI
scope:
- openid
- profile
- email
authorization-grant-type: authorization_code
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
provider:
keycloak:
issuer-uri: https://serendipity-identity-service.localhost/realms/serendipity-dev
authorization-uri: https://serendipity-identity-service.localhost/realms/serendipity-dev/protocol/openid-connect/auth
token-uri: https://serendipity-identity-service.localhost/realms/serendipity-dev/protocol/openid-connect/token
user-info-uri: https://serendipity-identity-service.localhost/realms/serendipity-dev/protocol/openid-connect/userinfo
jwk-set-uri: https://serendipity-identity-service.localhost/realms/serendipity-dev/protocol/openid-connect/certs
user-name-attribute: preferred_username

Each field must match what Keycloak has for the client:

Application.yml fieldWhat it must match
client-idThe Client ID set when creating the client (serendipity-web-bff)
client-secretThe Client Secret shown in the Keycloak Credentials tab after the client is created (Keycloak shows this only once — copy it immediately)
authorization-grant-type: authorization_codeStandard flow enabled (the 'Standard flow' checkbox in the client creation wizard)
redirect-uriMust be a valid redirect URI registered on the client. Spring Security's default code endpoint is /{baseUrl}/login/oauth2/code/{registrationId}, so register https://serendipity.localhost/login/oauth2/code/keycloak as a valid redirect URI in Keycloak (the wildcard https://serendipity.localhost/* covers it)
issuer-uriThe realm URL (https://serendipity-identity-service.localhost/realms/serendipity-dev)

The scopes (openid, profile, email) correspond to what the client is allowed to request; they must be valid scopes on the Keycloak realm's OIDC provider, but they are not separately configured on the client itself.

In production the client-secret should come from an environment variable or secret manager, not live in application.yml committed to source. The project's .env file is the intended place for environment-specific values during local development.

tip

When the BFF is running and the PWA navigates to https://serendipity.localhost/, the user is redirected to Keycloak's login endpoint (https://serendipity-identity-service.localhost/realms/serendipity-dev/protocol/openid-connect/auth). After entering credentials, Keycloak redirects back to https://serendipity.localhost/login/oauth2/code/keycloak with an authorization code. The BFF exchanges the code for an access token and the user is taken to the contacts page. A subsequent visit to the PWA does not require re-authentication — the JSESSIONID session cookie is still valid.

Optional: enable OIDC global logout​

If you want logging out of the PWA to also end the Keycloak session (so the user is signed out everywhere, not just from the BFF), register a Valid post logout redirect URI on the client in Keycloak:

https://serendipity.localhost/

This matches the postLogoutRedirectUri set on the BFF's OidcClientInitiatedLogoutSuccessHandler in the BFF's security configuration (SecurityConfig.java). Without it, the BFF can still log the user out locally, but the Keycloak session remains active and a subsequent login will not require re-authentication.

tip

When the user clicks Logout in the PWA, the browser calls GET https://serendipity.localhost/logout. The BFF invalidates the session and the JSESSIONID cookie, then redirects the browser to Keycloak's logout endpoint (https://serendipity-identity-service.localhost/realms/serendipity-dev/protocol/openid-connect/logout). If the post-logout redirect URI is registered on the client, Keycloak destroys the SSO session and redirects the browser back to https://serendipity.localhost/. Without the post-logout redirect URI registered, the browser is still redirected to https://serendipity.localhost/ but the Keycloak session remains active — opening the PWA again logs the user back in without a credential prompt.

Export a realm​

To export a realm, in the project's /backend directory, run

REALM_NAME=serendipity-dev docker compose run --rm serendipity-identity-service-export
info

The exported realm file is written to backend/services/identity-service/export/. Look for a file named serendipity-dev-realm.json there.

References​

Keycloak​