Skip to main content

Add Two Factor Authentication (2FA) to your App

Ory Kratos supports two-factor authentication. Depending on the devices used, you might want to call this two-factor verification depending on which features you have enabled. Learn more about the distinction between two-factor authentication and two-factor verification in this excellent 1password blog post!

There are four key components available in Ory Kratos with regard to multi-factor authentication. For this guide, we will assume that you are using Ory Cloud Managed UI available at github.com/ory/kratos-selfservice-ui-node. At the end of this guide you will find details and ideas on how to implement your own UI with Ory Kratos' MFA features.

Terminology

Before we start let's establish the key terminology to make the following sections easier to understand!

Authenticator Assurance Level (AAL)

The Authenticator Assurance Level (AAL) has two possible values:

  • aal1: Implies that the identity has completed only one authentication factor (password, oidc).
  • aal2: Implies that the identity has completed the first (password, oidc) and the second (totp, lookup_secrets, webauthn) authentication factor (for example: password + totp, oidc + webauthn, ...). First authentication factors can't be combined to gain aal2 (for example password + oidc = aal1, not aal2)!

Authentication Method Reference (AMR)

The Authentication Method Reference (AMR) is an array of authentication methods that were used over the lifetime of an Ory Session. In an Ory Session, this will have the following layout:

Example Ory Session JSON Payload
{
id: '6b51a3f2-6a2c-4557-90a8-4e23de7072aa',
active: true,
// ...
authenticator_assurance_level: 'aal2',
authentication_methods: [
{
method: 'password',
completed_at: '2021-10-14T09:37:53.872104Z'
},
{
method: 'lookup_secret',
completed_at: '2021-10-14T09:41:16.771859Z'
}
]
// ...
}

The methods can be one of password, oidc, totp, webauthn, lookup_secrets. A method can be included more than once, for example when the identity refreshes their Ory Session by re-authenticating:

Example Ory Session JSON Payload
{
id: '6b51a3f2-6a2c-4557-90a8-4e23de7072aa',
active: true,
// ...
authenticator_assurance_level: 'aal2',
authentication_methods: [
{
method: 'password',
completed_at: '2021-10-14T09:37:53.872104Z'
},
{
method: 'lookup_secret',
completed_at: '2021-10-14T09:41:16.771859Z'
},
{
method: 'password',
completed_at: '2021-10-14T12:00:00.134567Z'
}
]
// ...
}

Strict and Lax Multi-Factor Authentication

Before jumping into the concrete MFA modules, we take a lok at "Soft Multi-Factor Authentication". Ory Kratos has two endpoints which require an Ory Session Token or Ory Session Cookie to function:

When you enable one of the 2FA methods, you can configure when an Ory Session Token or Ory Session Cookie is considered "valid" for these two endpoints:

kratos.config.yml
# ...
selfservice:
flows:
settings:
required_aal: aal1
# ...
session:
whoami:
required_aal: aal1
# ...

The field required_aal can be one of:

  • highest_available (default): If set, requires identities who have set up a second factor (for example totp, webauthn) to have an Ory Session where both factors (for example password + totp) have been used to authenticated.
  • aal1: Even if an identity has a second factor set up, an Ory Session with only one factor (for example oidc, password) is enough to access it.

For example, if you want your users to sign in without forcing the second factor, you could set:

kratos.config.yml
# ...
session:
whoami:
required_aal: aal1
# ...

When the user is doing something that needs more security (for example a bank transfer), you could add a check which only allows Ory Sessions that have aal2 to access that feature.

The Ory Session, which you can get by calling the /sessions/whoami endpoint, contains the session's authenticator_assurance_level. For a session which only completed the first factor, this would be:

GET /session/whoami
{
id: '6b51a3f2-6a2c-4557-90a8-4e23de7072aa',
active: true,
// ...
authenticated_at: '2021-10-14T09:37:53.877216Z',
authenticator_assurance_level: 'aal1'
// ...
}

A session that has completed the second factor, this would be:

GET /session/whoami
{
id: '6b51a3f2-6a2c-4557-90a8-4e23de7072aa',
active: true,
// ...
authenticated_at: '2021-10-14T09:37:53.877216Z',
authenticator_assurance_level: 'aal2'
// ...
}

If highest_available is configured for the /session/whoami endpoint, then Ory Kratos will instruct the user interface - if it's a browser flow - to redirect to the 2FA screen after the end-user signs in with their first factor.

Requesting Second Factor Authentication

When an end-user is signed in you may prompt them to provide their second factor by initiating a new Login using the /self-service/login/browser or /self-service/login/api API and setting the aal parameter to aal2. Once the end-user has provided their second factor, the method (for example totp) will be added to the Or Session AMR, the Ory Session AAL will be set to aal2, and authenticated_at will be set to the current time.

/self-service/login/browser?aal=aal2
/self-service/login/api?aal=aal2

If the Ory Session has aal2 already, this will error. In that case you can request to refresh the session using the second factor:

/self-service/login/browser?refresh=true&aal=aal2
/self-service/login/api?refresh=true&aal=aal2

Time-Based One-Time Password (TOTP) / Authenticator App

Time-Based One-Time Password (TOTP) is a standardized algorithm (see RFC6238) that's used by apps supported by apps like Google Authenticator (iOS, Android), 1Password, Bitwarden, and many others.

Google Authenticator App

You can enable TOTP in your Ory Kratos config:

kratos.config.yml
selfservice:
methods:
totp:
enabled: true
config:
# The issuer (for example a domain name) will be shown in the TOTP app (for example Google Authenticator). It helps the user differentiate between different codes.
issuer: Example.com

To help the user identify the correct code in their TOTP authenticator app, you should set the issuer (see code example above) to your brand name or domain name. However, users might have multiple identities registered in your system. To help them distinguish between them, you can specify a traits in your Identity Schema which should be the TOTP account name (in the screenshot above alice@example.org):

identity.schema.json
{
$schema: 'http://json-schema.org/draft-07/schema#',
type: 'object',
properties: {
traits: {
type: 'object',
properties: {
email: {
type: 'string',
format: 'email',
title: 'Your E-Mail',
minLength: 3,
'ory.sh/kratos': {
credentials: {
// ...
+ totp: {
+ account_name: true
+ }
}
// ...
}
}
// ...
}
// ...
}
}
}

In the settings UI, the user will be presented with a QR code, a secret which can be used instead of the QR code, and a field to enter a TOTP password generated by the linked TOTP app.

Google Authenticator App

To complete the process, the Ory Session must be privileged. Once set up, the end-user can unlink the TOTP device if for example it's lost:

Google Authenticator App

Writing E2E Tests for TOTP

If you wish to write TOTP tests, take a look at how we solve E2E tests for TOTP.

Lookup Secrets

Lookup secrets are passwords generated by the server. These passwords can only be used once, and the end-user typically downloads them, stores them in their password manager, or writes them down. Lookup secrets are commonly used when the end-user loses access to their TOTP or WebAuthn device!

On your settings UI, this method could look as follows. First, the end-user generates new lookup secrets.

Generating new recovery codes

New recovery codes

The end-user must confirm these lookup secrets. This action requires a privileged Ory Session and the user might be prompted to confirm the action by re-authenticating!

Generating new recovery codes

Once confirmed, the end-user can use these lookup secrets as a second factor! When a code was used,

Used recovery code

warning

The end-user must ensure to re-generate lookup secrets before all lookup secrets have been used up!

Lookup secrets are known by several names. Google for example calls them "Backup Codes". Another common name is "2FA Recovery Codes".

You can enable this method in your Ory Kratos config:

kratos.config.yml
selfservice:
methods:
lookup_secret:
enabled: true

FIDO2 / U2F WebAuthn

The Web Authentication Browser API (also known as WebAuthn) is a specification written by the W3C and FIDO. The WebAuthn API allows servers to register and authenticate users using public key cryptography instead of a password. WebAuthn is commonly used with

  • a USB, NFC, or Bluetooth low energy device (e.g. YubiKey) to authenticate;
  • using an Operating System "platform module" (e.g. TouchID, FaceID, Windows Hello Face, Android Biometric Authentication, ...);

Once the end-user triggers the WebAuthn process, the browser will show a WebAuthn prompt which looks different per browser:

WebAuthn Prompt

Ory's WebAuthN implementation can be used for both multi-factor authentication and passwordless authentication. You need to configure whether WebAuthn is used for passwordless, or for multi-factor authentication.

Configuration

WebAuthn needs to be configured and is disabled per default.

info

Once passwordless is set to either true or false, avoid changing it. Doing so may lock some users out of their accounts.

ory patch identity-config <your-project-id> \
--add '/selfservice/methods/webauthn/enabled=true' \
--add '/selfservice/methods/webauthn/config/passwordless=false' \
--add '/selfservice/methods/webauthn/config/rp/display_name="My Display Name"'

(Custom) Identity Schema

All Ory presets have the correct settings for WebAuthn enabled.

If you want to use a custom identity schema, you need to define what field of the identity schema is the primary identifier for WebAuthn. This is used for both multi-factor authentication as well as passwordless:

identity.schema.json
{
$schema: 'http://json-schema.org/draft-07/schema#',
type: 'object',
properties: {
traits: {
type: 'object',
properties: {
email: {
type: 'string',
format: 'email',
title: 'Your E-Mail',
minLength: 3,
'ory.sh/kratos': {
credentials: {
// ...
webauthn: {
identifier: true
}
}
// ...
}
}
// ...
}
// ...
}
}
}

Writing E2E Tests

You will need a browser to run E2E tests using WebAuthn. Take a look at our E2E test for WebAuthn for Cypress. You can find more information about the approach for Cypress in cypress#6991.

WebAuthN Constraints

There are some limitations to WebAuthN to be considered in development:

  • WebAuthN is a Browser standard. It does not work on native mobile apps.
  • WebAuthN is limited to one domain and does not work in a local environment when using CNAME / Ory Proxy. WebAuthN uses an https://origin URL as part of the client<->server challenge/response mechanism. This mechanism allows for only one URL as the origin. Read more in the WebAuthN guide and on GitHub.
  • Implementing WebAuthN in your own UI can be challenging, depending on which framework to use. Please check our reference implementations to see how we solved it for different app types (web, single page app).

Build Your Own UI

The major benefit of Ory Kratos is that you can bring your own login, registration, account recovery, ... user-interface. You can write that user-interface in any language or framework! We've reference UI implementations available to help you get started!