Keycloak:Tutorial EN
Tutorial
Keycloak
version 1.1
Created on July 22, 2025
Published online September 21, 2025.
Updates on October 19, 2025.
Language English
- French ![]()
Author
José Mans
Purpose
Reason
Standardize authentication to the company's web services including MediaWiki and Nextcloud while continuing to use the LDAP directory (AD).
This document describes using a single authentication interface to allow access to the company's services. This doc focuses only on Nextcloud and the Wiki (MediaWiki).
Single sign-on to access all services.
Approach
The idea here is to run your Keycloak service inside a container managed by Docker and have it start when you run compose up. That is why preparation is emphasized over installation in this document.
Estimated time for a complete installation and configuration: 1 hour.
Prerequisites
- Database: MySQL or MariaDB
- Docker CE
- Apache2
- SSL certificate
Optional
- OpenLDAP or Active Directory
Advice
Download "Dockerfile" and "docker-compose.yml" before continuing and change parameters/variables as they are mentioned.
Other tutorial
Official tutorial: [Getting started with Docker]
Preparation
External database
MariaDB / MySQL
Since Keycloak 20 the utf8mb4 collation is used despite the documentation warning: https://www.keycloak.org/server/db (2025 09 05).
You therefore need to create a database using this format to have long indexes.
CREATE DATABASE keycloak CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'kuser'@'%' IDENTIFIED BY 'UnMotDePassTrèsFort';
GRANT ALL PRIVILEGES ON keycloak.* TO 'kuser'@'%';
FLUSH PRIVILEGES;
KC_DB syntax
Once the database and user are created the docker-compose.yml needs to include these settings through the KC_DB variables as described below:
Example
- #KC_DB_URL_DATABASE: keycloak # No need to specify with the full form
- KC_DB_URL_PORT: 3306
- KC_DB_URL: jdbc:mariadb://172.17.0.1/keycloak?characterEncoding=UTF-8&useSSL=false
- KC_DB_USERNAME: kuser
- KC_DB_PASSWORD: "UnMotDePassTrèsFort" # Database access password
ip a show dev docker0
or
docker network inspect $(docker network ls | grep -P "^.*bridge.*bridge.local" | sed -E 's/^([a-zA-Z0-9]+)\s+(bridge)./\1/')
For information, database-related variables are documented at:
https://www.keycloak.org/server/db
Update
It is highly recommended to perform a mysqldump BEFORE any Keycloak update, as there is a risk of data loss when a Liquibase update is performed.
Otherwise, it would be necessary to customize the SQL queries using <sql> or <modifySql> to preserve these attributes... This is not covered in this tutorial.
Firewall
Before allowing database connections from the Docker container, ensure Keycloak can reach the external MySQL/MariaDB server.
Therefore check the firewall to allow connections from the docker0 bridge and the MariaDB server.
On my host the firewall does not accept foreign interfaces by default. I therefore added a few lines:
DOCKER=docker0
CHAIN=dock-me
Iptables
iptables -N ${CHAIN}
iptables -A INPUT -i $DOCKER -j ${CHAIN}
iptables -A ${CHAIN} -p TCP --dport 3306 -j ACCEPT
iptables -A ${CHAIN} -j RETURN
nft
nft add chain inet filter ${CHAIN} {}\;
nft add inet filter input iifname "${DOCKER}" counter jump ${CHAIN}
nft add rule inet filter ${CHAIN} tcp dport 3306 counter accept
nft add rule inet filter ${CHAIN} counter return
SSL Certificates
File access rights for Keycloak and use of a Let's Encrypt certificate renewed every 90 days.
The Keycloak image is intentionally minimal for security reasons. It is not appropriate to add tools inside the image to perform certificate renewals.
Therefore the mount method is chosen, because it requires less manipulation within the container. The host performs certificate renewal with certbot.
Mount-based access method
This method is adopted because the server can reload new certificates without service interruption — at worst a simple docker restart.
Re-assigning SSL certificate permissions
By default the container runs with UID=1000 and GID=0. In other words it has read/write/execute rights for any files and directories belonging to the container's root group and — when volumes are mounted — on the host as well.
Without going into details it seems preferable to assign a different GID to the container so it can access the SSL certificates.
# sur le hôte du docker, où sont enregistrés les certificats.
addgroup ssl-access
getent group ssl-access
# affiche le gid '''5020'''
chown root:'''5020''' /etc/letsencrypt/{archive,live} /etc/letsencrypt/archive/domain.tld/priv*.pem
chmod 650 /etc/letsencrypt/archive/domain.tld/priv*.pem /etc/letsencrypt/{archive,live}
## Ceci est ajouté dans la crontab qui gère le renouvellement des certificats :
## vi /usr/local/sbin/crontab_re_hash.sh afin de remodifier les droits à chaque changement par certbot...
pkcs12 method
Just for completeness: Keycloak recently supports PKCS#12 certificate format.
This method is not recommended, but if you have no other option, here is how to convert certificates:
openssl pkcs12 -export -in /etc/letsencrypt/live/domain.tld/fullchain.pem -inkey /etc/letsencrypt/live/domain.tld/privkey.pem -out server.keystore -name server -passout pass:password
Apache proxy
To avoid having multiple SSL certificates with different domains, a reverse proxy solution was chosen.
The domain "domain.tld" is used with a dedicated path; Keycloak will be reachable from the public-facing server at:
A recurring error between Keycloak and an HTTP server such as Nginx or Apache.
Using a proxy often triggers this error: error="cookie_not_found"
Using tools like "curl" or "http toolkit" helps to understand why it happens. In my case the problem was a cookie rewrite from a previous rule where "Path: /TrucMuche/" had been added to the Set-Cookie: header.
To avoid this, locate any ProxyPassReverseCookiePath rule and adapt it inside the <Location "/auth/">... block and you're done :)Here are the Apache2 directives to adapt and place in your site configuration so redirects work properly:
# --- Keycloak START ---
ProxyRequests off
ProxyPreserveHost On
ProxyPass /auth/ https://domain.tld:8086/
ProxyPassReverse https://domain.tld:8086/(.*)$ https://domain.tld/auth/$1
# En-têtes X-Forwarded essentiels
Header always set X-Forwarded-Proto "https"
Header always set X-Forwarded-Port "443"
RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Forwarded-Port "443"
RequestHeader set X-Forwarded-For %{REMOTE_ADDR}s
<Location "/auth/">
# Gestion des cookies sécurisés
ProxyPassReverseCookieDomain domain.tld domain.tld
ProxyPassReverseCookiePath / /auth/
</Location>
#CAS DECOLE: Header edit Set-Cookie "^(.*)$" "$1; Secure; SameSite=None"
# --- Keycloak END ---
Installation
The Keycloak version used is the Docker-optimized image. Therefore Docker must be installed before proceeding (installation tutorial)
Remember to download "Dockerfile" and "docker-compose.yml" so you can edit them while reading the following chapters.
Building the image
The provided Dockerfile contains the essential directives to build the image required to run the final container.
For example:
FROM quay.io/keycloak/keycloak:26.3.4 AS builder
# Enable health and metrics support
# NOTE: not supported inside this build: hence the --.* options in RUN are commented out
#ENV KC_HEALTH_ENABLED=true
#ENV KC_METRICS_ENABLED=true
WORKDIR /opt/keycloak
# for demonstration purposes only, please make sure to use proper certificates in production instead
#RUN keytool -genkeypair -storepass password -storetype PKCS12 -keyalg RSA -keysize 2048 -dname "CN=domain.tld" -alias server -ext "SAN:c=DNS:localhost,IP:127.0.0.1" -keystore conf/server.keystore
#COPY /opt/keycloak/ /opt/keycloak/
FROM quay.io/keycloak/keycloak:26.3.4
COPY --from=builder /opt/keycloak/ /opt/keycloak/
RUN /opt/keycloak/bin/kc.sh build --db=mariadb --health-enabled=true --metrics-enabled=true --features="user-event-metrics,persistent-user-sessions"
ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]
e.g., for MySQL use --db=mysql
See supported drivers: https://www.keycloak.org/server/db
Once the file is ready, build the image with:
docker build -t keycloak . # Dockerfile must be in the current directory...
Pre-image options
Options usable only during the image build. They determine the Keycloak server behavior.
RUN /opt/keycloak/bin/kc.sh build ... --features="Option1,Option2"
<!> no spaces allowed between features — use a comma "," only
Creating the final container
The docker-compose.yml contains the essential settings for the Keycloak server.
Of course adapt it to your company requirements. Here is its content:
services:
keycloak:
container_name: keycloak2
user: "keycloak:5020" # UID and GID from /etc/passwd container / image
image: keycloak:26.3.4
#image: quay.io/keycloak/keycloak:26.3.2
ports:
#- ":8084:8080"
#- ":8087:9000"
- ":8086:8443"
volumes:
- /etc/letsencrypt/:/etc/letsencrypt/:ro
environment:
# BASE DBASE
#KC_DB: mariadb # Already declared in the image!
KC_DB_URL: jdbc:mariadb://172.17.0.1/keycloak?characterEncoding=UTF-8&useSSL=false
KC_DB_URL_PORT: 3306
KC_DB_USERNAME: kuser
KC_DB_PASSWORD: "UnMotDePassTrèsFort" # The database password
# Paramètres d'administration de Keycloak
#Déclassé: KEYCLOAK_ADMIN: admin
#Déclassé: KEYCLOAK_ADMIN_PASSWORD: admin
KC_BOOTSTRAP_ADMIN_USERNAME: admin
KC_BOOTSTRAP_ADMIN_PASSWORD: *********
# Hostname and Proxy Configuration
# Base URL where Keycloak can be accessed from a local network or the internet
KC_HOSTNAME: https://domain.tld/auth/ # ou l'option: command: ... --hostname=https://domain.tld/auth/
# Health Settings and Metrics
#KC_HEALTH_ENABLED: "true"
#KC_METRICS_ENABLED: "true"
# LOGs
KC_LOG: console
KC_LOG_LEVEL: info
KC_LOG_COLOR: true
# Too verbose :
#KC_LOG_CONSOLE_LEVEL: all
#Valid SSL certificates and hourly reloading
KC_HTTPS_CERTIFICATE_FILE: /etc/letsencrypt/live/domain.tld/fullchain.pem
KC_HTTPS_CERTIFICATE_KEY_FILE: /etc/letsencrypt/live/domain.tld/privkey.pem
KC_HTTPS_CERTIFICATES_RELOAD_PERIOD: 1h
# Version for tests
#command: start-dev --hostname-strict=false --proxy-headers forwarded --verbose
command: start --verbose --http-enabled=true --proxy-trusted-addresses='''IP_SERVEUR'''/32,127.0.0.0/8 --proxy-headers=xforwarded --optimized
Adjust the variables:
- KC_HOSTNAME (v2)
- Contains the hostname Keycloak should use.
- It can also contain the external access URL.
- In my case I want Keycloak accessible from the internet using the main domain's valid SSL certificate. That implies using a proxy so external requests to https://domain.tld/auth transparently reach Keycloak and, MOST IMPORTANT, responses are adapted to the expected paths in Keycloak's HTML output...
- KC_BOOTSTRAP_ADMIN_PASSWORD
- Password for the admin console
- KC_HTTPS_CERTIFICATE_FILE
- Public certificate
- KC_HTTPS_CERTIFICATE_KEY_FILE
- Private key
- --proxy-trusted-addresses
- Replace with the IPs allowed to access Keycloak directly, including IP_SERVEUR.
First launch
Once docker-compose.yml is modified, create and start it with:
docker compose up
The container should then be created and start without issue.
During the process, there will be warnings regarding MariaDB and what it doesn't support, followed by a long wait (2 minutes...).
Here is an excerpt of a message confirming that the server started correctly:
keycloak2 | 2025-09-24 13:22:15,825 INFO [io.quarkus] (main) Profile prod activated.
keycloak2 | 2025-09-24 13:22:15,825 INFO [io.quarkus] (main) Installed features: [agroal, cdi, hibernate-orm, jdbc-mariadb, keycloak, micrometer, narayana-jta, opentelemetry, reactive-routes, rest, rest-jackson, smallrye-context-propagation, smallrye-health, vertx]
Admin console access
After running docker compose up, the admin console is accessible at: Admin console -> https://domain.tld/auth/].
The bootstrap super-admin credentials are specified in the docker-compose.yml via variables:
- KC_BOOTSTRAP_ADMIN_USERNAME: admin
- KC_BOOTSTRAP_ADMIN_PASSWORD: ********* # Replace the stars with what you set ;)
Before anything
The first login to Keycloak places the admin user in the "master" realm.
It is recommended to create a new administrator account and disable the default 'admin' account.
Security
Create a new admin account for the 'master' realm and for any new realms…
- Manage realms
- 'master' (Normally already selected)
- Users
- Add user and fill fields
- Email verified : On
- Username : kadmin
- Email : ValidEmail!@domain.tld
- Create
- Credentials
- Set password and enter the password
- Temporary : Off
- Save
- Save password
- Role mapping
- Assign role
- Realm roles
- admin
- create-realm
- Assign
- Credentials
Disable default 'admin' account
- Manage realms
- 'master'
- Users
- 'admin'
- click 'Enable' so the button becomes 'Disable'
- disable
Creation
From this chapter onward, create the realm dedicated to the organization, its groups, users and clients (services). Do not forget to use the LDAP directory to synchronize existing groups and users to avoid re-creating employee accounts...
Realm
Called a "realm" and represents a grouping of services.
Think of a realm as an organization that manages authentication for its services (clients) and its users.
Each realm is unique and isolated. One organization manages its own rules without interfering with another realm.
Create Gyptis as the organization's realm, and modify some settings:
- Manage realms
- Create realm
- Realm name : Gyptis
- Enable : On
- Create
- Create realm
After creation, the "Manage" and "Configure" sections will be dedicated to the newly created realm.
The badge (top-left)
indicates the selected realm.
Contrast with
which shows the server's 'master' realm selected.
Groups
Group management is similar to any directory (AD, LDAP, ...). Creating a group is straightforward.
Create an administrator group for Gyptis:
- Manage realms
- 'master'
- Groups
- Create group
- Name: Gadmins
- Description : Group for Gyptis administrators
- Save
- Create group
Assign admin rights to Gadmins
- Groups
- Gadmins
- Role mapping
- Assign role
- Client roles
- Search : Type Gyptis-realm and confirm
- to show only roles related to the realm
- Select all roles named Gyptis-*
- Assign
- Search : Type Gyptis-realm and confirm
User account
An administrator account and a test user should be created for the realm Gyptis.
- The admin account will serve the organization to manage the realm.
- ATTENTION: this admin must be created from the 'master' realm
- The test user is for doing the first checks within Gyptis and should be created in its own realm.
- it can be disabled afterwards...
Admin access for Gyptis
- Manage realms
- 'master'
- Users
- Add user
- Email verified : On
- Username : admin-gyptis
- Email : a_real_email@domain.tld
- First Name :
- Last name :
- Join Groups
- Select Gadmins
- Join *** Create
- Credentials (new tab)
- Set password
- Enter the password twice...
- Temporary : Off
- Save
- Save password
- Add user
- Assign role
- Client roles
- Search : Gyptis
- Tick: all roles named Gyptis-* in the ClientID column
- Assign
Once finished, switch to the realm (realm) Gyptis
Assuming you follow this advice I will leave it to you to explore what changed...
Test user
Only for initial testing...
- Manage realms
- Gyptis
- Users (for realm Gyptis!)
- Create new user (ou Add user)
- Email verified : On
- Username : Guser
- Email : another_real_email@domain.tld
- First Name :
- Last name :
- Create
- Create new user (ou Add user)
- Credentials (nouvel onglet)
- Set password
- Saisir le mot de passe 2 fois...
- Temporay : Off
- Save
- Save password
- Users (for realm Gyptis!)
Direct access
Once the realm is created and configured users can authenticate via the Keycloak UI at: https://domain.tld/auth/realms/Gyptis/account.
This is the case for Guser but not for admin-Gyptis
Client account
An application client represents the rules and information of a service in the realm.
To allow Cloud and the Wiki to use Keycloak, create a 'client' for each service in Manage->Clients: nextcloud and mediawiki.
Creating a client
Create both clients with minimal information; important details will be configured later.
Manage -> Clients -> Create client
- Client ID: nextcloud
- Name : (optional)
- Next
- Next (arrived at Login settings)
- Save
Do the same for mediawiki.
Client settings
Nextcloud
Cloud is managed by Nextcloud (also valid for ownCloud) using the 'OpenID Connect user backend' app
- Clients -> nextcloud
- General settings
- Client ID : nextcloud
- Name : Nextcloud Client (optional)
- Access settings
- Root URL : leave blank
- Home URL : https://domain.tld/NextCloud/index.php
- Valid redirection URIs : https://domain.tld/NextCloud/*
- Valid post logout redirects URIs: https://domain.tld/NextCloud/*
- Web Origin : +
- Admin URL : https://domain.tld/Nextcloud/index.php/settings/admin
- Capability config
- Client authentication : On
- Authorization : On
- Standard flow
- Direct access grants
- Service account roles
- Standard Token Exchange
- OAuth2.0 Device Authorization grants
- Login settings
- Login theme : optional
- Consent required : Off
- Display client on screen : Off
- Logout settings
- Front channel logout : Off
- Backchannel logout URL : https://domain.tld/Nextcloud/index.php/apps/user_oidc/backchannel-logout/Keycloak
- 'Keycloak' represents the provider name in the Nextcloud/user_oidc configuration...
- Backchannel logout session required : On
- Backchannel logout revoke offline sessions : On
- Backchannel logout URL : https://domain.tld/Nextcloud/index.php/apps/user_oidc/backchannel-logout/Keycloak
- Front channel logout : Off
- Save
- Keys (tab)
- Use JWKS URL configs: On
- JWKS URL : https://domain.tld/NextCloud/index.php/apps/oidc/jwks
- Save
- Client scopes
- address
- firstName
- organization
- phone
- profile
- roles
- service-account
- web-origins : Default
- General settings
MediaWiki
The wiki is managed by MediaWiki with the OpenIDConnect extension.
- Clients -> mediawiki
- General settings
- Client ID : mediawiki
- Name : MediaWiki client (optional)
- Root URL : leave blank
- Valid redirection URIs : https://domain.tld/MediaWiki/*
- Valid post logout redirects URIs: https://domain.tld/MediaWiki/index.php/Special:UserLogout
- Web Origin : https://domain.tld/MediaWiki/
- Admin URL : https://domain.tld/MediaWiki/Special:UserLogin
- Login settings
- Capability config
- Client authentication : On
- Authorization : On
- Standard flow
- Logout settings
- Front channel logout : Off
- Backchannel logout URL : https://domain.tld/MediaWiki/rest.php/pluggableauth/v1/logout
- Backchannel logout session required : On
- Backchannel logout revoke offline sessions : Off
- Save
- Client scopes
- same as for client 'nextcloud'
- General settings
Once both clients are created, MediaWiki and Nextcloud can use Keycloak to authenticate company users.
However, as stated in the Purpose we want users to authenticate once and gain access to both services, and logging out from one service should log them out of the others...
The next section explains how to achieve that.
Application authorization
This is where Keycloak is configured so that Nextcloud, MediaWiki and Keycloak itself can be used with a single authentication.
Logging into one of these services will grant access to the others without repeating the login process.
Clients side
Create a client-level role for each client.
For MediaWiki and Nextcloud the roles will be named: access-mediawiki and access-nextcloud, i.e. logically: access-[mediawiki|nextcloud]. Assign the role accordingly.
- Client->[nextcloud | mediawiki]
- Roles->Create Role : access-[nextcloud | mediawiki]
- then Client Scopes->[nextcloud | mediawiki]-dedicated
- Add Mapper, From predefined mapper
- choose "client roles"
Precisely:
| Nextcloud Role | MediaWiki Role |
|---|---|
|
|
Groups part
Authorize a group to use single sign-on.
- Groups
- Create group
- Name : Gyptis_Group
- Create
- Create group
Application attribution for the group
- Groups
- Gyptis_Group (or any other)
- Role mapping
- Assign role
- client roles and select:
- access-nextcloud
- access-mediawiki
- Assign
- Role mapping
For an existing user
- Users
- Gyptis_user
- Role mapping
- Assign role
- client role
- access-nextcloud
- access-mediawiki
- Role mapping
All necessary steps have been created. The services can use single sign-on with the user 'guser', but first they need to be configured to connect to the Keycloak service.
Connect services to Keycloak
With clients created, configure MediaWiki and Nextcloud to use Keycloak for authentication.
Installation is not complicated and doesn't need yet another tutorial, so here are links to the official guides for the OpenID Connect modules:
MediaWiki : https://www.mediawiki.org/wiki/Extension:OpenID_Connect/fr
Nextcloud : https://github.com/nextcloud/user_oidc
Retrieve client_secret
When creating each client a unique secret is generated. It is intended for the client of each service.
Client->[Nextcloud & mediawiki ] -> Credentials -> Copy 'Client secret' and place it below.
Client-side configuration
MediaWiki
Shell method
Edit LocalSettings.php
wfLoadExtension( 'OpenIDConnect' );
# uri de vérification, si json apparait OK : https://domain.tld/auth/realms/Gyptis/.well-known/openid-configuration
$wgPluggableAuth_Config["Gyptis"] = [
'plugin' => 'OpenIDConnect',
'data' => [
'providerURL' => 'https://domain.tld/auth/realms/Gyptis',
'clientId' => 'mediawiki',
'clientSecret' => '*********',
'codeChallengeMethod' => 'S256',
'scope' => [ 'openid', 'profile', 'email', 'firstName', 'address', 'organization', 'phone', 'profile', 'roles', 'web-origins' ],
],
];
$wgOpenIDConnect_SingleLogout = true; # déconnecter l'user aussi sur KeyC
# Change la redirection, idéale pour aller au Portail quand déco.
#$wgHooks['UserLogoutComplete'][] = function ( $user, &$inject_html, $old_name ) {
# // Redirige vers l'URL préférée après déconnexion.
# header( "Location: https://Portail-Entreprise/" );
# exit;
#};
# Variables pour forcer l'identification et donc empêcher la lecture des articles par les anonymes.
$wgPluggableAuth_EnableAutoLogin = true; # False permet la navigation, 'true' oblige à s'identifier. Mais ne fonctionne pas sans celle ci-dessous!
$wgGroupPermissions['*']['read'] = true; # Oblige l'identification pour lire les articles.
If you want Keycloak to become the only authentication method, disable all other auth modules, LDAP, and MediaWiki's local auth...
$wgPluggableAuth_EnableLocalLogin = false;
$wgPluggableAuth_Config = array(); # <!> This implies count( $wgPluggableAuth_Config[] ) contains only one element!
$wgPluggableAuth_Config["..."] put the OpenID config here...
...
cd /var/www/MediaWiki
sudo -u www-data -g www-data php maintenance/run.php --conf /var/www/MediaWiki/LocalSettings.php update --quick --force
Adapt according to your server configuration!
systemctl restart "php*"
systemctl restart redis-server memcached
redis-cli FLUSHDB
redis-cli -n DB_NUMBER FLUSHDB
redis-cli -n DB_NUMBER FLUSHDB ASYNC
redis-cli FLUSHALL
redis-cli FLUSHALL ASYNC
(echo "flush_all" )
Nextcloud
Add the OpenID Connect extension (user_oidc)
- Installing OpenID Connect
- Log into the Nextcloud admin console
- Open the Apps menu and go to Security.
- Search for "OpenID Connect user backend", download it and enable it.
- Return to the admin console, then open the new "OpenID Connect" menu
- Registered Providers
Shell method
Use the occ command:
sudo -u www-data -g www-data php occ user_oidc:provider Keycloak --clientid="nextcloud" \
--clientsecret="***********" \
--discoveryuri="https://domain.tld/auth/realms/Gyptis/.well-known/openid-configuration"
Complete:
sudo -u www-data -g www-data php occ user_oidc:provider Keycloak \
--clientid="nextcloud" \
--clientsecret="***********" \
--discoveryuri="https://domain.tld/auth/realms/Gyptis/.well-known/openid-configuration" \
--scope="openid email address organization phone profile roles web-origins organization groupOfNames-scope" \
--mapping-display-name="displayName" \
--mapping-email="email" \
--mapping-uid="entryUUID | username | email" \
--mapping-groups="groupOfNames" \
--mapping-language="preferredLanguage" \
--mapping-website="labeledURI" \
--mapping-avatar="jpegPhoto" \
--mapping-phone="phone" \
--unique-uid=false \
--check-bearer=true \
--send-id-token-hint=true \
--bearer-provisioning=true \
--group-provisioning=true \
--group-whitelist-regex="^(cloud|dedie|famille)\$" \
--resolve-nested-claims=false \
--group-restrict-login-to-whitelist=true # false for test...
Web console method
Configuration
- Client configuration
- Identifier : Keycloak
- Client ID: nextcloud
- Client secret : to be retrieved
- Discovery endpoint : https://domain.tld/auth/realms/Gyptis/.well-known/openid-configuration
- Custom end session :
- Scope : openid email address organization phone profile roles web-origins groupOfNames-scope entryUUID-scope
- The two bold items are essential if you plan to use LDAP (see below). Otherwise, do not add them.
- Their creation is detailed in Fields returned to clients
- The two bold items are essential if you plan to use LDAP (see below). Otherwise, do not add them.
- Extra claims :
- Attribute mapping
- Enable nested and fallback ... :
- User ID mapping : entryUUID | sub
- or the attribute adapted to your config, e.g., uid
- entryUUID => user from LDAP
- sub => user from Keycloak
- or the attribute adapted to your config, e.g., uid
- Before using Keycloak, if you used Nextcloud/ownCloud + LDAP, it's likely users' folder names are based on the entryUUID of each owner.
- If you want more human-friendly folder names, replace 'sub' with something else like 'email'. Obviously if a user leaves the company and another user with the same name takes their place, they will inherit the old email, which requires removing the departed employee from Nextcloud...
- Quota mapping :
- Groups mapping : groups
- Extra attributes mapping
- Email mapping : email
- Language mapping : preferredLanguage
- Phone mapping : phone
- Website mapping : labeledURI
- Avatar mapping : jpegPhoto
- Authentication and Access Control Settings
- Use unique user ID :
- Use provider identifier as prefix for IDs :
- Use group provisioning :
- Group whitelist regex : ^(nextcloud|mediawiki|your company groups... AND present in Keycloak)$ Detailed explanation...
- Once this chapter is complete, consult Returned field
- Group whitelist regex : ^(nextcloud|mediawiki|your company groups... AND present in Keycloak)$ Detailed explanation...
- Restrict login for users that are not in any whitelist group :
- Allows access only to users in the whitelist groups (Group whitelist)
- Check Bearer token on API and WebDAV .... :
- Auto provision user when accessing API and WebDAV :
- Send ID token hint on logout :
- Submit or Update provider
LDAP configuration
Keycloak can manage users and groups: add, modify, delete, ...
However, companies often already have an LDAP directory (AD or OpenLDAP) and Keycloak allows using it to avoid conversion or migration...
Below are ObjectClasses and attributes related to an OpenLDAP-managed directory on Linux; the principle is the same for Active Directory...
If you use FusionDirectory the ObjectClasses / Attributes will be the same.
base=$(ldapsearch -LLL -Q -Y EXTERNAL -H "ldapi:///" "(&(objectClass=organization)(objectClass=dcObject))" "dn:"| sed -re 's/^dn: *(.*)/\1/')
To know objectClasses:
Use to find the user's ObjectClasses:
ldapsearch -LLL -Q -Y EXTERNAL -H "ldapi:///" "uid=$(whoami)" "objectclass"
Use to find a group's ObjectClasses:
ldapsearch -LLL -Q -Y EXTERNAL -H "ldapi:///" -b "$(ldapsearch -LLL -Q -Y EXTERNAL -H "ldapi:///" -b "ou=groups,${base}" "(&(objectClass=posixGroup)(memberUid=$(whoami)))" cn | grep -E "^dn: cn=.groups." | sed -re 's/^dn: (.)$/\1/g' | head -1)" objectClass
or
ldapsearch -LLL -Q -Y EXTERNAL -H "ldapi:///" "cn=$(whoami)" objectClass
if the group "$(whoami)" exists!
If the two ldapsearch commands above fail, use:
ldapsearch -LLL -Q -Y EXTERNAL -H "ldapi:///" | less
And search for a user entry and then a group containing that user.
Otherwise contact our friend AI ;)
User federation
Two choices to add a Kerberos and/or LDAP provider
Add LDAP providers
- Add new provider->LDAP (Settings section)
- UI display name : Ldap
- Vendor : Other
Connection and authentication settings
- Connection URL : ldap://ldap.domain.tld/
- Enable StartTLS : Off
- Use Truststore SPI : Always
- Connection pooling : On
- Connection timeout :
- Bind type : simple
- Bind DN : cn=ro,dc=gyptis
- Bind credentials : password for the binding account
LDAP searching and updating
- Edit mode : READ ONLY
- Users DN : ou=People,dc=gyptis
- Relative user creation DN :
- Username LDAP attribute : cn
- RDN LDAP attribute : uid
- UUID LDAP attribute : entryUUID
- User object classes : posixAccount
- User LDAP filter : (|(objectclass=gosaMailAccount)(objectclass=inetOrgPerson)(objectclass=organizationalPerson)(objectclass=posixAccount)(objectclass=sambaSamAccount)(objectclass=top))
- Search scope : One Level * Read timeout :
- Pagination : Off
- Referral :
Synchronizations settings
- Import users : Off (On in production)
- Sync Registrations : On
- Batch size : 124000
- Periodic full sync : Off
- Periodic changed users sync : Off
- Remove invalid users during searches : Off
Kerberos integration
- Allow Kerberos authentication : Off
- Use Kerberos for password authentication : Off
Cache settings
- Cache policy : DEFAULT
Advanced settings
- Enable the LDAPv3 password modify extended operation : Off
- Validate password policy : Off
- Trust Email : On
- Connection trace : Off
- Save
Import LDAP / AD groups into Keycloak
Mapper
Mappers->Add mapper
- User federation
- enter the configuration created above, by default its name is 'ldap'
- Mappers
- Add mapper
- Name : groupOfNames
- Mapper type : group-ldap-mapper
- LDAP Groups DN : ou=groups,dc=gyptis
- Relative creation DN :
- Group Name LDAP Attribute : cn
- Group Object Classes : groupOfNames,posixGroup,gosaGroupOfNames
- Preserve Group Inheritance : On
- Ignore Missing Groups : Off
- Membership LDAP Attribute : member (or memberUid with AD)
- Membership Attribute Type : DN
- Membership User LDAP Attribute : uid
- LDAP Filter : (&(|(objectclass=groupOfNames)(objectclass=posixGroup)(objectclass=gosaGroupOfNames)))
- Mode : READ_ONLY
- User Groups Retrieve Strategy : LOAD_GROUPS_BY_MEMBER_ATTRIBUTE
- Member-Of LDAP Attribute :
memberOf - Mapped Group Attributes :
- Drop non-existing groups during sync : Off
- Groups Path : /
- Save
Use: Sync LDAP groups to Keycloak, from the Action button in the upper right corner.
- If group retrieval is performed, confirmation should occur after making the selection.
Fields returned to clients
A client may need specific attributes to make the new Keycloak-based identity consistent with its own databases.
You must therefore make certain attributes available to clients by defining client scopes and mappers. These attributes were collected from LDAP during provider configuration and are held in Keycloak's memory.
However, this is useless if we don't make their names and contents available to the clients that need them. Here we decide which client will have access to the content of these values.
groupOfNames and entryUUID
Most services need to know a user's groups and also their unique identifier that even a namesake would not share (often the uid).
The groupOfNames and entryUUID attributes are necessary for Nextcloud and MediaWiki; their roles and mappers should be defined as described below.
This is important for Nextcloud/ownCloud which use openLDAP for user identity. The entryUUID attribute is used to create a user's data directory. However Keycloak does not know this attribute by default and if an existing LDAP user who used the cloud before Keycloak logs in they may not see their data and think it was lost.
To prevent this we must create a scope and mapper so Nextcloud can obtain the user's entryUUID attribute, and also configure its "user_oidc" module to request this attribute.
| entryUUID-scope | groupOfNames-scope |
|---|---|
|
|
Provider / User federation side
In Nextcloud/ownCloud when a user is imported from LDAP, entryUUID is used.
You must configure the LDAP provider to retrieve this attribute.
- User federation
- Ldap, so an existing provider ;)
- Mappers
- Add mapper
- Name : entryUUID
- Mapper type : user-attribute-ldap-mapper
- User Model Attribute : entryUUID
- LDAP attribute : entryUUID
- Read only : On
- everything else Off
- 'Save
On the Groups Side
Remember to assign authorized applications to the LDAP base groups that were imported from User federation.
Here is the shortcut:
- Groups->(select LDAP group for cloud member...)->Role mapping->Assign role->client roles->[entryUUID-scope | groupOfName-scope ]->Assign
However, you will need to perform the additions to groups imported from the LDAP base. So something other than 'Group_Gyptis'. In our company, users with full rights to use the Nextcloud application are in the group with the same name, i.e., 'nextcloud'. So just follow the procedure above Application attribution for the group, and configure your organization's groups.
On the Client Scopes Side
- Clients
- Nextcloud
- Client scopes
- Add client scope
- the two new 'client scopes' created above should appear in the list
- Select groupOfName-scope and entryUUID-scope
- Add
- Default
- Add client scope
Do the same for mediawiki, except for entryUUID which it won't need.
Nextcloud side
Instruct Nextcloud / user_oidc to request the desired attribute:
- Edit the 'keycloak' provider.
- Under "User ID mapping"
- Add "entryUUID" (default is 'sub'!)
- You can combine as described in Attribute mapping when two user stores are used.
Prevent user edits of certain attributes
By default users can edit some profile attributes like email, firstName, lastName.
On a public forum that may be fine, but in a corporate environment it's more sensitive.
To prevent users from changing [ email | firstName | lastName ]:
- Realm settings
- User profile
- Edit attributes one by one: email & firstName & lastName
- Uncheck "Who can edit?" for the User role
Congrulation !
Once all the steps above have been applied, single sign-on should be operational.
Every login or logout to one of the services will apply to the others.
For any questions, you can contact me via the web form, mentioning the Keycloak subject to avoid being filtered by the anti-spam ;)
Annex
Login events log
After creating the new realm we can adjust logging for authentication activity across that realm.
First, keep all connection events logged for 1 hour:
- Realm settings
- Events (tab)
- Event listeners (sub-tab under 'Events')
- add email
- User events settings
- save event: On
- 1 hours expiration
- Event listeners (sub-tab under 'Events')
- Events (tab)
this keeps connection events for only 1 hour
- Admin events settings
- save event: On
- 1 hours expiration
- Save
Save the three modified tabs
List of command options
List of Keycloak configuration options when creating the container
Mappings
Concordance between startup options, keycloak.conf and environment variables.
LDAP Group whitelist regex
Adapting examples from the user_oidc interface (cf: Group whitelist regex) with the "Restrict login" option yielded inconsistent results.
Indeed, a user was allowed to open Nextcloud even though they were not in the "Nextcloud" and "mediawiki" groups but in another group with a slightly different name, here "cloud".
This is explained by the PHP method (getSyncGroupsOfToken) that handles comparison using "preg_match" (PCRE). In the example, the old group "cloud" is found inside the string :
- "Group whitelist regex": ["Nextcloud","mediawiki"]
To avoid this problem we use the regex form accepted by PCRE and therefore it was useful to inform the reader.
FAQ
keycloak3 | 2025-09-24 13:37:17,883 WARN [org.keycloak.events] (executor-thread-13) type="REFRESH_TOKEN_ERROR", realmId="******", realmName="master", clientId="security-admin-console", userId="null", ipAddress="*****", error="invalid_token", reason="Invalid refresh token", grant_type="refresh_token", client_auth_method="client-secret"
If you have no other administrative access, it is impossible to change the admin password because Keycloak requires a valid authentication for any administrative change, whether through the CLI (kc.sh) or the web console.
You must therefore edit the SQL database directly: stop the server, modify the database, then restart Keycloak.
| Solution 1: No admin access | Solution 2: With admin access |
|---|---|
BEFORE ANYTHING ELSE, stop the server before modifying the password using the SQL method.
# 'keyc-tools.py' automatically retrieves SQL credentials from docker-compose.yml:
python3 keyc-tools.py realm=master user=kadmin password
Without docker-compose.yml, use the full syntax: # <!> If the account is disabled, add the "activate" option
python3 keyc-tools.py dbuser=DBUser dbname=DBName dbpassword=DBPass dbhost=localhost dbport=3306 dbtype=mariadb realm=master user=kadmin password
|
From inside the Docker container: docker exec -it ID bash
alias kcadm=/opt/keycloak/bin/kcadm.sh
# Authenticate first
kcadm config credentials --server https://domain.tld/auth/ --realm master --user admin
# Retrieve the user ID
kcadm get users -r master --offset 0 --limit 100 --fields 'id,username'
# Change the password
# <!> Replace ID_RECOVERED with the actual user ID...
kcadm update users/ID_RECOVERED/reset-password -r master -s type=password -s value="New password" -s temporary=true -n
|
| Solution 3: Using hc.sh (.bat) | |
| Personally, I am not a fan of the method described in the official manual, especially with the KC_* variable configuration method. The slightest error can block the container.
That’s why I recommend Solution #1 — it only takes about 2 minutes to reset the password! If you still insist, refer to the official tutorial. | |
docker exec -it ID bash
/opt/keycloak/bin/kcadm.sh config credentials --server https://domain.tld/auth/ --realm master --user kadmin
- Incorrect path scope in the "httpd" configuration (during login)
- Add a Location block in Apache (or Nginx) as described in Apache Proxy.
- Using "kadmin" and "kuser" within the same browser
- Close other active windows/tabs where the Keycloak UI is open. If you have https://.../auth/admin/realm/master and https://.../auth/realms/Gyptis open simultaneously, close the unused ones — it can cause cookie conflicts.
- If neither of the first two solutions work:
- Delete all KEYCLOAK cookies using F12 (Chrome) or Inspect (Edge), go to the Application tab → Cookies...
Terminology
- Always display in UI
- By default, clients that are allowed to be used are only displayed if they have been used recently. By enabling the option, they are always visible in the user's "Applications" menu.
- Root Url
- Base address for the client; if other fields use relative paths they will be completed using this Root URL.
- Home Url
- Entry point of the client site — the main page.
- Valid Redirect URIs
- A list of URIs that define the allowed scope of URLs; they are compared to the beginning of each redirect URL used during authentication.
- Example: If you put a single URI in this field like "https://my.keycloak.tld/path/*" and one of your redirects points to "https://other.domain.com/" Keycloak will reject it. This reduces phishing risk.
- The following error will be shown in that case: Invalid parameter: redirect_uri
- A list of URIs that define the allowed scope of URLs; they are compared to the beginning of each redirect URL used during authentication.
- Valid post logout redirect URIs
- List of URIs allowed to be used by Keycloak for logout — same logic as "Valid Redirect URIs".
- Web origins
- List of domains for CORS and "silent refresh" via JavaScript. Appears when Standard flow is selected.
- Admin URL
- URL to send admin notifications, forced logout, token revocation, ... Destination must support the backend endpoints or may be left empty.
- Backchannel logout URL
- URL used by Keycloak to terminate a user's session when they log out of another client.
- Typically looks like: https://domain.tld/auth/realms/Gyptis/protocol/openid-connect/auth/device
- For MediaWiki: https://domain.tld/rest.php/pluggableauth/v1/logout
- Backchannel logout session required
- Forces session information to be included in the back-channel procedure
- Backchannel logout revoke offline sessions
- Forces revocation of persistent sessions (e.g., "remember me")
- web-origins
- Indicates the URL to return the user to after logout.
LDAP
- RDN LDAP
- Relative Distinguished Name, often uid. A short unique identifier...
Translated from French to English with assistance from ChatGPT (GPT-5 Thinking mini, OpenAI). Author reviewed and adapted the translation.
Translated from French to English by DeepSeek AI (v3, DeepSeek Company).
