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:

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.

FIDO2 Building Blocks

WebAuthn/FIDO2 standards enable strong and even passwordless authentication between servers, authenticators and browsers.

Requirements

Steps

  1. Step 1: Enable preview features
  2. Step 2: Configure realm
  3. Step 3: Configure authentication
  4. 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.

Server-Info_Profile

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.

Authentication Flow

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 and ALTERNATIVE 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:

References