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
