Dieses Tutorial bietet eine Grundlage für die Verwendung von Keycloak mit einem Passkey. In diesem fall wird ein YubiKey verwendet.
3. Keycloak Konfiguration
Es wird erwartet das jq auf dem System installiert ist |
Um den Lernenden diesen Schritt zu erleichtern wurde die Keycloak-Instanz mit der Keycloak Admin CLI konfiguriert.
kc-init
#!/bin/sh
KC_VERSION=26.1.4
KC_NAME=keycloak-"${KC_VERSION}"
if [ ! -d "${KC_NAME}" ]; then
echo "================================"
echo " DOWNLOADING KEYCLOAK-${KC_VERSION} "
echo "================================"
curl -LO https://github.com/keycloak/keycloak/releases/download/"${KC_VERSION}"/"${KC_NAME}".zip
unzip "${KC_NAME}".zip
rm "${KC_NAME}".zip
echo "==========================================="
echo " FINISHED KEYCLOAK-${KC_VERSION} DOWNLOAD "
echo "==========================================="
else
echo "================================"
echo " FOUND KEYCLOAK-${KC_VERSION} "
echo "================================"
fi
cd "${KC_NAME}"
./bin/kc.sh build --features="web-authn,account-api,account,passkeys"
KEYCLOAK_ADMIN=admin KEYCLOAK_ADMIN_PASSWORD=admin ./bin/kc.sh start-dev
kc-setup
#!/bin/sh
KC_VERSION=26.1.4
KC_NAME=keycloak-"${KC_VERSION}"
HOST_FOR_KCADM=localhost
KCADM=./bin/kcadm.sh
REALM_NAME=water
CLIENT_ID=drop
USER_NAME=user1
# the new realm is also set as enabled
createRealm() {
# arguments
REALM_NAME=$1
#
EXISTING_REALM=$($KCADM get realms/$REALM_NAME)
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)
}
# create a top level flow for the given alias if it doesn't exist yet and return the object id
createTopLevelFlow() {
# arguments
REALM_NAME=$1
ALIAS=$2
#
FLOW_ID=$(getTopLevelFlow "$REALM_NAME" "$ALIAS")
if [ "$FLOW_ID" == "" ]; then
$KCADM create authentication/flows -r "$REALM_NAME" -s alias="$ALIAS" -s providerId=basic-flow -s topLevel=true -s builtIn=false
fi
echo $(getTopLevelFlow "$REALM_NAME" "$ALIAS")
}
deleteTopLevelFlow() {
# arguments
REALM_NAME=$1
ALIAS=$2
#
FLOW_ID=$(getTopLevelFlow "$REALM_NAME" "$ALIAS")
if [ "$FLOW_ID" != "" ]; then
$KCADM delete authentication/flows/"$FLOW_ID" -r "$REALM_NAME"
fi
echo $(getTopLevelFlow "$REALM_NAME" "$ALIAS")
}
getTopLevelFlow() {
# arguments
REALM_NAME=$1
ALIAS=$2
#
ID=$($KCADM get authentication/flows -r "$REALM_NAME" --fields id,alias| jq '.[] | select(.alias==("'$ALIAS'")) | .id')
echo $(sed -e 's/"//g' <<< $ID)
}
# create a new execution for a given providerId (the providerId is defined by AuthenticatorFactory)
createExecution() {
# arguments
REALM_NAME=$1
FLOW=$2
PROVIDER=$3
REQUIREMENT=$4
#
EXECUTION_ID=$($KCADM create authentication/flows/"$FLOW"/executions/execution -i -b '{"provider" : "'"$PROVIDER"'"}' -r "$REALM_NAME")
$KCADM update authentication/flows/"$FLOW"/executions -b '{"id":"'"$EXECUTION_ID"'","requirement":"'"$REQUIREMENT"'"}' -r "$REALM_NAME"
}
# create a new subflow
createSubflow() {
# arguments
REALM_NAME=$1
TOPLEVEL=$2
PARENT=$3
ALIAS="$4"
REQUIREMENT=$5
#
FLOW_ID=$($KCADM create authentication/flows/"$PARENT"/executions/flow -i -r "$REALM_NAME" -b '{"alias" : "'"$ALIAS"'" , "type" : "basic-flow"}')
EXECUTION_ID=$(getFlowExecution "$REALM_NAME" "$TOPLEVEL" "$FLOW_ID")
$KCADM update authentication/flows/"$TOPLEVEL"/executions -r "$REALM_NAME" -b '{"id":"'"$EXECUTION_ID"'","requirement":"'"$REQUIREMENT"'"}'
echo "Created new subflow with id '$FLOW_ID', alias '"$ALIAS"'"
}
getFlowExecution() {
# arguments
REALM_NAME=$1
TOPLEVEL=$2
FLOW_ID=$3
#
ID=$($KCADM get authentication/flows/"$TOPLEVEL"/executions -r "$REALM_NAME" --fields id,flowId,alias | jq '.[] | select(.flowId==("'"$FLOW_ID"'")) | .id')
echo $(sed -e 's/"//g' <<< $ID)
}
registerRequiredAction() {
#arguments
REALM_NAME="$1"
PROVIDER_ID="$2"
NAME="$3"
$KCADM delete authentication/required-actions/"$PROVIDER_ID" -r "$REALM_NAME"
$KCADM create authentication/register-required-action -r "$REALM_NAME" -s providerId="$PROVIDER_ID" -s name="$NAME"
}
echo ""
echo "================================="
echo "setting up realm $REALM_NAME..."
echo "================================="
echo ""
cd "${KC_NAME}"
$KCADM config credentials --server http://$HOST_FOR_KCADM:8080 --user admin --password admin --realm master
createRealm $REALM_NAME
# enable user registration
$KCADM update realms/$REALM_NAME -s registrationAllowed=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"]'
# clients
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=["*"]'
#############
# webauthn authentication flow with nested subflows
#
# set browser flow back to default so we can delete our flow
$KCADM update realms/$REALM_NAME -s browserFlow=browser
# flows: new flow
TOP_LEVEL_FLOW_NAME=Browser-Webauthn
deleteTopLevelFlow $REALM_NAME $TOP_LEVEL_FLOW_NAME
createTopLevelFlow $REALM_NAME $TOP_LEVEL_FLOW_NAME
# Cookie
createExecution $REALM_NAME $TOP_LEVEL_FLOW_NAME auth-cookie ALTERNATIVE
# create subflow for all user interactions
FORMS_SUBFLOW_NAME=Forms
createSubflow $REALM_NAME $TOP_LEVEL_FLOW_NAME $TOP_LEVEL_FLOW_NAME $FORMS_SUBFLOW_NAME ALTERNATIVE
# Username
createExecution $REALM_NAME $FORMS_SUBFLOW_NAME auth-username-form REQUIRED
# create subflow
PWD_OR_2FA_SUBFLOW_NAME="Passwordless_Or_Two-factors"
createSubflow $REALM_NAME "$TOP_LEVEL_FLOW_NAME" $FORMS_SUBFLOW_NAME "$PWD_OR_2FA_SUBFLOW_NAME" REQUIRED
# Passwordless
createExecution "$REALM_NAME" "$PWD_OR_2FA_SUBFLOW_NAME" webauthn-authenticator-passwordless ALTERNATIVE
# create subflow
PWD_AND_2FA_SUBFLOW_NAME="Password_And_Second-factor"
createSubflow "$REALM_NAME" "$TOP_LEVEL_FLOW_NAME" "$PWD_OR_2FA_SUBFLOW_NAME" "$PWD_AND_2FA_SUBFLOW_NAME" ALTERNATIVE
# Password
createExecution "$REALM_NAME" "$PWD_AND_2FA_SUBFLOW_NAME" auth-password-form REQUIRED
# create subflow 2FA
SECOND_FACTOR_SUBFLOW_NAME="Second-factor"
createSubflow "$REALM_NAME" "$TOP_LEVEL_FLOW_NAME" "$PWD_AND_2FA_SUBFLOW_NAME" "$SECOND_FACTOR_SUBFLOW_NAME" CONDITIONAL
#
createExecution "$REALM_NAME" "$SECOND_FACTOR_SUBFLOW_NAME" conditional-user-configured REQUIRED
# SecurityKey 2FA
createExecution "$REALM_NAME" "$SECOND_FACTOR_SUBFLOW_NAME" webauthn-authenticator ALTERNATIVE
# OTP 2FA
createExecution "$REALM_NAME" "$SECOND_FACTOR_SUBFLOW_NAME" auth-otp-form ALTERNATIVE
# bindings: set the new flow for the browser flow
$KCADM update realms/$REALM_NAME -s browserFlow=Browser-Webauthn
# required actions
registerRequiredAction $REALM_NAME "webauthn-register" "Webauthn Register"
registerRequiredAction $REALM_NAME "webauthn-register-passwordless" "Webauthn Register Passwordless"
# every new user gets this required action attached by default
$KCADM update authentication/required-actions/webauthn-register-passwordless -r $REALM_NAME -s defaultAction=true
# policy
$KCADM update realms/$REALM_NAME -s webAuthnPolicyPasswordlessUserVerificationRequirement=required
#############
# users
# user with username/password, a security key must be configured upon first login because of the webauthn-register-passwordless required-action
USER_ID=$(createUser $REALM_NAME $USER_NAME)
$KCADM set-password -r $REALM_NAME --username $USER_NAME --new-password $USER_NAME
$KCADM update users/$USER_ID -r $REALM_NAME -s firstName="My" -s lastName="User" -s email="my.user@email.com"
Die Variablen werden gesetzt damit die Konfiguration durch die Änderung dieser leicht angepasst werden kann.
KC_VERSION=26.1.4
KC_NAME=keycloak-"${KC_VERSION}"
HOST_FOR_KCADM=localhost
KCADM=./bin/kcadm.sh
REALM_NAME=water
CLIENT_ID=drop
USER_NAME=user1
Um die Keycloak Admin CLI nun zu verwendung muss man sich zuerst beim Keycloak anmelden.
USER=admin
PASSWORD=admin
$KCADM config credentials --server http://$HOST_FOR_KCADM:8080 --user ${USER} --password ${PASSWORD} --realm master
Erstellung eines Clients für die Verwendung.
# clients
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=["*"]'
Für dieses Projekt erstellen wir einen neuen realm mit einer Funktion die von Keycloak angeboten wird.
createRealm $REALM_NAME
Das Herzstück dieses Tutorials liegt in der neuen Definierung des Browser-Flows, um Passkeys zur Anmeldung verwenden zu können.
$KCADM update realms/$REALM_NAME -s browserFlow=browser
TOP_LEVEL_FLOW_NAME=Browser-Webauthn
deleteTopLevelFlow $REALM_NAME $TOP_LEVEL_FLOW_NAME
createTopLevelFlow $REALM_NAME $TOP_LEVEL_FLOW_NAME
Die einzelnen Schritte müssen angegeben werden, wobei auch wieder Funktion von Keycloak verwendet werden.
# Cookie
createExecution $REALM_NAME $TOP_LEVEL_FLOW_NAME auth-cookie ALTERNATIVE
# create subflow for all user interactions
FORMS_SUBFLOW_NAME=Forms
createSubflow $REALM_NAME $TOP_LEVEL_FLOW_NAME $TOP_LEVEL_FLOW_NAME $FORMS_SUBFLOW_NAME ALTERNATIVE
# Username
createExecution $REALM_NAME $FORMS_SUBFLOW_NAME auth-username-form REQUIRED
# create subflow
PWD_OR_2FA_SUBFLOW_NAME="Passwordless_Or_Two-factors"
createSubflow $REALM_NAME "$TOP_LEVEL_FLOW_NAME" $FORMS_SUBFLOW_NAME "$PWD_OR_2FA_SUBFLOW_NAME" REQUIRED
# Passwordless
createExecution "$REALM_NAME" "$PWD_OR_2FA_SUBFLOW_NAME" webauthn-authenticator-passwordless ALTERNATIVE
# create subflow
PWD_AND_2FA_SUBFLOW_NAME="Password_And_Second-factor"
createSubflow "$REALM_NAME" "$TOP_LEVEL_FLOW_NAME" "$PWD_OR_2FA_SUBFLOW_NAME" "$PWD_AND_2FA_SUBFLOW_NAME" ALTERNATIVE
# Password
createExecution "$REALM_NAME" "$PWD_AND_2FA_SUBFLOW_NAME" auth-password-form REQUIRED
# create subflow 2FA
SECOND_FACTOR_SUBFLOW_NAME="Second-factor"
createSubflow "$REALM_NAME" "$TOP_LEVEL_FLOW_NAME" "$PWD_AND_2FA_SUBFLOW_NAME" "$SECOND_FACTOR_SUBFLOW_NAME" CONDITIONAL
#
createExecution "$REALM_NAME" "$SECOND_FACTOR_SUBFLOW_NAME" conditional-user-configured REQUIRED
# SecurityKey 2FA
createExecution "$REALM_NAME" "$SECOND_FACTOR_SUBFLOW_NAME" webauthn-authenticator ALTERNATIVE
# OTP 2FA
createExecution "$REALM_NAME" "$SECOND_FACTOR_SUBFLOW_NAME" auth-otp-form ALTERNATIVE
Nun kann der erstellte Flow verwendet werden. Required Actions
$KCADM update realms/$REALM_NAME -s browserFlow=Browser-Webauthn
registerRequiredAction $REALM_NAME "webauthn-register" "Webauthn Register"
registerRequiredAction $REALM_NAME "webauthn-register-passwordless" "Webauthn Register Passwordless"

Es kann auch ein User mit der Keycloak Admin CLI erstellt werden.
USER_ID=$(createUser $REALM_NAME $USER_NAME)
$KCADM set-password -r $REALM_NAME --username $USER_NAME --new-password $USER_NAME
$KCADM update users/$USER_ID -r $REALM_NAME -s firstName="My" -s lastName="User" -s email="my.user@email.com"
Um diesen Tutorial zu folgen können nun die beiden Shell-Scripts ausgeführt werden.
./kc-init.sh
./kc-setup.sh

