Tutorial 3 - Configuring WebAuthn
What are we doing?
We configure a Keycloak instance with a new tutorial_webauthn
realm for the WebAuthn support. This means that we create a new authentication flow Browser-Webauthn
and bind it as browser flow to be used in the new realm. We create a new client myclient
.
We also enable the registration for new users and require the setup of a passwordless security key upon registration.
We activate two new features of Keycloak, which are hidden behind the preview flag:
- the new Account UI called
account2
- the Account API called
account_api
, which is used by the new Account UI
By enabling the new Account UI the users can manage their WebAuthn security keys in their profile.
Of course we do all of the above configurations again in a scripted manner using the Admin CLI of Keycloak. We will reuse a great part of the shell scripts which we have introduced in tutorial 1.
Why are we doing this?
The WebAuthn standard is a universally accepted W3C specification. With WebAuthn, servers can integrate with authenticators such as hardware security keys, smartphones or any device with a trusted platform module via modern browsers for an authentication based on public key cryptography. Databases are no longer attack targets because public keys are useless without the corresponding private keys.
WebAuthn/FIDO2 standards enable strong and even passwordless authentication between servers, authenticators and browsers.
Requirements
- Keycloak Installation as shown in tutorial 1. The following shell variables must be defined:
- KCADM (e.g. $KEYCLOAK_HOME/bin/kcadm.sh)
- HOST_FOR_KCADM (e.g. localhost | host.docker.internal)
- KEYCLOAK_USER (e.g. admin)
- KEYCLOAK_PASSWORD (e.g. admin)
- Browser with WebAuthn support
- Optional hardware security key (from SoloKeys, Yubico, TrustKey, Feitan, and others)
Steps
- Step 1: Enable preview features
- Step 2: Configure realm
- Step 3: Configure authentication
- Step 4: Configure users
We will apply the configuration by the shell scripts with the structure introduced in tutorial 1. This means that the complete configuration is again contained within the five scripts keycloak-configuration.sh, keycloak-configuration-helpers.sh, realm.sh, realm_master.sh and realm_tutorial_webauthn.sh.
The Keycloak testing application can also be used for this tutorial. Only the name of the realm has been changed (tutorial_webauthn
instead of myrealm
).
In the next section will show and explain only the important Admin CLI calls coming mainly from the realm_tutorial_webauthn.sh script. The mentioned scripts contain the complete configuration. To execute the configuration, please just run the keycloak-configuration.sh script.
Step 1: Enable preview features
Keycloak uses profiles to enable or disable specific features. We can enable these features in different ways.
One way is to pass a system property for each feature that you want to enable.
./bin/standalone.sh -Dkeycloak.profile.feature.account2=enabled -Dkeycloak.profile.feature.account_api=enabled
If it is easier for you, you can also create the file profile.properties
and place it the configuration
folder of your runtime mode (standalone/domain). Define a property for the feature (e.g. feature.account2
) with its desired enable/disable value.
The actual enabled features can be checked in the Web Admin Console.
Step 2: Configure realm
All shown CLI calls of this step are coming from the file realm_tutorial_webauthn.sh
(download)
#!/usr/bin/env bash
REALM_NAME='tutorial_webauthn'
echo ""
echo "================================="
echo "setting up realm $REALM_NAME..."
echo "================================="
echo ""
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
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=["*"]'
# set account theme to account2
$KCADM update realms/$REALM_NAME -s accountTheme="keycloak-preview"
#############
# 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_NAME=user1
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="[email protected]"
Please note that most the following excerpts can’t be executed as shown below, because they use the helper bash functions from keycloak-configuration-helpers.sh. They are listed here to provide a step by step explanation.
First we create a new realm tutorial_webauthn
just for this tutorial.
REALM_NAME='tutorial_webauthn'
createRealm $REALM_NAME
We enable the user registration.
# enable user registration
$KCADM update realms/$REALM_NAME -s registrationAllowed=true
If we have enabled the two preview features account2
and account_api
in step 1, we can update the account theme to use the new Account UI.
$KCADM update realms/$REALM_NAME -s accountTheme="keycloak-preview"
We create the client myclient
.
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=["*"]'
With the above client configuration we make sure, that we can use the Keycloak testing application with this tutorial. You only have to change the value of the realm in the app to tutorial_webauthn
.
Step 3: Configure authentication
All shown CLI calls of this step are coming from the file realm_tutorial_webauthn.sh
(download)
#!/usr/bin/env bash
REALM_NAME='tutorial_webauthn'
echo ""
echo "================================="
echo "setting up realm $REALM_NAME..."
echo "================================="
echo ""
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
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=["*"]'
# set account theme to account2
$KCADM update realms/$REALM_NAME -s accountTheme="keycloak-preview"
#############
# 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_NAME=user1
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="[email protected]"
Flows
We configure the following authentication flow for WebAuthn with the CLI calls in this section.
We show the CLI calls for each line of the above flow.
To provide an easier way for idempotence in this script, the best way is to delete a custom flow and set it up again from scratch. To delete a flow, it must not be bound as a currently used flow. Therefore, we set the built-in browser
flow as the browser flow.
$KCADM update realms/$REALM_NAME -s browserFlow=browser
Now we can delete our custom flow and create it again.
TOP_LEVEL_FLOW_NAME=Browser-Webauthn
deleteTopLevelFlow $REALM_NAME $TOP_LEVEL_FLOW_NAME
createTopLevelFlow $REALM_NAME $TOP_LEVEL_FLOW_NAME
Line 1: Cookie Authenticator
The Cookie Authenticator auth-cookie
is not used for the WebAuthn support, but necessary for the single sign on user experience. It is also configured in the built-in browser flow of Keycloak. We set the requirement for this authenticator to ALTERNATIVE
.
The values
auth-cookie
andALTERNATIVE
and are hardcoded in the Keycloak sources. We use the following strategies for finding them: inspect the source code of Keycloak or look at the network traffic during manual configuration within the Web Admin Console.
createExecution $REALM_NAME $TOP_LEVEL_FLOW_NAME auth-cookie ALTERNATIVE
Line 2: Forms (Subflow)
With this subflow the configuration for WebAuthn support begins.
FORMS_SUBFLOW_NAME=Forms
createSubflow $REALM_NAME $TOP_LEVEL_FLOW_NAME $TOP_LEVEL_FLOW_NAME $FORMS_SUBFLOW_NAME ALTERNATIVE
Line 3: Username (Form Authenticator)
As a first interaction with the user the username is requested.
createExecution $REALM_NAME $FORMS_SUBFLOW_NAME auth-username-form REQUIRED
Line 4: Passwordless Or Two-factors (Subflow)
We offer two authentication alternatives (with or without password) with this 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
Line 5: WebAuthn Passwordless Authenticator
This is the authenticator for the WebAuthn passwordless login.
createExecution "$REALM_NAME" "$PWD_OR_2FA_SUBFLOW_NAME" webauthn-authenticator-passwordless ALTERNATIVE
Line 6: Password And Second-factor (Subflow)
This subflow contains the two steps for a strong login.
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
Line 7: Password Form Authenticator
The password is the first step.
createExecution "$REALM_NAME" "$PWD_AND_2FA_SUBFLOW_NAME" auth-password-form REQUIRED
Line 8: Second-factors (Subflow)
This subflow contains the second step.
SECOND_FACTOR_SUBFLOW_NAME="Second-factor"
createSubflow "$REALM_NAME" "$TOP_LEVEL_FLOW_NAME" "$PWD_AND_2FA_SUBFLOW_NAME" "$SECOND_FACTOR_SUBFLOW_NAME" CONDITIONAL
Line 9: Condition User Configured Authenticator
createExecution "$REALM_NAME" "$SECOND_FACTOR_SUBFLOW_NAME" conditional-user-configured REQUIRED
Line 10: WebAuthn Authenticator
Either with a WebAuthn security key.
createExecution "$REALM_NAME" "$SECOND_FACTOR_SUBFLOW_NAME" webauthn-authenticator ALTERNATIVE
Line 11: OTP Form Authenticator
Or with OTP.
createExecution "$REALM_NAME" "$SECOND_FACTOR_SUBFLOW_NAME" auth-otp-form ALTERNATIVE
Bindings
We change the browser flow of this realm to use our new Browser-Webauthn
flow.
$KCADM update realms/$REALM_NAME -s browserFlow=Browser-Webauthn
Required Actions
We register the required actions, so that they can be attached to users.
registerRequiredAction $REALM_NAME "webauthn-register" "Webauthn Register"
registerRequiredAction $REALM_NAME "webauthn-register-passwordless" "Webauthn Register Passwordless"
By setting the registration of WebAuthn passwordless as a default action, we force the setup of a security key by the user.
$KCADM update authentication/required-actions/webauthn-register-passwordless -r $REALM_NAME -s defaultAction=true
Policies
We configure stronger requirements for the security key when the authentication is done passwordless. For this, we require a user verification from the WebAuthn authenticator.
$KCADM update realms/$REALM_NAME -s webAuthnPolicyPasswordlessUserVerificationRequirement=required
Step 4: Configure users
All shown CLI calls of this step are coming from the file realm_tutorial_webauthn.sh
(download)
#!/usr/bin/env bash
REALM_NAME='tutorial_webauthn'
echo ""
echo "================================="
echo "setting up realm $REALM_NAME..."
echo "================================="
echo ""
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
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=["*"]'
# set account theme to account2
$KCADM update realms/$REALM_NAME -s accountTheme="keycloak-preview"
#############
# 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_NAME=user1
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="[email protected]"
For testing purpose we create a first user.
Please note that its always a risk for an user account if it has only 1 configured 2nd factor. In productive environments you should make sure that there is always a fallback 2nd factor setup forced.
User user1
with username/password
USER_NAME=user1
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="[email protected]"
This user will be forced to setup a WebAuthn passwordless security key. Additional users can easily be created on the login page.
Usage
The Keycloak testing application can now be used for testing the above configuration. Only the name of the realm must be changed to tutorial_webauthn
in its UI or by using this link.
Authentication
When the user user1
authenticates for the first time he will be forced to do the WebAuthn registration:
The easiest way to use a WebAuthn security key is with the Chrome browser. No additional hardware is required.
After a successful WebAuthn registration the user can then authenticate with his security key instead of his password. By default the password credential has the highest priority and thus the password dialog is always shown. To authenticate passwordless the user has to click the Try Another Link
link instead of entering his password.
The Try Another Link
link leads to the dialog for choosing a signing in option:
Profile
On the new account UI of Keycloak users may manage multiple ways to sign in:
In the screenshot above the user has configured 3 passwordless security keys (Goldengate, Yubico and Chrome).
In the user management of Keycloak Admin console the ordering of the credentials can be changed. The first listed credential is used as the default for the authentication process. In the following screenshot the passwordless WebAuthn credentials will be used as default for the user user2
: