Keycloak:Tutorial EN
Tutorial
Keycloak
version 1.1
Created on July 22, 2025
Published online September 21, 2025.
Updates on April 16, 2026.
Language English
- French ![]()
Author
José Mans
Purpose[modifier | modifier le wikicode]
Reason[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
- Database: MySQL or MariaDB
- Docker CE
- Apache2
- SSL certificate
Optional[modifier | modifier le wikicode]
- OpenLDAP or Active Directory
Advice[modifier | modifier le wikicode]
Download "Dockerfile" and "docker-compose.yml" before continuing and change parameters/variables as they are mentioned.
Other tutorial[modifier | modifier le wikicode]
Official tutorial: [Getting started with Docker]
Preparation[modifier | modifier le wikicode]
External database[modifier | modifier le wikicode]
MariaDB / MySQL[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
- #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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
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:[modifier | modifier le wikicode]
- 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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
- Manage realms
- 'master'
- Users
- 'admin'
- click 'Enable' so the button becomes 'Disable'
- disable
Creation[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
- 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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
Nextcloud[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
- 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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
MediaWiki[modifier | modifier le wikicode]
Shell method[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
Configuration[modifier | modifier le wikicode]
- 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[modifier | modifier le wikicode]
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, see the equivalence table...
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[modifier | modifier le wikicode]
Two choices to add a Kerberos and/or LDAP provider
Add LDAP providers[modifier | modifier le wikicode]
- Add new provider->LDAP (Settings section)
- UI display name : Ldap
- Vendor : Other
Connection and authentication settings[modifier | modifier le wikicode]
- 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[modifier | modifier le wikicode]
- 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[modifier | modifier le wikicode]
- 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[modifier | modifier le wikicode]
- Cache policy : DEFAULT
Advanced settings[modifier | modifier le wikicode]
- 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[modifier | modifier le wikicode]
Mapper[modifier | modifier le wikicode]
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
- DN plus robuste que uuid ou sAMAccountNameavec, sur AD, s'il ont veut rester standard remplacer DN par "sAMAccountName"
- Membership User LDAP Attribute : dn
- 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.
Equivalence table between OpenLDAP and Active Directory[modifier | modifier le wikicode]
| OpenLDAP | Active Directory | ||
|---|---|---|---|
| Users DN | ou=people,dc=gyptis,dc=org | cn=Users,dc=gyptis | |
| Relative user creation DN | dn | (not used / handled automatically by Keycloak) | |
| Username LDAP attribute | uid | sAMAccountName | |
| RDN LDAP attribute | uid | sAMAccountName | |
| UUID LDAP attribute | entryUUID | objectGUID | |
| User object classes | posixAccount | user | |
| User LDAP filter | (objectclass=gosaMailAccount)(objectclass=inetOrgPerson)(objectclass=organizationalPerson)(objectclass=posixAccount)(objectclass=sambaSamAccount)(objectclass=top)) | (objectClass=user) | |
| Groups | |||
| LDAP Groups DN | ou=groups,dc=gyptis | ou=Users,dc=gyptis | |
| Group Name LDAP Attribute | cn | cn | |
| Group Object Classes | groupOfNames | group | |
| Membership LDAP Attribute | member | member | |
| Membership Attribute Type | dn | dn | |
| Membership User LDAP Attribute | dn | sAMAccountName | |
| LDAP Filter | (&(|(objectclass=groupOfNames)(objectclass=posixGroup)(objectclass=gosaGroupOfNames))) | (objectClass=group) | |
| Member-Of LDAP Attribute | memberOf | memberOf | |
Of course, you will need to adapt the filter list to match your LDAP directory (AD or OpenLDAP), for example: objectclass=gosaGroupOfNames or posixGroup, which are specific to our directory.
In most environments, using groupOfNames alone is sufficient and recommended for compatibility with Keycloak.
Keycloak relies heavily on stable and unique identifiers. Make sure to correctly configure the UUID LDAP attribute (entryUUID for OpenLDAP, objectGUID for Active Directory), otherwise user synchronization issues may occur.
Fields returned to clients[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
- 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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
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 ![modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
Login events log[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
List of Keycloak configuration options when creating the container
Mappings[modifier | modifier le wikicode]
Concordance between startup options, keycloak.conf and environment variables.
LDAP Group whitelist regex[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
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[modifier | modifier le wikicode]
- 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).
