Skip to main content

Authentication Flow Setup

Wire the Email OTP authenticator into a Keycloak authentication flow and configure realm email settings.

Step 1 — Configure Realm Email (SMTP)​

  1. Login as admin to your Keycloak installation
  2. Switch to your target realm
  3. Navigate to Realm Settings → Email tab
  4. Fill in your SMTP server details:
FieldExample
SMTP Hostsmtp.gmail.com
SMTP Port587
From Emailnoreply@yourdomain.com
Enable StarTLS✅
Username / Passwordyour credentials

See Email Provider Configuration for more details on these settings.

Step 2 — Create the Authentication Flow​

a) Copy the Browser Flow​

  1. Navigate to Authentication
  2. Inside the Flows tab, locate the Browser flow
  3. Click the "⋮" (overflow) menu
  4. Select "Duplicate"

Keycloak Authentication Flows list — duplicate the Browser flow

  1. Name it "Browser with Email OTP"
  2. Click "Duplicate" to create the new flow

Keycloak Authentication Flows list — name the copy of Browser flow

b) Add the OTP Execution​

Inside the copied flow, locate the "Browser with Email OTP Forms - Conditional 2FA" sub-flow. This is where the OTP step lives alongside username/password.

  1. Click "+" Add on the right side of the Forms row
  2. Click "Add execution"

Add execution button inside the Forms sub-flow

Choose your execution type depending on who should get the OTP step:

ExecutionWho is asked for a codeShown under "Try Another Way""Condition - User Configured"
Email OTPUsers who have enrolled the email authenticatorOnly to enrolled usersTrue only for enrolled users
Email OTP (No Enrollment)Every user with an email address, no enrollmentTo every user, including those who never enrolledTrue for every user with an email
Conditional Email OTPEnrolled users, filtered by role / attribute / headerOnly to enrolled usersTrue only for enrolled users

:::info Why two "Email OTP" executions? Keycloak decides whether an authenticator is tied to a stored credential from its type, not from its settings. A credential-based authenticator is only offered under "Try Another Way" to users who already have that credential. Email OTP follows Keycloak's convention for built-in second factors. Email OTP (No Enrollment) is the variant for realms where having an email address is enough. :::

Use this when: you want OTP enforced for every user, every login — no conditions.

  1. Search for and select "Email OTP"
  2. Click "Add"

Selecting Email OTP from the execution list

  1. On the "Email OTP" row, set the requirement:
RequirementBehaviour
REQUIREDEvery login always prompts for an OTP code
ALTERNATIVEOTP is offered but skipped if another factor already satisfies the flow
DISABLEDOTP step is never triggered

Setting the requirement on the Email OTP row

tip

Use REQUIRED for the strictest security. Use ALTERNATIVE only if you have other authenticators in the same flow that can substitute for OTP.

c) Bind the Flow​

  1. At the top right of the flow editor, click the "Actions" menu
  2. Select "Bind flow"

Binding the new flow to the Browser flow

  1. Set Binding type to Browser flow
  2. Click "Save"

Binding the new flow to the Browser flow

After binding, the new flow will replace the default Browser flow for all browser-based logins.

Binding the new flow to the Browser flow

Resulting Flow Structure​

After completing the above steps, when users log in through the browser, after successfully entering their username and password, they will be prompted to enter an OTP code sent to their email (if they have one configured and the requirement is met).

Completed authentication flow with Email OTP step

Step 3 — Configure Email OTP Settings​

Click the ⚙️ Settings icon on the "Email OTP" row to open the authenticator configuration panel.

Email OTP settings panel

Settings reference​

SettingDescriptionDefaultRecommended (prod)
Code lengthNumber of digits in the OTP code66
Time-to-liveSeconds before the code expires300300
Simulation modeLog codes to server logs instead of sending emailsfalsefalse
Resend cooldownSeconds a user must wait before requesting a new code3060
Max code attemptsWrong attempts allowed before the code is invalidated53
Show masked email on OTP formShow a masked version of the destination email on the OTP login and enrollment screensfalsetrue
Auto-enrol users whose email is already verifiedEnrol users with a verified email without sending a setup code (Email OTP and Conditional Email OTP only)falsedepends on how emailVerified is set in your realm

Setting details​

Code length​

Controls how many digits are in the OTP. 6 is the industry standard and what users expect. Increasing to 8 adds marginal security at the cost of more user errors.

Time-to-live​

How long (in seconds) the code remains valid after being sent. 300 seconds (5 minutes) is a good balance between security and usability. If your users report codes expiring too quickly (e.g. they check email on a slow device), increase to 600.

Simulation mode​

When true, no email is sent — the OTP code is written to the Keycloak server log instead. Use this during development and local testing to avoid needing a real mail server.

docker logs keycloak | grep "SIMULATION MODE"
# ***** SIMULATION MODE ***** Email code send to test@example.com for user testuser is: 123456
warning

Never enable simulation mode in production — codes are visible in plain text in the logs.

Resend cooldown​

Prevents users from spamming the "Resend code" button. The default of 30 seconds is fine for development; increase to 60 in production to reduce email abuse.

Max code attempts​

After this many wrong attempts, the current code is invalidated and the user must request a new one. Lower values (e.g. 3) make brute-forcing harder but may frustrate users who mistype frequently.

This limit applies whether or not the realm has Brute Force Detection enabled. When it is enabled, each wrong code also counts towards the realm's lockout (reported to Keycloak as an otp failure), so the limit stops guessing against one code and the lockout stops an attacker who keeps requesting new ones. On Keycloak 26.5 and earlier a wrong code already counts towards the lockout; from 26.6 on Keycloak ignores failures outside its own categories, so this extension reports them explicitly. A correct code is accepted as an otp success, which also clears the realm's secondary-factor counter (Max secondary auth failures), so occasional typos do not add up to a permanent lockout.

Show masked email on OTP form​

When enabled, the authenticator injects a server-generated maskedEmail attribute into the OTP form and displays it to the user, for example u***e@example.com. This improves UX by confirming where the code was sent without exposing the full address.

This applies both to the OTP login form and to the verification step of enrollment, so the user knows which mailbox to check in either case.

This setting is intentionally implemented server-side. It does not depend on ${user.email!} being present in the FreeMarker login template context.

Upgrading from "Skip setup"​

Earlier releases had a Skip setup option ("Treat any user with an email as configured"). It has been replaced by the Email OTP (No Enrollment) execution, because a setting cannot change how Keycloak lists an authenticator under "Try Another Way".

On first startup after the upgrade, the extension migrates existing realms automatically:

  • Skip setup applied to the whole realm: enabling it on any Email OTP or Conditional Email OTP execution affected every one of them. So if it was enabled anywhere in a realm, every Email OTP execution in that realm is switched to Email OTP (No Enrollment), keeping its other settings. Users who never enrolled still get a code.
  • Conditional Email OTP executions in such a realm cannot be switched. The server log shows a WARN naming the realm and flow, only on that first startup. From now on, these executions only ask enrolled users for a code. Ask the remaining users to enroll, or redesign the flow around Email OTP (No Enrollment).
  • The obsolete setting is removed from every Email OTP configuration.

:::warning Behaviour change for users without an email address Previously, a REQUIRED Email OTP step with Skip setup enabled let users without an email address log in without any OTP. A REQUIRED Email OTP (No Enrollment) step blocks them instead, with Keycloak's "credential setup required" error. Make sure every user who goes through that step has an email address. :::

Check the server log after upgrading:

docker logs keycloak | grep SkipSetupMigration

What's Next?​