Tutorial 1 - Installing & Configuring Keycloak
What are we doing?
We install and configure Keycloak in a scripted manner.
The shown configuration will use the same values as in the Getting Started tutorials from the Keycloak website. Because of that, we can reuse the single page application (SPA) as is from the Keycloak website to test our result.
A big difference to most of the other blogs/tutorials/articles about Keycloak is, that we show all configurations by using the Admin CLI of Keycloak.
We demonstrate the usage of the Admin CLI to customize the master
realm and how to create a new realm myrealm
with one user and one client.
We also propose a set of shell scripts for a clean and readable configuration procedure. In all upcoming tutorials the same procedure for configuring Keycloak will be applied.
You can select from 3 different runtime environments for your installation:
- the official distribution file
keycloak-<VERSION>.[zip|tar.gz]
within a local runtime - the official docker image within a docker host runtime
- the official docker image within an OpenShift runtime
You only need to execute one of the above variants to complete this scenario and be prepared for the other scenarios.
Why are we doing this?
Doing all configurations in a scripted manner has many advantages:
- it allows automation
- it allows the usage of variables for supporting various environments
- it allows idempotence (the script can be executed multiple times producing the same results)
There are also alternatives for a scripted configuration, but the Admin CLI is available in all runtime environments and can be used as is.
Using the Admin REST API directly is an option, which requires additional custom development.
Keycloak Operator can only be used in a Kubernetes based runtime.
Requirements
For processing the JSON structures resulting from Admin CLI calls easily, we use the jq tool.
Steps
Step 1: Installing and starting the Keycloak server
Installation by distribution file
Let’s start with the download of the Keycloak distribution.
export KC_VERSION=21.0.2
curl -LO https://github.com/keycloak/keycloak/releases/download/"${KC_VERSION}"/keycloak-"${KC_VERSION}".zip
We continue with unpacking the archive.
unzip keycloak-${KC_VERSION}.zip
We now change to the new directory.
cd keycloak-${KC_VERSION}
This directory contains a Keycloak Quarkus application.
When we start the server for the first time, we have to set the admin user and the admin password:
KEYCLOAK_ADMIN=admin KEYCLOAK_ADMIN_PASSWORD=admin ./bin/kc.sh start-dev
When we start again, it is not necessary to set these variables, again. You can start the server with:
./bin/kc.sh start-dev
start-dev
runs the quarkus application in DEV-mode. Do not use this in production.
By default, the Keycloak server is using the following ports. They are only served from the localhost loopback address 127.0.0.1
:
- 8080 for Keycloak using HTTP
One of the last lines from the log output is:
2023-04-11 13:23:29,545 INFO [io.quarkus] (main) Keycloak 21.0.2 on JVM (powered by Quarkus 2.13.7.Final) started in 4.418s. Listening on: http://0.0.0.0:8080
We can now open the Administration Console from localhost and do the login with the just created admin user.
The distribution also contains the Admin CLI. This is the shell script ./bin/kcadm.sh
.
We define the environment variable KCADM
for the kcadm.sh
script. It must be the absolute path to the kcadm.sh
script from the above Keycloak installation.
export KCADM="/path/to/keycloak/bin/kcadm.sh"
export HOST_FOR_KCADM=localhost
Installation by docker image
To install and run Keycloak as a docker container a single command is necessary.
export KC_VERSION=21.0.2
docker run -p 8080:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:${KC_VERSION} start-dev
In the next steps we are using the Admin CLI script (kcadm.sh
). It is also contained in the Keycloak docker image. This means every call of the Admin CLI is executing the script from within the docker image.
docker run --rm --entrypoint /opt/keycloak/bin/kcadm.sh quay.io/keycloak/keycloak:${KC_VERSION}
We define the environment variable KCADM
for the above command. Additionally, we mount the $HOME/.keycloak
folder from the docker host at /opt/.keycloak
.
export KCADM="docker run --rm --entrypoint /opt/keycloak/bin/kcadm.sh -v ${HOME}/.keycloak:/opt/keycloak/.keycloak quay.io/keycloak/keycloak:${KC_VERSION}"
When we executed $KCADM
successfully the following output is shown:
Keycloak Admin CLI
Use 'kcadm.sh config credentials' command with username and password to start a session against a specific
server and realm.
For example:
$ kcadm.sh config credentials --server http://localhost:8080/auth --realm master --user admin
Enter password:
Logging into http://localhost:8080/auth as user admin of realm master
Any configured username can be used for login, but to perform admin operations the user
needs proper roles, otherwise operations will fail.
Usage: kcadm.sh COMMAND [ARGUMENTS]
Global options:
-x Print full stack trace when exiting with error
--help Print help for specific command
--config Path to the config file (~/.keycloak/kcadm.config by default)
Commands:
config Set up credentials, and other configuration settings using the config file
create Create new resource
get Get a resource
update Update a resource
delete Delete a resource
get-roles List roles for a user or a group
add-roles Add role to a user or a group
remove-roles Remove role from a user or a group
set-password Re-set password for a user
help This help
Use 'kcadm.sh help <command>' for more information about a given command.
When executing this script with a command (like config, create, get etc.) it connects to the Keycloak instance running in another docker container. Depending on the docker environment you are using, the host name of the Keycloak instance must be specified differently. For Docker Desktop environments the host name can be defined as host.docker.internal
.
export HOST_FOR_KCADM=host.docker.internal
Installation by docker image within OpenShift
Coming soon!
see also Getting Started with Keycloak on OpenShift
Step 2: Connecting the Admin CLI
Now we connect the Keycloak Admin CLI to the API and authenticate with the user created previously. We use two environment variables created in Step 1:
$KCADM
$HOST_FOR_KCADM
Please make sure they are defined. Their definition is dependent on the runtime you have chosen.
We log in to the master
realm with the admin
user. By using the options config credentials
we request and maintain an authenticated session, which is used for all further calls. Be aware the access and refresh tokens for this session will be stored in the file $HOME/.keycloak/kcadm.config
.
$KCADM config credentials --server http://$HOST_FOR_KCADM:8080 --user admin --password admin --realm master
To check the successful authentication and an authenticated session, we make a first request to the API.
$KCADM get serverinfo
The Keycloak server responds with a dump of information about its state and functionality. The same information is also available within the Web Admin Console.
Step 3: Configuring
We configure a new realm with a new client and a new user.
Simple Configuration
We do the complete configuration of Keycloak by executing the Admin CLI commands within shell scripts.
For simplicity reasons all CLI commands in this section are in one file myrealm-keycloak-configuration.sh
. The next section shows how the scripts can be organized for a better maintenance.
The complete content of the file myrealm-keycloak-configuration.sh
(download)
#!/usr/bin/env bash
set -euo pipefail
# authenticate and create session
$KCADM config credentials --server http://$HOST_FOR_KCADM:8080 --user admin --password admin --realm master
# define the name of the realm used as the target for the following commands
REALM_NAME=master
# enable the storage of admin events including their representation
$KCADM update events/config -r ${REALM_NAME} -s adminEventsEnabled=true -s adminEventsDetailsEnabled=true
# enable the storage of login events and define the expiration of a stored login event
$KCADM update events/config -r ${REALM_NAME} -s eventsEnabled=true -s eventsExpiration=259200
# define the login event types to be stored
$KCADM update events/config -r ${REALM_NAME} -s 'enabledEventTypes=["CLIENT_DELETE", "CLIENT_DELETE_ERROR", "CLIENT_INFO", "CLIENT_INFO_ERROR", "CLIENT_INITIATED_ACCOUNT_LINKING", "CLIENT_INITIATED_ACCOUNT_LINKING_ERROR", "CLIENT_LOGIN", "CLIENT_LOGIN_ERROR", "CLIENT_REGISTER", "CLIENT_REGISTER_ERROR", "CLIENT_UPDATE", "CLIENT_UPDATE_ERROR", "CODE_TO_TOKEN", "CODE_TO_TOKEN_ERROR", "CUSTOM_REQUIRED_ACTION", "CUSTOM_REQUIRED_ACTION_ERROR", "EXECUTE_ACTIONS", "EXECUTE_ACTIONS_ERROR", "EXECUTE_ACTION_TOKEN", "EXECUTE_ACTION_TOKEN_ERROR", "FEDERATED_IDENTITY_LINK", "FEDERATED_IDENTITY_LINK_ERROR", "GRANT_CONSENT", "GRANT_CONSENT_ERROR", "IDENTITY_PROVIDER_FIRST_LOGIN", "IDENTITY_PROVIDER_FIRST_LOGIN_ERROR", "IDENTITY_PROVIDER_LINK_ACCOUNT", "IDENTITY_PROVIDER_LINK_ACCOUNT_ERROR", "IDENTITY_PROVIDER_LOGIN", "IDENTITY_PROVIDER_LOGIN_ERROR", "IDENTITY_PROVIDER_POST_LOGIN", "IDENTITY_PROVIDER_POST_LOGIN_ERROR", "IDENTITY_PROVIDER_RESPONSE", "IDENTITY_PROVIDER_RESPONSE_ERROR", "IDENTITY_PROVIDER_RETRIEVE_TOKEN", "IDENTITY_PROVIDER_RETRIEVE_TOKEN_ERROR", "IMPERSONATE", "IMPERSONATE_ERROR", "INTROSPECT_TOKEN", "INTROSPECT_TOKEN_ERROR", "INVALID_SIGNATURE", "INVALID_SIGNATURE_ERROR", "LOGIN", "LOGIN_ERROR", "LOGOUT", "LOGOUT_ERROR", "PERMISSION_TOKEN", "PERMISSION_TOKEN_ERROR", "REFRESH_TOKEN", "REFRESH_TOKEN_ERROR", "REGISTER", "REGISTER_ERROR", "REGISTER_NODE", "REGISTER_NODE_ERROR", "REMOVE_FEDERATED_IDENTITY", "REMOVE_FEDERATED_IDENTITY_ERROR", "REMOVE_TOTP", "REMOVE_TOTP_ERROR", "RESET_PASSWORD", "RESET_PASSWORD_ERROR", "RESTART_AUTHENTICATION", "RESTART_AUTHENTICATION_ERROR", "REVOKE_GRANT", "REVOKE_GRANT_ERROR", "SEND_IDENTITY_PROVIDER_LINK", "SEND_IDENTITY_PROVIDER_LINK_ERROR", "SEND_RESET_PASSWORD", "SEND_RESET_PASSWORD_ERROR", "SEND_VERIFY_EMAIL", "SEND_VERIFY_EMAIL_ERROR", "TOKEN_EXCHANGE", "TOKEN_EXCHANGE_ERROR", "UNREGISTER_NODE", "UNREGISTER_NODE_ERROR", "UPDATE_CONSENT", "UPDATE_CONSENT_ERROR", "UPDATE_EMAIL", "UPDATE_EMAIL_ERROR", "UPDATE_PASSWORD", "UPDATE_PASSWORD_ERROR", "UPDATE_PROFILE", "UPDATE_PROFILE_ERROR", "UPDATE_TOTP", "UPDATE_TOTP_ERROR", "USER_INFO_REQUEST", "USER_INFO_REQUEST_ERROR", "VALIDATE_ACCESS_TOKEN", "VALIDATE_ACCESS_TOKEN_ERROR", "VERIFY_EMAIL", "VERIFY_EMAIL_ERROR"]'
# change target realm
REALM_NAME=myrealm
# create a new realm
$KCADM create realms -s realm="${REALM_NAME}" -s enabled=true
# enable the storage of admin events including their representation
$KCADM update events/config -r ${REALM_NAME} -s adminEventsEnabled=true -s adminEventsDetailsEnabled=true
# enable the storage of login events and define the expiration of a stored login event
$KCADM update events/config -r ${REALM_NAME} -s eventsEnabled=true -s eventsExpiration=259200
# define the login event types to be stored
$KCADM update events/config -r ${REALM_NAME} -s 'enabledEventTypes=["CLIENT_DELETE", "CLIENT_DELETE_ERROR", "CLIENT_INFO", "CLIENT_INFO_ERROR", "CLIENT_INITIATED_ACCOUNT_LINKING", "CLIENT_INITIATED_ACCOUNT_LINKING_ERROR", "CLIENT_LOGIN", "CLIENT_LOGIN_ERROR", "CLIENT_REGISTER", "CLIENT_REGISTER_ERROR", "CLIENT_UPDATE", "CLIENT_UPDATE_ERROR", "CODE_TO_TOKEN", "CODE_TO_TOKEN_ERROR", "CUSTOM_REQUIRED_ACTION", "CUSTOM_REQUIRED_ACTION_ERROR", "EXECUTE_ACTIONS", "EXECUTE_ACTIONS_ERROR", "EXECUTE_ACTION_TOKEN", "EXECUTE_ACTION_TOKEN_ERROR", "FEDERATED_IDENTITY_LINK", "FEDERATED_IDENTITY_LINK_ERROR", "GRANT_CONSENT", "GRANT_CONSENT_ERROR", "IDENTITY_PROVIDER_FIRST_LOGIN", "IDENTITY_PROVIDER_FIRST_LOGIN_ERROR", "IDENTITY_PROVIDER_LINK_ACCOUNT", "IDENTITY_PROVIDER_LINK_ACCOUNT_ERROR", "IDENTITY_PROVIDER_LOGIN", "IDENTITY_PROVIDER_LOGIN_ERROR", "IDENTITY_PROVIDER_POST_LOGIN", "IDENTITY_PROVIDER_POST_LOGIN_ERROR", "IDENTITY_PROVIDER_RESPONSE", "IDENTITY_PROVIDER_RESPONSE_ERROR", "IDENTITY_PROVIDER_RETRIEVE_TOKEN", "IDENTITY_PROVIDER_RETRIEVE_TOKEN_ERROR", "IMPERSONATE", "IMPERSONATE_ERROR", "INTROSPECT_TOKEN", "INTROSPECT_TOKEN_ERROR", "INVALID_SIGNATURE", "INVALID_SIGNATURE_ERROR", "LOGIN", "LOGIN_ERROR", "LOGOUT", "LOGOUT_ERROR", "PERMISSION_TOKEN", "PERMISSION_TOKEN_ERROR", "REFRESH_TOKEN", "REFRESH_TOKEN_ERROR", "REGISTER", "REGISTER_ERROR", "REGISTER_NODE", "REGISTER_NODE_ERROR", "REMOVE_FEDERATED_IDENTITY", "REMOVE_FEDERATED_IDENTITY_ERROR", "REMOVE_TOTP", "REMOVE_TOTP_ERROR", "RESET_PASSWORD", "RESET_PASSWORD_ERROR", "RESTART_AUTHENTICATION", "RESTART_AUTHENTICATION_ERROR", "REVOKE_GRANT", "REVOKE_GRANT_ERROR", "SEND_IDENTITY_PROVIDER_LINK", "SEND_IDENTITY_PROVIDER_LINK_ERROR", "SEND_RESET_PASSWORD", "SEND_RESET_PASSWORD_ERROR", "SEND_VERIFY_EMAIL", "SEND_VERIFY_EMAIL_ERROR", "TOKEN_EXCHANGE", "TOKEN_EXCHANGE_ERROR", "UNREGISTER_NODE", "UNREGISTER_NODE_ERROR", "UPDATE_CONSENT", "UPDATE_CONSENT_ERROR", "UPDATE_EMAIL", "UPDATE_EMAIL_ERROR", "UPDATE_PASSWORD", "UPDATE_PASSWORD_ERROR", "UPDATE_PROFILE", "UPDATE_PROFILE_ERROR", "UPDATE_TOTP", "UPDATE_TOTP_ERROR", "USER_INFO_REQUEST", "USER_INFO_REQUEST_ERROR", "VALIDATE_ACCESS_TOKEN", "VALIDATE_ACCESS_TOKEN_ERROR", "VERIFY_EMAIL", "VERIFY_EMAIL_ERROR"]'
# create a new client
$KCADM create clients -r ${REALM_NAME} -s clientId="myclient" -s enabled=true -s name="My Client" -s protocol=openid-connect -s publicClient=true -s standardFlowEnabled=true -s 'redirectUris=["https://www.keycloak.org/app/*"]' -s baseUrl="https://www.keycloak.org/app/" -s 'webOrigins=["*"]'
# create a new user
$KCADM create users -r $REALM_NAME -s username="myuser" -s enabled=true -s firstName="My" -s lastName="User" -s email="[email protected]"
# set the password for the new user
$KCADM set-password -r $REALM_NAME --username "myuser" --new-password "myuser"
Subsequently, we explain every CLI call of the above script.
We log in to the master
realm with the admin
user and request an authenticated session, which is used for all further calls.
$KCADM config credentials --server http://localhost:8080/auth --user admin --password admin --realm master
The admin
user has access to all realms. Therefore, we need to specify to which realm each command should be applied to. This can be done with the -r
option followed by the name of the realm. We define a variable for the realm name used with the -r
option in all upcoming calls.
REALM_NAME=master
Keycloak knows a rich set of events. They are divided into admin and login events. Because Keycloak doesn’t store any of these events by default, they cannot be viewed in the Admin Console. We first enable the storage of admin events with their representation details.
$KCADM update events/config -r ${REALM_NAME} -s adminEventsEnabled=true -s adminEventsDetailsEnabled=true
We also enable the storage of login events and set the expiration period for a stored event to 3 days (in seconds 259200
).
$KCADM update events/config -r ${REALM_NAME} -s eventsEnabled=true -s eventsExpiration=259200
We define the list of login event types, which are stored. The easiest way to get the complete list of login event types is from the serverinfo
.
$KCADM update events/config -r ${REALM_NAME} -s 'enabledEventTypes=["CLIENT_DELETE", "CLIENT_DELETE_ERROR", "CLIENT_INFO", "CLIENT_INFO_ERROR", "CLIENT_INITIATED_ACCOUNT_LINKING", "CLIENT_INITIATED_ACCOUNT_LINKING_ERROR", "CLIENT_LOGIN", "CLIENT_LOGIN_ERROR", "CLIENT_REGISTER", "CLIENT_REGISTER_ERROR", "CLIENT_UPDATE", "CLIENT_UPDATE_ERROR", "CODE_TO_TOKEN", "CODE_TO_TOKEN_ERROR", "CUSTOM_REQUIRED_ACTION", "CUSTOM_REQUIRED_ACTION_ERROR", "EXECUTE_ACTIONS", "EXECUTE_ACTIONS_ERROR", "EXECUTE_ACTION_TOKEN", "EXECUTE_ACTION_TOKEN_ERROR", "FEDERATED_IDENTITY_LINK", "FEDERATED_IDENTITY_LINK_ERROR", "GRANT_CONSENT", "GRANT_CONSENT_ERROR", "IDENTITY_PROVIDER_FIRST_LOGIN", "IDENTITY_PROVIDER_FIRST_LOGIN_ERROR", "IDENTITY_PROVIDER_LINK_ACCOUNT", "IDENTITY_PROVIDER_LINK_ACCOUNT_ERROR", "IDENTITY_PROVIDER_LOGIN", "IDENTITY_PROVIDER_LOGIN_ERROR", "IDENTITY_PROVIDER_POST_LOGIN", "IDENTITY_PROVIDER_POST_LOGIN_ERROR", "IDENTITY_PROVIDER_RESPONSE", "IDENTITY_PROVIDER_RESPONSE_ERROR", "IDENTITY_PROVIDER_RETRIEVE_TOKEN", "IDENTITY_PROVIDER_RETRIEVE_TOKEN_ERROR", "IMPERSONATE", "IMPERSONATE_ERROR", "INTROSPECT_TOKEN", "INTROSPECT_TOKEN_ERROR", "INVALID_SIGNATURE", "INVALID_SIGNATURE_ERROR", "LOGIN", "LOGIN_ERROR", "LOGOUT", "LOGOUT_ERROR", "PERMISSION_TOKEN", "PERMISSION_TOKEN_ERROR", "REFRESH_TOKEN", "REFRESH_TOKEN_ERROR", "REGISTER", "REGISTER_ERROR", "REGISTER_NODE", "REGISTER_NODE_ERROR", "REMOVE_FEDERATED_IDENTITY", "REMOVE_FEDERATED_IDENTITY_ERROR", "REMOVE_TOTP", "REMOVE_TOTP_ERROR", "RESET_PASSWORD", "RESET_PASSWORD_ERROR", "RESTART_AUTHENTICATION", "RESTART_AUTHENTICATION_ERROR", "REVOKE_GRANT", "REVOKE_GRANT_ERROR", "SEND_IDENTITY_PROVIDER_LINK", "SEND_IDENTITY_PROVIDER_LINK_ERROR", "SEND_RESET_PASSWORD", "SEND_RESET_PASSWORD_ERROR", "SEND_VERIFY_EMAIL", "SEND_VERIFY_EMAIL_ERROR", "TOKEN_EXCHANGE", "TOKEN_EXCHANGE_ERROR", "UNREGISTER_NODE", "UNREGISTER_NODE_ERROR", "UPDATE_CONSENT", "UPDATE_CONSENT_ERROR", "UPDATE_EMAIL", "UPDATE_EMAIL_ERROR", "UPDATE_PASSWORD", "UPDATE_PASSWORD_ERROR", "UPDATE_PROFILE", "UPDATE_PROFILE_ERROR", "UPDATE_TOTP", "UPDATE_TOTP_ERROR", "USER_INFO_REQUEST", "USER_INFO_REQUEST_ERROR", "VALIDATE_ACCESS_TOKEN", "VALIDATE_ACCESS_TOKEN_ERROR", "VERIFY_EMAIL", "VERIFY_EMAIL_ERROR"]'
Because Keycloak suggests the master
realm should only be used for administration purposes of other realms, we create a new realm with the name myrealm
.
REALM_NAME=myrealm
$KCADM create realms -s realm="${REALM_NAME}" -s enabled=true
We configure the same event storage for our new realm as the master
realm.
$KCADM update events/config -r ${REALM_NAME} -s adminEventsEnabled=true -s adminEventsDetailsEnabled=true
$KCADM update events/config -r ${REALM_NAME} -s eventsEnabled=true -s eventsExpiration=259200
$KCADM update events/config -r ${REALM_NAME} -s 'enabledEventTypes=["CLIENT_DELETE", "CLIENT_DELETE_ERROR", "CLIENT_INFO", "CLIENT_INFO_ERROR", "CLIENT_INITIATED_ACCOUNT_LINKING", "CLIENT_INITIATED_ACCOUNT_LINKING_ERROR", "CLIENT_LOGIN", "CLIENT_LOGIN_ERROR", "CLIENT_REGISTER", "CLIENT_REGISTER_ERROR", "CLIENT_UPDATE", "CLIENT_UPDATE_ERROR", "CODE_TO_TOKEN", "CODE_TO_TOKEN_ERROR", "CUSTOM_REQUIRED_ACTION", "CUSTOM_REQUIRED_ACTION_ERROR", "EXECUTE_ACTIONS", "EXECUTE_ACTIONS_ERROR", "EXECUTE_ACTION_TOKEN", "EXECUTE_ACTION_TOKEN_ERROR", "FEDERATED_IDENTITY_LINK", "FEDERATED_IDENTITY_LINK_ERROR", "GRANT_CONSENT", "GRANT_CONSENT_ERROR", "IDENTITY_PROVIDER_FIRST_LOGIN", "IDENTITY_PROVIDER_FIRST_LOGIN_ERROR", "IDENTITY_PROVIDER_LINK_ACCOUNT", "IDENTITY_PROVIDER_LINK_ACCOUNT_ERROR", "IDENTITY_PROVIDER_LOGIN", "IDENTITY_PROVIDER_LOGIN_ERROR", "IDENTITY_PROVIDER_POST_LOGIN", "IDENTITY_PROVIDER_POST_LOGIN_ERROR", "IDENTITY_PROVIDER_RESPONSE", "IDENTITY_PROVIDER_RESPONSE_ERROR", "IDENTITY_PROVIDER_RETRIEVE_TOKEN", "IDENTITY_PROVIDER_RETRIEVE_TOKEN_ERROR", "IMPERSONATE", "IMPERSONATE_ERROR", "INTROSPECT_TOKEN", "INTROSPECT_TOKEN_ERROR", "INVALID_SIGNATURE", "INVALID_SIGNATURE_ERROR", "LOGIN", "LOGIN_ERROR", "LOGOUT", "LOGOUT_ERROR", "PERMISSION_TOKEN", "PERMISSION_TOKEN_ERROR", "REFRESH_TOKEN", "REFRESH_TOKEN_ERROR", "REGISTER", "REGISTER_ERROR", "REGISTER_NODE", "REGISTER_NODE_ERROR", "REMOVE_FEDERATED_IDENTITY", "REMOVE_FEDERATED_IDENTITY_ERROR", "REMOVE_TOTP", "REMOVE_TOTP_ERROR", "RESET_PASSWORD", "RESET_PASSWORD_ERROR", "RESTART_AUTHENTICATION", "RESTART_AUTHENTICATION_ERROR", "REVOKE_GRANT", "REVOKE_GRANT_ERROR", "SEND_IDENTITY_PROVIDER_LINK", "SEND_IDENTITY_PROVIDER_LINK_ERROR", "SEND_RESET_PASSWORD", "SEND_RESET_PASSWORD_ERROR", "SEND_VERIFY_EMAIL", "SEND_VERIFY_EMAIL_ERROR", "TOKEN_EXCHANGE", "TOKEN_EXCHANGE_ERROR", "UNREGISTER_NODE", "UNREGISTER_NODE_ERROR", "UPDATE_CONSENT", "UPDATE_CONSENT_ERROR", "UPDATE_EMAIL", "UPDATE_EMAIL_ERROR", "UPDATE_PASSWORD", "UPDATE_PASSWORD_ERROR", "UPDATE_PROFILE", "UPDATE_PROFILE_ERROR", "UPDATE_TOTP", "UPDATE_TOTP_ERROR", "USER_INFO_REQUEST", "USER_INFO_REQUEST_ERROR", "VALIDATE_ACCESS_TOKEN", "VALIDATE_ACCESS_TOKEN_ERROR", "VERIFY_EMAIL", "VERIFY_EMAIL_ERROR"]'
We create a new client. Please note that a client has two very similar attributes within Keycloak: the id
and the clientId
. The clientId
must be given to the CLI call, and the id
is generated by Keycloak and written to the output upon client creation. For other CLI calls either the id
or the clientId
must be used.
$KCADM create clients -r ${REALM_NAME} -s clientId="myclient" -s enabled=true -s name="My Client" -s protocol=openid-connect -s publicClient=true -s standardFlowEnabled=true -s 'redirectUris=["https://www.keycloak.org/app/*"]' -s baseUrl="https://www.keycloak.org/app/" -s 'webOrigins=["*"]'
We create a new user with the username myuser
.
$KCADM create users -r $REALM_NAME -s username="myuser" -s enabled=true -s firstName="My" -s lastName="User" -s email="[email protected]"
We set the password myuser
for the new user.
$KCADM set-password -r $REALM_NAME --username "myuser" --new-password "myuser"
When we execute the [myrealm-keycloak-configuration.sh] script to start a fresh Keycloak instance, it runs through and does the setup in a fully automated way.
You should see an output similar to this:
Logging into http://localhost:8080/auth as user admin of realm master
Created new realm with id 'myrealm'
Created new client with id 'a5e95d8c-2dc0-4e42-a48e-f7873944b3eb'
Created new user with id '559905a6-77bc-49ae-acdb-2788cd4282c4'
After the script has been executed successfully, the configuration can be tested. The easiest way is by using the test application from the Keycloak website. It is a single page application (SPA) with configurable parameters for the Keycloak server to be used. The predefined parameters for Keycloak URL
, Realm
and Client
are well suited for our configuration.
The screen flow can be seen by the following screenshots: SPA Config → SPA Sign in → Keycloak Login → SPA Hello.
In another tutorial we will explain the details steps happening behind the scenes when using this example application.
However, this script is far from being ready for production use, not only because it fails if there is already a realm called myrealm
. If you want to execute this script again, you need to delete the myrealm
realm. This can be done in the Admin Console or also by a CLI call.
$KCADM delete realms/myrealm
Please note that deleting a realm will also delete the users from that realm. That is the reason why this action is not a good idea for production environments.
Production Configuration
For a better maintenance we propose a refactoring of the above script.
- we split the single script into multiple scripts with clearer responsibilities
- we split the creation CLI calls into a
create
andupdate
call to make the script idempotent - we create bash functions to keep the scripts more verbose
We split the script from the previous section into 5 different scripts
The keycloak-configuration.sh script is the entry point into the Keycloak configuration. Only this script must be started. It handles the authentication using the environment variables KEYCLOAK_USER
and KEYCLOAK_PASSWORD
and defines the variable KCADM
for the kcadm.sh
shell script by using the environment variable KEYCLOAK_HOME
. The KCADM
variable can be used in the other scripts.
The keycloak-configuration-helpers.sh script contains all reusable bash functions for the Admin CLI. It is sourced by the keycloak-configuration.sh script.
Mainly create
CLI calls should be defined as a function, which check for existence before doing the call. This way we can assure the idempotence of the whole configuration. As a general pattern we try to keep the create
CLI call as minimal as possible and do all other configurations using update
CLI calls. For easier handling the create
CLI calls should always output the id
of the created or already existing resource for further use.
The realms.sh script sources the corresponding shell script, e.g. realm_master.sh
and realm_myrealm.sh
for each realm to be configured. It is in turn sourced by the keycloak-configuration.sh script.
The realm_master.sh script contains all configurations for the master
realm.
The realm_myrealm.sh script contains all configurations for the myrealm
realm.
Let’s look at each of these files. The resulting configuration is the same as in the previous section.
keycloak-configuration.sh
This script is the entry point into the Keycloak configuration.
keycloak-configuration.sh
(download)
#!/usr/bin/env bash
set -euo pipefail
echo ""
echo "========================================================"
echo "== STARTING KEYCLOAK CONFIGURATION =="
echo "========================================================"
BASEDIR=$(dirname "$0")
source $BASEDIR/keycloak-configuration-helpers.sh
if [ "$KCADM" == "" ]; then
KCADM=$KEYCLOAK_HOME/bin/kcadm.sh
echo "Using $KCADM as the admin CLI."
fi
$KCADM config credentials --server http://$HOST_FOR_KCADM:8080 --user $KEYCLOAK_USER --password $KEYCLOAK_PASSWORD --realm master
source $BASEDIR/realms.sh
echo "========================================================"
echo "== KEYCLOAK CONFIGURATION DONE =="
echo "========================================================"
echo ""
This file rarely changes.
keycloak-configuration-helpers.sh
This script contains all reusable bash functions for the Admin CLI. It is sourced by the keycloak-configuration.sh script.
keycloak-configuration-helpers.sh
(download)
#!/usr/bin/env bash
set -euo pipefail
# the new realm is also set as enabled
createRealm() {
# arguments
REALM_NAME=$1
#
EXISTING_REALM=$($KCADM get realms | jq '.[] | select(.realm==("'$REALM_NAME'")) | .id')
if [ "$EXISTING_REALM" == "" ]; then
$KCADM create realms -s realm="${REALM_NAME}" -s enabled=true
fi
}
# the new client is also set as enabled
createClient() {
# arguments
REALM_NAME=$1
CLIENT_ID=$2
#
ID=$(getClient $REALM_NAME $CLIENT_ID)
if [[ "$ID" == "" ]]; then
$KCADM create clients -r $REALM_NAME -s clientId=$CLIENT_ID -s enabled=true
fi
echo $(getClient $REALM_NAME $CLIENT_ID)
}
# get the object id of the client for a given clientId
getClient () {
# arguments
REALM_NAME=$1
CLIENT_ID=$2
#
ID=$($KCADM get clients -r $REALM_NAME --fields id,clientId | jq '.[] | select(.clientId==("'$CLIENT_ID'")) | .id')
echo $(sed -e 's/"//g' <<< $ID)
}
# create a user for the given username if it doesn't exist yet and return the object id
createUser() {
# arguments
REALM_NAME=$1
USER_NAME=$2
#
USER_ID=$(getUser $REALM_NAME $USER_NAME)
if [ "$USER_ID" == "" ]; then
$KCADM create users -r $REALM_NAME -s username=$USER_NAME -s enabled=true
fi
echo $(getUser $REALM_NAME $USER_NAME)
}
# the object id of the user for a given username
getUser() {
# arguments
REALM_NAME=$1
USERNAME=$2
#
USER=$($KCADM get users -r $REALM_NAME -q username=$USERNAME | jq '.[] | select(.username==("'$USERNAME'")) | .id' )
echo $(sed -e 's/"//g' <<< $USER)
}
This file changes when new functions must be added.
realms.sh
This script sources the corresponding shell script, e.g. realm_master.sh, realm_myrealm.sh, for each realm to be configured.
realms.sh
(download)
#!/usr/bin/env bash
set -euo pipefail
######################
### Setup realms via their script
######################
echo "setting up realms..."
source $BASEDIR/realm_master.sh
source $BASEDIR/realm_myrealm.sh
This file changes if the list of realms must be updated.
realm_master.sh
realm_master.sh
(download)
#!/usr/bin/env bash
set -euo pipefail
REALM_NAME='master'
echo ""
echo "================================="
echo "setting up realm $REALM_NAME..."
echo "================================="
echo ""
# enable the storage of admin events including their representation
$KCADM update events/config -r ${REALM_NAME} -s adminEventsEnabled=true -s adminEventsDetailsEnabled=true
# enable the storage of login events and define the expiration of a stored login event
$KCADM update events/config -r ${REALM_NAME} -s eventsEnabled=true -s eventsExpiration=259200
# define the login event types to be stored
$KCADM update events/config -r ${REALM_NAME} -s 'enabledEventTypes=["CLIENT_DELETE", "CLIENT_DELETE_ERROR", "CLIENT_INFO", "CLIENT_INFO_ERROR", "CLIENT_INITIATED_ACCOUNT_LINKING", "CLIENT_INITIATED_ACCOUNT_LINKING_ERROR", "CLIENT_LOGIN", "CLIENT_LOGIN_ERROR", "CLIENT_REGISTER", "CLIENT_REGISTER_ERROR", "CLIENT_UPDATE", "CLIENT_UPDATE_ERROR", "CODE_TO_TOKEN", "CODE_TO_TOKEN_ERROR", "CUSTOM_REQUIRED_ACTION", "CUSTOM_REQUIRED_ACTION_ERROR", "EXECUTE_ACTIONS", "EXECUTE_ACTIONS_ERROR", "EXECUTE_ACTION_TOKEN", "EXECUTE_ACTION_TOKEN_ERROR", "FEDERATED_IDENTITY_LINK", "FEDERATED_IDENTITY_LINK_ERROR", "GRANT_CONSENT", "GRANT_CONSENT_ERROR", "IDENTITY_PROVIDER_FIRST_LOGIN", "IDENTITY_PROVIDER_FIRST_LOGIN_ERROR", "IDENTITY_PROVIDER_LINK_ACCOUNT", "IDENTITY_PROVIDER_LINK_ACCOUNT_ERROR", "IDENTITY_PROVIDER_LOGIN", "IDENTITY_PROVIDER_LOGIN_ERROR", "IDENTITY_PROVIDER_POST_LOGIN", "IDENTITY_PROVIDER_POST_LOGIN_ERROR", "IDENTITY_PROVIDER_RESPONSE", "IDENTITY_PROVIDER_RESPONSE_ERROR", "IDENTITY_PROVIDER_RETRIEVE_TOKEN", "IDENTITY_PROVIDER_RETRIEVE_TOKEN_ERROR", "IMPERSONATE", "IMPERSONATE_ERROR", "INTROSPECT_TOKEN", "INTROSPECT_TOKEN_ERROR", "INVALID_SIGNATURE", "INVALID_SIGNATURE_ERROR", "LOGIN", "LOGIN_ERROR", "LOGOUT", "LOGOUT_ERROR", "PERMISSION_TOKEN", "PERMISSION_TOKEN_ERROR", "REFRESH_TOKEN", "REFRESH_TOKEN_ERROR", "REGISTER", "REGISTER_ERROR", "REGISTER_NODE", "REGISTER_NODE_ERROR", "REMOVE_FEDERATED_IDENTITY", "REMOVE_FEDERATED_IDENTITY_ERROR", "REMOVE_TOTP", "REMOVE_TOTP_ERROR", "RESET_PASSWORD", "RESET_PASSWORD_ERROR", "RESTART_AUTHENTICATION", "RESTART_AUTHENTICATION_ERROR", "REVOKE_GRANT", "REVOKE_GRANT_ERROR", "SEND_IDENTITY_PROVIDER_LINK", "SEND_IDENTITY_PROVIDER_LINK_ERROR", "SEND_RESET_PASSWORD", "SEND_RESET_PASSWORD_ERROR", "SEND_VERIFY_EMAIL", "SEND_VERIFY_EMAIL_ERROR", "TOKEN_EXCHANGE", "TOKEN_EXCHANGE_ERROR", "UNREGISTER_NODE", "UNREGISTER_NODE_ERROR", "UPDATE_CONSENT", "UPDATE_CONSENT_ERROR", "UPDATE_EMAIL", "UPDATE_EMAIL_ERROR", "UPDATE_PASSWORD", "UPDATE_PASSWORD_ERROR", "UPDATE_PROFILE", "UPDATE_PROFILE_ERROR", "UPDATE_TOTP", "UPDATE_TOTP_ERROR", "USER_INFO_REQUEST", "USER_INFO_REQUEST_ERROR", "VALIDATE_ACCESS_TOKEN", "VALIDATE_ACCESS_TOKEN_ERROR", "VERIFY_EMAIL", "VERIFY_EMAIL_ERROR"]'
realm_myrealm.sh
realm_myrealm.sh
(download)
#!/usr/bin/env bash
set -euo pipefail
REALM_NAME='myrealm'
echo ""
echo "================================="
echo "setting up realm $REALM_NAME..."
echo "================================="
echo ""
createRealm $REALM_NAME
# enable the storage of admin events including their representation
$KCADM update events/config -r ${REALM_NAME} -s adminEventsEnabled=true -s adminEventsDetailsEnabled=true
# enable the storage of login events and define the expiration of a stored login event
$KCADM update events/config -r ${REALM_NAME} -s eventsEnabled=true -s eventsExpiration=259200
# define the login event types to be stored
$KCADM update events/config -r ${REALM_NAME} -s 'enabledEventTypes=["CLIENT_DELETE", "CLIENT_DELETE_ERROR", "CLIENT_INFO", "CLIENT_INFO_ERROR", "CLIENT_INITIATED_ACCOUNT_LINKING", "CLIENT_INITIATED_ACCOUNT_LINKING_ERROR", "CLIENT_LOGIN", "CLIENT_LOGIN_ERROR", "CLIENT_REGISTER", "CLIENT_REGISTER_ERROR", "CLIENT_UPDATE", "CLIENT_UPDATE_ERROR", "CODE_TO_TOKEN", "CODE_TO_TOKEN_ERROR", "CUSTOM_REQUIRED_ACTION", "CUSTOM_REQUIRED_ACTION_ERROR", "EXECUTE_ACTIONS", "EXECUTE_ACTIONS_ERROR", "EXECUTE_ACTION_TOKEN", "EXECUTE_ACTION_TOKEN_ERROR", "FEDERATED_IDENTITY_LINK", "FEDERATED_IDENTITY_LINK_ERROR", "GRANT_CONSENT", "GRANT_CONSENT_ERROR", "IDENTITY_PROVIDER_FIRST_LOGIN", "IDENTITY_PROVIDER_FIRST_LOGIN_ERROR", "IDENTITY_PROVIDER_LINK_ACCOUNT", "IDENTITY_PROVIDER_LINK_ACCOUNT_ERROR", "IDENTITY_PROVIDER_LOGIN", "IDENTITY_PROVIDER_LOGIN_ERROR", "IDENTITY_PROVIDER_POST_LOGIN", "IDENTITY_PROVIDER_POST_LOGIN_ERROR", "IDENTITY_PROVIDER_RESPONSE", "IDENTITY_PROVIDER_RESPONSE_ERROR", "IDENTITY_PROVIDER_RETRIEVE_TOKEN", "IDENTITY_PROVIDER_RETRIEVE_TOKEN_ERROR", "IMPERSONATE", "IMPERSONATE_ERROR", "INTROSPECT_TOKEN", "INTROSPECT_TOKEN_ERROR", "INVALID_SIGNATURE", "INVALID_SIGNATURE_ERROR", "LOGIN", "LOGIN_ERROR", "LOGOUT", "LOGOUT_ERROR", "PERMISSION_TOKEN", "PERMISSION_TOKEN_ERROR", "REFRESH_TOKEN", "REFRESH_TOKEN_ERROR", "REGISTER", "REGISTER_ERROR", "REGISTER_NODE", "REGISTER_NODE_ERROR", "REMOVE_FEDERATED_IDENTITY", "REMOVE_FEDERATED_IDENTITY_ERROR", "REMOVE_TOTP", "REMOVE_TOTP_ERROR", "RESET_PASSWORD", "RESET_PASSWORD_ERROR", "RESTART_AUTHENTICATION", "RESTART_AUTHENTICATION_ERROR", "REVOKE_GRANT", "REVOKE_GRANT_ERROR", "SEND_IDENTITY_PROVIDER_LINK", "SEND_IDENTITY_PROVIDER_LINK_ERROR", "SEND_RESET_PASSWORD", "SEND_RESET_PASSWORD_ERROR", "SEND_VERIFY_EMAIL", "SEND_VERIFY_EMAIL_ERROR", "TOKEN_EXCHANGE", "TOKEN_EXCHANGE_ERROR", "UNREGISTER_NODE", "UNREGISTER_NODE_ERROR", "UPDATE_CONSENT", "UPDATE_CONSENT_ERROR", "UPDATE_EMAIL", "UPDATE_EMAIL_ERROR", "UPDATE_PASSWORD", "UPDATE_PASSWORD_ERROR", "UPDATE_PROFILE", "UPDATE_PROFILE_ERROR", "UPDATE_TOTP", "UPDATE_TOTP_ERROR", "USER_INFO_REQUEST", "USER_INFO_REQUEST_ERROR", "VALIDATE_ACCESS_TOKEN", "VALIDATE_ACCESS_TOKEN_ERROR", "VERIFY_EMAIL", "VERIFY_EMAIL_ERROR"]'
# clients
CLIENT_ID=myclient
ID=$(createClient $REALM_NAME $CLIENT_ID)
$KCADM update clients/$ID -r $REALM_NAME -s name="My Client" -s protocol=openid-connect -s publicClient=true -s standardFlowEnabled=true -s 'redirectUris=["https://www.keycloak.org/app/*"]' -s baseUrl="https://www.keycloak.org/app/" -s 'webOrigins=["*"]'
#users
USER_NAME=myuser
USER_ID=$(createUser $REALM_NAME $USER_NAME)
$KCADM update users/$USER_ID -r $REALM_NAME -s firstName="My" -s lastName="User" -s email="[email protected]"
$KCADM set-password -r $REALM_NAME --username $USER_NAME --new-password $USER_NAME
References
- Getting Started with Keycloak
- Keycloak Testing SPA
- Server Guides
- Server Administration - Master Realm
- Server Administration - Auditing and Events
- Server Administration - The Admin CLI