Skip to main content
Version: v0.7

Account Recovery and Password Reset


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

Account Recovery must be performed if access to an account needs to be recovered. Common use cases include:

  • "Forgot password" flows
  • "Lost MFA device" flows
  • ...

There are two Recovery Flow types supported in Ory Kratos:

  • Flows where the user sits in front of the Browser and the application is
    • a server-side application (e.g. NodeJS, Java, ...)
    • a client-side application (e.g. ReactJS, AngularJS, ...)
  • Flows where API interaction is required (e.g. mobile app, Smart TV, ...)

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

You can configure which methods to use in the Ory Kratos config:
selfservice:  methods:    link:      enabled: true      config:        # Defines how long a recovery link is valid for (default 1h)        lifespan: 15m
  flows:    recovery:      enabled: true
      # Defines how long a recovery flow (the UI interaction, not the link!)      # is valid for (default 1h)      lifespan: 15m      # ...


Currently, one recovery method is supported:

  • The link method performs account recovery (also known as password reset) by sending an email containing a recovery link to the user.

Recovery link Method#

There are two email types sent by this method:

Recovery email sent to unknown address
If the requested email address is an unknown recovery address, an account recovery attempt email is sent to that email address.

Recovery email sent to a known recovery address
If the requested email address is a known recovery address, a recovery link is sent to that email address.

This prevents account enumeration attacks as explained in this brilliant blog post by Troy Hunt.

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

  • Unknown email address: recovery_invalid
  • Known email address: recovery_valid

You should also configure how long a session is privileged. The user will only be able to update his/her password (or any other credential) for the specified amount of time after clicking on the recovery link.

You can configure the privileged session duration in the Ory Kratos config:

selfservice:  flows:    settings:      privileged_session_max_age: 15m

To specify that an identity's trait is a recovery email, use the following Identity Schema:

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

Initialize Recovery Flow#


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:

  • and will work;
  • and will work;
  • and will not work.

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


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

Recovery for Server-Side Browser Clients#

The Recovery Flow for browser clients relies on HTTP redirects between Ory Kratos, your Recovery 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 Recovery Flow, point the Browser to the initialization endpoint:

$ curl -s -i -X GET \    -H "Accept: text/html" \
HTTP/2 302date: Fri, 09 Jul 2021 12:50:38 GMTcontent-type: text/html; charset=utf-8content-length: 120location: private, no-cache, no-store, must-revalidateset-cookie: aHR0cHM6Ly9wbGF5Z3JvdW5kLnByb2plY3RzLm9yeWFwaXMuY29tL2FwaS9rcmF0b3MvcHVibGlj_csrf_token=WCOWjhwHy338874TGz6S7ICVp/hFFVHV8Ev7e1hZYSo=; Path=/api/kratos/public;; Max-Age=31536000; HttpOnly; Secure; SameSite=Nonevary: Originvary: Cookiestrict-transport-security: max-age=15724800; includeSubDomains
<a href="">Found</a>.

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

You can configure which recovery URL to use in the Ory Kratos config:

selfservice:  flows:    recovery:      # becomes      ui_url:

Recovery for Client-Side (AJAX) Browser Clients#

The Recovery Flow for client-side browser clients relies on AJAX requests.


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 Recovery Flow, call the recovery initialization endpoint and set Accept: application/json:

$ curl -v -s -X GET \    -H "Accept: application/json"  \ | jq
> GET /api/kratos/public/self-service/recovery/browser HTTP/2> Host:> 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;; Max-Age=31536000; HttpOnly; Secure; SameSite=None< vary: Origin< vary: Cookie< strict-transport-security: max-age=15724800; includeSubDomains
{  "id": "d37e270d-eb84-472a-8032-3f0f01786210",  "type": "browser",  "expires_at": "2021-06-15T14:27:31.270617Z",  "issued_at": "2021-06-15T14:22:31.270617Z",  "request_url": "",  "ui": {    "action": "",    "method": "POST",    "nodes": [      {        "type": "input",        "group": "default",        "attributes": {          "name": "csrf_token",          "type": "hidden",          "value": "17VNGchueAPEYWji/X3ORohTfHUGqA0HPqTJILpE4xYfMNmIvif7xPIIcJdf62809Y1xGRTwhZMz6SyMDtCSag==",          "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"}

Recovery for API Clients and Clients without Browsers#


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 Recovery 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": "660cc928-48f1-485b-8003-dc690f5afc35",  "type": "api",  "expires_at": "2021-07-09T13:52:56.270081773Z",  "issued_at": "2021-07-09T12:52:56.270081773Z",  "request_url": "",  "ui": {    "action": "",    "method": "POST",    "nodes": [ /* */ ]  },  "state": "choose_method"}

Recovery Flow Payloads#

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

$ flowId=$(curl -s -X GET \    -H "Accept: application/json" \ | jq -r '.id')
$ curl -s -X GET \    -H "Accept: application/json" \    "$flowId" | jq
{  "id": "7c033350-a935-4807-a06e-85cfc2475c75",  "type": "api",  "expires_at": "2021-07-09T14:00:52.752217Z",  "issued_at": "2021-07-09T13:00:52.752217Z",  "request_url": "",  "active": "default",  "ui": {    "action": "",    "method": "POST",    "nodes": [ /* ... */ ]  },  "state": "choose_method"}

Send Recovery Link to Email#


The link recovery 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 Recovery Flow:

$ curl -H "Accept: application/json" -s \    '' | \      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"    }  }}

Recovery Form Validation#

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

  • An HTTP 302 Found redirect pointing to the Registration UI for Browser Clients;
  • An application/json response for API Clients and Client-Side Browser applications (e.g. Single Page Apps).

Recovery Link via Email#

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

Account Recovery HTML Form with validation errors

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

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

Successful Submission#

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

Account Recovery HTML Form with success message

Unsuccessful Recovery#

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


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

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

Account Recovery HTML Form with an invalid challenge

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

Successful Recovery#

Completing account recovery always results in a HTTP 302 redirect with a Ory Kratos Login Session Cookie to the Settings UI with a Settings Flow prompting the user to update their password or credentials:

Account Recovery HTML Form with an invalid challenge

Code Examples for NodeJS, ReactJS, Go, ...#

The Recovery User Interface is a route (page / site) in your application (server, native app, single page app) that should render a recovery 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 Recovery Flow JSON response to render the recovery form UI, which could looks as follows depending on your programming language and web framework:

Account Recovery HTML Form