Skip to main content
Version: v0.5

Email and Phone Verification and Account Activation


Please read the Self-Service Flows overview before continuing with this document.

ORY Kratos allows users to verify their out-of-band (email, telephone number, ...) communication channels. Verification can be initiated

  • after registration or by performing a verification flow;
  • manually by the user.

There are two Verification Flow types supported in ORY Kratos:

  • Flows where the user sits in front of the Browser (e.g. website, single page app, ...)
  • Flows where API interaction is required (e.g. mobile app, Smart TV, ...)

The Verification Flow can be summarized as the following state machine:

To enable verification flows, make the following adjustments to your ORY Kratos configuration:

selfservice:  methods:    link:      enabled: true  flows:    verification:      enabled: true

Account Activation#

Using this feature implements the so-called "account activation" with the difference that ORY Kratos does not provide a feature that prevents signing into accounts without verified addresses. The reason being that verification is proving that the user controls the given address, but it is not an authentication mechanism.

You may however choose to limit what an identity without verified addresses is able to do in your application logic or API Gateways.

Verification Methods#

Currently, ORY Kratos only supports one verification method:

  • The link method performs verification of email addresses.

Verification link Method#

The link method is dis/enabled in the ORY Kratos config:

selfservice:  methods:    link:      enabled: true      # ...

Enabling this method will send out verification emails on new sign ups and when a verified email address is updated.

Before sending out a verification E-Mail, ORY Kratos will check if the email address is already known. Depending on the result, one of the two flows will be executed:

  • Unknown email address: An email is sent to the address informing the recipient that someone tried to verify this email address but that it is not known by the system:Verification email for unknown address
  • Known email address: An email which includes a verification link is sent to the address:Verification email for known address

This prevents Account Enumeration Attacks as it is not possible for a threat agent to determine if an account exists or not based on the verification flow.

The emails are using templates that can be customised as explained in Customizing E-Mail Templates. The template IDs are:

  • Unknown email address: verification_invalid
  • Known email address: verification_valid

You must define at least one Identity Traits field as a verification field. You can do so by defining the following section in your Identity JSON Schema:

 {   "$id": "",   "$schema": "",   "title": "Person",   "type": "object",   "properties": {     "traits": {       "type": "object",       "properties": {         "email": {           "type": "string",           "format": "email",           "": {             "credentials": {               "password": {                 "identifier": true               }             },+            "verification": {+              "via": "email"+            }           }         }       }       "additionalProperties": false     }   } }

You can also combine this with the password method login identifier (see example above). That way, the field email (or any field you define with these properties) will serve as both the login identifier and as a verifiable email address.

Initialize Verification Flow to Request or Resend Verification Challenge#

The first step is to initialize the Verification Flow. This sets up Anti-CSRF tokens and more. Each verification flow has a state parameter which follows the state machine:


  • choose_method indicates that the user has not chosen a verification method yet. This is useful when link is not the only verification method active.
  • sent_email implies that the verification email has been sent out.
  • passed_challenge is set when the user has clicked the verification link and completed the account verification.

Verification for Browser Clients#

The Verification Flow for browser clients relies on HTTP redirects between ORY Kratos, your Verification UI, and the end-user's browser:

The Flow UI (your application!) is responsible for rendering the actual Login and Registration HTML Forms. You can of course implement one app for rendering all the Login, Registration, ... screens, and another app (think "Service Oriented Architecture", "Micro-Services" or "Service Mesh") is responsible for rendering your Dashboards, Management Screens, and so on.

To initialize the Verification Flow, point the Browser to the initialization endpoint:

curl -s -v -X GET \  -H "Accept: text/html"  \
> GET /self-service/verification/browser HTTP/1.1> Host:> User-Agent: curl/7.64.1> Accept: text/html>< HTTP/1.1 302 Found< Cache-Control: 0< Content-Type: text/html; charset=utf-8< Location:< Set-Cookie: csrf_token=y4Ocu6V83BapwJwbPw/pnlRHHw40DZbjq5iuDrxl0Ds=; Path=/; Domain=; Max-Age=31536000; HttpOnly<<a href="">Found</a>.

The server responds with a HTTP 302 redirect to the Verification UI, appending the ?flow=<flow-id> query parameter (see the curl example) to the URL configured here:

selfservice:  flows:    verification:      # becomes      ui_url:

Verification for API Clients#


Never use API flows to implement Browser applications! Using API flows in Single-Page-Apps as well as server-side apps opens up several potential attack vectors, including Login and other CSRF attacks.

The Verification Flow for API clients does not use HTTP Redirects and can be summarized as follows:

To initialize the API flow, the client calls the API-flow initialization endpoint (REST API Reference) which returns a JSON response:

$ curl -s -X GET \  -H "Accept: application/json"  \ | jq
{  "id": "4fd52be5-5f80-4493-ab5c-90cb9bd331d2",  "type": "api",  "expires_at": "2020-09-10T07:39:10.9503284Z",  "issued_at": "2020-09-10T06:39:10.9503284Z",  "request_url": "",  "methods": {    // ...  },  "state": "choose_method"}

Verification Flow Payloads#

Fetching the Verification Flow (REST API Reference) is usually only required for browser clients but also works for Verification Flows initialized by API clients. All you need is a valid flow ID:

$ curl -H "Accept: application/json" -s \    '' | jq
{  "id": "4fd52be5-5f80-4493-ab5c-90cb9bd331d2",  "type": "api",  "expires_at": "2020-09-10T07:39:10.9503284Z",  "issued_at": "2020-09-10T06:39:10.9503284Z",  "request_url": "",  "messages": null,  "methods": {    // ...  },  "state": "choose_method"}

Send Verification Link to Email#


The link verification mode will always open a link in the browser, even if it was initiated by an API client. This is because the user clicks the link in his/her email client, which opens the browser.

When the link method is enabled, it will be part of the methods payload in the Verification Flow:

$ curl -H "Accept: application/json" -s \    '' | jq -r ''
{  "action": "",  "method": "POST",  "fields": [    {      "name": "csrf_token",      "type": "hidden",      "required": true,      "value": "l6jPm/3mDxYAODgv4SDitfCyfWD3+PH+loc5//E/UDFiJA9P3h6KCNKDyn/OpIrlEjv6Tf4Zaurp7KN/dD07Yg=="    },    {      "name": "email",      "type": "email",      "required": true    }  ]}

Verification Flow Form Rendering#

The Verification User Interface is a route (page / site) in your application (server, native app, single page app) that should render a verification form.

In stark contrast to other Identity Systems, ORY Kratos does not render this HTML. Instead, you need to implement the HTML code in your application (e.g. NodeJS + ExpressJS, Java, PHP, ReactJS, ...), which gives you extreme flexibility and customizability in your user interface flows and designs.

You will use the Verification Flow JSON response to render the verification form UI, which could looks as follows depending on your programming language and web framework:

Email Verification HTML Form

Verification Form Validation#

The form payloads are then submitted to ORY Kratos which follows up with:

  • An HTTP 302 Found redirect pointing to the Verification UI for Browser Clients.
  • An application/json response for API Clients.

Verification Link via Email#

To send the verification email, the end-user fills out the form. There might be validation errors such as a malformed email:

Email Verification and Account Activation HTML Form with validation errors

When validation errors happen, browser clients receive a HTTP 302 Found redirect to the Verification Flow UI, containing the Verification Flow ID which includes the error payloads.

For API Clients, the server typically responds with HTTP 400 Bad Request and the Verification Flow in the response payload as JSON.

Successful Submission#

On successful submission, an email will be sent to the provided address:

Email Verification and Account Activation HTML Form with success message

Unsuccessful Verification#

If the verification challenge (e.g. the link in the verification email) is invalid or expired, the user will be HTTP 302 redirected to the Verification UI.


When an invalid or expired challenge is used, ORY Kratos initializes a new Account Verification flow automatically. This flow will always be a Browser-based flow because the challenge is completed by clicking a link!

The new Verification Flow includes an error message such as the following:

Email Verification and Account Activation HTML Form with an invalid challenge

Please keep in mind that this part of the flow always involves a Browser!

Successful Verification#

If the verification challenge is completed (e.g. the sent verification link was clicked), the end-user's Browser is redirected to the Verification UI with a Verification Flow that has now the state of passed_challenge:

Email Verification and Account Activation HTML Form with an invalid challenge

You may also configure a redirect URL instead which would send the end-user to that configured URL.