Email and phone verification and account activation
Please read the Self-Service Flows overview before continuing with this document.
Ory Identities 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 Identities:
- Flows where the user sits in front of the Browser and the application is
- a server-side application (Node.js, Java, ...)
- a client-side application (React.js, Angular, ...)
- Flows where API interaction is required (Mobile app, Smart TV, ...)
The Verification Flow can be summarized as the following state machine:
- The Ory Network
- Ory Identities
Account verification can be configured in the Customize/Email Verification
screen of Ory Console.
To enable verification flows, make the following adjustments to your Ory Identities configuration:
selfservice:
methods:
link:
enabled: true
config:
# Defines how long the verification or the recovery link is valid for (default 1h)
lifespan: 15m
# If the link should point to a domain (and path) that differs from the configured public base URL,
# set this value to the base URL you want:
base_url: https://my-example-domain.com
flows:
verification:
enabled: true
# Defines how long the verification flow (the UI interaction, not the link!)
# is valid for (default 1h)
lifespan: 15m
Account activation
Using this feature implements the so-called "account activation". Depending on your scenario or use cases, you may either choose
to limit what an identity without verified addresses is able to do in your application logic or API Gateways, or make Ory
Identities prevent signing into accounts without verified addresses. Latter is possible by the usage of require_verified_address
hook (See Hooks for more details).
Please be aware, that since require_verified_address
hook is enforcing a verified address before the user can login, a typo in
an email address done by the user either during the registration or as part of a self-service flow (email change) will make the
login for that user impossible. So you should think about measures to prevent such situations, like requiring two email addresses
being configured by the user, thus having a backup if something goes wrong.
Verification methods
- The
link
method performs verification of email addresses.
Verification link
method
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 Identities 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 isn't known by the system:
- Known email address: An email which includes a verification link is sent to the address:
This prevents Account Enumeration Attacks as it isn't 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 customized 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 Schema:
{
"$id": "https://schemas.ory.sh/presets/kratos/quickstart/email-password/identity.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Person",
"type": "object",
"properties": {
"traits": {
"type": "object",
"properties": {
"email": {
"type": "string",
"format": "email",
"ory.sh/kratos": {
"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
severity
Ory Kratos and your UI must be on the hosted on same top level domain! You can not host Ory Kratos and your UI on separate top level domains:
kratos.bar.com
andui.bar.com
will work;kratos.bar.com
andbar.com
will work;kratos.bar.com
andnot-ar.com
will not work.
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:
where
choose_method
indicates that the user hasn't chosen a verification method yet. This is useful whenlink
isn't 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 server-side browser clients
The Verification Flow for browser clients relies on HTTP redirects between Ory Identities, 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 -i -X GET \
-H "Accept: text/html" \
https://playground.projects.oryapis.com/self-service/verification/browser
HTTP/2 303
date: Fri, 09 Jul 2021 13:08:00 GMT
content-type: text/html; charset=utf-8
content-length: 124
location: https://playground.projects.oryapis.com/hosted/verification?flow=ba66958a-5f15-4b8f-88f9-52b35020db5c
cache-control: private, no-cache, no-store, must-revalidate
set-cookie: aHR0cHM6Ly9wbGF5Z3JvdW5kLnByb2plY3RzLm9yeWFwaXMuY29tL2FwaS9rcmF0b3MvcHVibGlj_csrf_token=VZDY6ArI9oL3QYTJgYOJWSavAmAp0qcJYLSFKGXDd3U=; Path=/api/kratos/public; Domain=playground.projects.oryapis.com; Max-Age=31536000; HttpOnly; Secure; SameSite=None
vary: Origin
vary: Cookie
strict-transport-security: max-age=15724800; includeSubDomains
<a href="https://playground.projects.oryapis.com/hosted/verification?flow=ba66958a-5f15-4b8f-88f9-52b35020db5c">See Other</a>.
The server responds with a HTTP 303 redirect to the Verification UI, appending the ?flow=<flow-id>
query parameter (see the curl
example) to the URL configured here:
- The Ory Network
- Ory Identities
The Ory Network offers a default UI implementation. Visit Bring Your Own UI to learn how to implement a custom UI.
You can configure which recovery URL to use in the Ory Identities config:
selfservice:
flows:
verification:
# becomes http://127.0.0.1:4455/verification?flow=df607aa1-d555-4b2a-b3e4-0f5a1d2fe6f3
ui_url: http://127.0.0.1:4455/verification
Verification for client-side (AJAX) browser clients
The Verification Flow for client-side browser clients relies on AJAX requests.
severity
This flow requires AJAX and you need to ensure that all cookies are sent using the appropriate CORS and includeCredentials
configurations. Additionally, Ory Kratos and your app must be hosted on the same domain.
To initialize the Verification Flow, call the verification initialization endpoint and set Accept: application/json
:
curl -v -s -X GET \
-H "Accept: application/json" \
https://playground.projects.oryapis.com/self-service/verification/browser | jq
> GET /self-service/verification/browser HTTP/2
> Host: playground.projects.oryapis.com
> User-Agent: curl/7.64.1
> Accept: application/json
< HTTP/2 200
< date: Fri, 09 Jul 2021 09:36:34 GMT
< content-type: application/json; charset=utf-8
< content-length: 1241
< cache-control: private, no-cache, no-store, must-revalidate
< set-cookie: aHR0cHM6Ly9wbGF5Z3JvdW5kLnByb2plY3RzLm9yeWFwaXMuY29tL2FwaS9rcmF0b3MvcHVibGlj_csrf_token=wSDoLSdDqNJv2uWVWdv5euaQo9UimCFS1GhXokTLU3o=; Path=/api/kratos/public; Domain=playground.projects.oryapis.com; Max-Age=31536000; HttpOnly; Secure; SameSite=None
< vary: Origin
< vary: Cookie
< strict-transport-security: max-age=15724800; includeSubDomains
{
"id": "bcf93f61-2ecd-48d2-ac18-6d731fead2b6",
"type": "browser",
"expires_at": "2021-06-15T14:38:26.190743Z",
"issued_at": "2021-06-15T14:33:26.190743Z",
"request_url": "https://playground.projects.oryapis.com/self-service/verification/browser",
"ui": {
"action": "http://127.0.0.1:4455/self-service/verification?flow=bcf93f61-2ecd-48d2-ac18-6d731fead2b6",
"method": "POST",
"nodes": [
{
"type": "input",
"group": "default",
"attributes": {
"name": "csrf_token",
"type": "hidden",
"value": "g0pSHhIJGTfOZ+VjShGfPEuGwDQMel7Xpc/DaLVYTSjMxuhLg90UqFKVrWrldezcf1ykF1jud9ZBNcg2rKarQw==",
"required": true,
"disabled": false
},
"messages": [],
"meta": {}
},
{
"type": "input",
"group": "link",
"attributes": {
"name": "email",
"type": "email",
"required": true,
"disabled": false
},
"messages": [],
"meta": {}
},
{
"type": "input",
"group": "link",
"attributes": {
"name": "method",
"type": "submit",
"value": "link",
"disabled": false
},
"messages": [],
"meta": {
"label": {
"id": 1070005,
"text": "Submit",
"type": "info"
}
}
}
]
},
"state": "choose_method"
}
Verification for API clients
warning
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 doesn't 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" \
https://playground.projects.oryapis.com/self-service/verification/api | jq
{
"id": "535faa25-d617-42ff-b85c-c0d04fd87c44",
"type": "api",
"expires_at": "2021-07-09T14:11:07.30270945Z",
"issued_at": "2021-07-09T13:11:07.30270945Z",
"request_url": "http://playground.projects.oryapis.com/self-service/verification/api",
"ui": {
"action": "https://playground.projects.oryapis.com/self-service/verification?flow=535faa25-d617-42ff-b85c-c0d04fd87c44",
"method": "POST",
"nodes": [
{
"type": "input",
"group": "default",
"attributes": {
"name": "csrf_token",
"type": "hidden",
"value": "",
"required": true,
"disabled": false
},
"messages": [],
"meta": {}
},
{
"type": "input",
"group": "link",
"attributes": {
"name": "email",
"type": "email",
"required": true,
"disabled": false
},
"messages": [],
"meta": {}
},
{
"type": "input",
"group": "link",
"attributes": {
"name": "method",
"type": "submit",
"value": "link",
"disabled": false
},
"messages": [],
"meta": {
"label": {
"id": 1070005,
"text": "Submit",
"type": "info"
}
}
}
]
},
"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:
flowId=$(curl -s -X GET \
-H "Accept: application/json" \
https://playground.projects.oryapis.com/self-service/verification/api | jq -r '.id')
curl -s -X GET \
-H "Accept: application/json" \
"https://playground.projects.oryapis.com/self-service/verification/flows?id=$flowId" | jq
{
"id": "979d00d7-4d45-4960-8566-730e37dc0a3e",
"type": "api",
"expires_at": "2021-07-09T14:11:53.197917Z",
"issued_at": "2021-07-09T13:11:53.197917Z",
"request_url": "http://playground.projects.oryapis.com/self-service/verification/api",
"ui": {
"action": "https://playground.projects.oryapis.com/self-service/verification?flow=979d00d7-4d45-4960-8566-730e37dc0a3e",
"method": "POST",
"nodes": [ /* ... */ ]
},
"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 \
'https://playground.projects.oryapis.com/self-service/verification/flows?id=956c0499-17a9-4a99-9602-9282d1632f7b' | \
jq -r '.ui.nodes[] | select(.group=="link")'
{
"type": "input",
"group": "link",
"attributes": {
"name": "email",
"type": "email",
"required": true,
"disabled": false
},
"messages": null,
"meta": {}
}
{
"type": "input",
"group": "link",
"attributes": {
"name": "method",
"type": "submit",
"value": "link",
"disabled": false
},
"messages": null,
"meta": {
"label": {
"id": 1070005,
"text": "Submit",
"type": "info"
}
}
}
Verification form validation
The form payloads are then submitted to Ory Identities which follows up with:
- An HTTP 303 See Other redirect pointing to the Registration UI for Browser Clients;
- An
application/json
response for API Clients and Client-Side Browser applications (for example Single Page Apps)e Apps).
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:
- Browser UI
- Missing Email
rl -H "Accept: application/json" -s \
'https://playground.projects.oryapis.com/self-service/verification/flows?id=11f6e7f4-99f9-4bcf-bcfc-208d8db942c5' | \
jq
{
"id": "11f6e7f4-99f9-4bcf-bcfc-208d8db942c5",
"type": "browser",
"expires_at": "2021-04-28T13:35:42.627410804Z",
"issued_at": "2021-04-28T12:35:42.627410804Z",
"request_url": "https://playground.projects.oryapis.com/self-service/verification/browser",
"active": "link",
"ui": {
"action": "https://playground.projects.oryapis.com/self-service/verification?flow=11f6e7f4-99f9-4bcf-bcfc-208d8db942c5",
"method": "POST",
"nodes": [
{
"type": "input",
"group": "default",
"attributes": {
"name": "csrf_token",
"type": "hidden",
"value": "UEHaePrAA2H+nDW2BFI6OTQh4LM1sC7rbVPkT3Z0zE/vl/Hnfn0nS8ZOOzAHJtlfzdi/jyEQq2I+IX7nxTJkKA==",
"required": true,
"disabled": false
},
"messages": null,
"meta": {}
},
{
"type": "input",
"group": "link",
"attributes": {
"name": "email",
"type": "email",
"value": "",
"required": true,
"disabled": false
},
"messages": [
{
"id": 4000002,
"text": "Property email is missing.",
"type": "error",
"context": {
"property": "email"
}
}
],
"meta": {}
},
{
"type": "input",
"group": "link",
"attributes": {
"name": "method",
"type": "submit",
"value": "link",
"disabled": false
},
"messages": null,
"meta": {
"label": {
"id": 1070005,
"text": "Submit",
"type": "info"
}
}
}
]
},
"state": "choose_method"
}
When validation errors happen, browser clients receive a HTTP 303 See Other 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:
- Browser UI
- Email Sent
curl -H "Accept: application/json" -s \
'https://playground.projects.oryapis.com/self-service/verification/flows?id=11f6e7f4-99f9-4bcf-bcfc-208d8db942c5' | \
jq
{
"id": "11f6e7f4-99f9-4bcf-bcfc-208d8db942c5",
"type": "browser",
"expires_at": "2021-04-28T13:35:42.627410804Z",
"issued_at": "2021-04-28T12:35:42.627410804Z",
"request_url": "https://playground.projects.oryapis.com/self-service/verification/browser",
"active": "link",
"ui": {
"action": "https://playground.projects.oryapis.com/self-service/verification?flow=11f6e7f4-99f9-4bcf-bcfc-208d8db942c5",
"method": "POST",
"messages": [
{
"id": 1070001,
"text": "An email containing a verification link has been sent to the email address you provided.",
"type": "info",
"context": {}
}
],
"nodes": [
{
"type": "input",
"group": "default",
"attributes": {
"name": "csrf_token",
"type": "hidden",
"value": "HiG0b2WUZpeyATykHlEGKbr/PrkfSIm5tysdMHrZGISh95/w4SlCvYrTMiIdJeVPQwZhhQvoDDDkWYeYyZ+w4w==",
"required": true,
"disabled": false
},
"messages": null,
"meta": {}
},
{
"type": "input",
"group": "link",
"attributes": {
"name": "email",
"type": "email",
"value": "foo@ory.sh",
"required": true,
"disabled": false
},
"messages": null,
"meta": {}
},
{
"type": "input",
"group": "link",
"attributes": {
"name": "method",
"type": "submit",
"value": "link",
"disabled": false
},
"messages": null,
"meta": {
"label": {
"id": 1070005,
"text": "Submit",
"type": "info"
}
}
}
]
},
"state": "sent_email"
}
Unsuccessful verification
If the verification challenge (for example the link in the verification email) is invalid or expired, the user will be HTTP 303 redirected to the Verification UI.
When an invalid or expired challenge is used, Ory Identities 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:
- Browser UI
- Invalid Challenge
rl -H "Accept: application/json" -s \
'http://127.0.0.1:4433/self-service/verification/flows?id=9040cdea-3674-4c3e-8b2d-b35b268c696e' | \
jq
{
"id": "9040cdea-3674-4c3e-8b2d-b35b268c696e",
"type": "browser",
"expires_at": "2021-04-28T13:44:02.334708332Z",
"issued_at": "2021-04-28T12:44:02.334708332Z",
"request_url": "https://playground.projects.oryapis.com/self-service/verification?flow=388891f2-32ed-4c79-a429-7c7fb7a92986&token=123123",
"ui": {
"action": "https://playground.projects.oryapis.com/self-service/verification?flow=9040cdea-3674-4c3e-8b2d-b35b268c696e",
"method": "POST",
"nodes": [
{
"type": "input",
"group": "default",
"attributes": {
"name": "csrf_token",
"type": "hidden",
"value": "n2ny80aMGQXvIK0ywI1Bozo1dzu36mP9uF4/1qItdpt1POGCvI7hU+hH+g+qWxMPXKLV9j2nEfa3vRATGkU/DA==",
"required": true,
"disabled": false
},
"messages": null,
"meta": {}
},
{
"type": "input",
"group": "link",
"attributes": {
"name": "email",
"type": "email",
"required": true,
"disabled": false
},
"messages": null,
"meta": {}
},
{
"type": "input",
"group": "link",
"attributes": {
"name": "method",
"type": "submit",
"value": "link",
"disabled": false
},
"messages": null,
"meta": {
"label": {
"id": 1070005,
"text": "Submit",
"type": "info"
}
}
}
],
"messages": [
{
"id": 4070001,
"text": "The verification token is invalid or has already been used. Please retry the flow.",
"type": "error",
"context": {}
}
]
},
"state": "choose_method"
}
Please keep in mind that this part of the flow always involves a Browser!
Successful verification
If the verification challenge is completed (for example 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
:
- Browser UI
- Success State
curl -H "Accept: application/json" -s \
'http://127.0.0.1:4433/self-service/verification/flows?id=78815659-b7d5-4c28-8327-5c86a5589ed8' | \
jq -r '.state'
passed_challenge
You may also configure a redirect URL instead which would send the end-user to that configured URL.
Code examples for Node.js, React.js, Go, ...
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 Identities doesn't render this HTML. Instead, you need to implement the HTML code in your application (for example Node.js + Express.js, Java, PHP, React.js, ...), 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:
- Browser UI
- Golang (API Flow)
- Express.js
- React.js
- React Native
- Verification View
- Generic Form View
- Example Input Form Element
- Generic Form View
- Example Input Form Element