User Registration
info
Please read the Self-Service Flows overview before continuing with this document.
There are two Registration Flow types supported in Ory Kratos:
- Flows where the user sits in front of the Browser and the application is
- a server-side application (NodeJS, Java, ...)
- a client-side application (ReactJS, AngularJS, ...)
- Flows where API interaction is required (Mobile app, Smart TV, ...)
The Registration Flow can be summarized as the following state machine:
Two registration methods are supported:
password
for signing up with an email / username and password;oidc
for signing up using a social sign in provider such as Google or Facebook (check out the set up guide).
- Ory Cloud
- Ory Kratos
Only the password method is enabled.
You can configure which methods to use in the Ory Kratos config:
selfservice:
methods:
password:
enabled: true
oidc:
enabled: true
# ...
Initialize Registration Flow
The first step is to initialize the Registration Flow. This allows pre-registration hooks to run, set up Anti-CSRF tokens, and more.
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.
Registration for Server-Side Browser Clients
The Registration Flow for browser clients relies on HTTP redirects between Ory Kratos, your Registration 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 Registration Flow, point the Browser to the initialization endpoint:
curl -s -i -X GET \
-H "Accept: text/html" \
https://playground.projects.oryapis.com/api/kratos/public/self-service/registration/browser
HTTP/2 303
date: Fri, 09 Jul 2021 09:35:26 GMT
content-type: text/html; charset=utf-8
content-length: 128
location: https://playground.projects.oryapis.com/hosted/registration?flow=6c2ae96c-8486-42f0-91f9-f654eccd09fb
cache-control: private, no-cache, no-store, must-revalidate
set-cookie: aHR0cHM6Ly9wbGF5Z3JvdW5kLnByb2plY3RzLm9yeWFwaXMuY29tL2FwaS9rcmF0b3MvcHVibGlj_csrf_token=JDYmr2vbucEi5hjc9/tjOxz0yORCcfFmjIgPDobyD9o=; 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/registration?flow=6c2ae96c-8486-42f0-91f9-f654eccd09fb">See Other</a>.
The server responds with a HTTP 303 redirect to the Registration UI, appending
the ?flow=<flow-id>
query parameter (see the curl example) to the configured
registration URL.
- Ory Cloud
- Ory Kratos
Ory Cloud offers a default UI implementation. Visit Bring Your Own UI to learn how to implement a custom UI.
You can configure which login URL to use in the Ory Kratos config:
selfservice:
flows:
registration:
# becomes http://127.0.0.1:4455/auth/registration?flow=df607aa1-d555-4b2a-b3e4-0f5a1d2fe6f3
ui_url: http://127.0.0.1:4455/auth/registration
Registration for Client-Side (AJAX) Browser Clients
The Registration 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 Registration Flow, call the registration initialization
endpoint and set Accept: application/json
:
curl -v -s -X GET \
-H "Accept: application/json" \
https://playground.projects.oryapis.com/api/kratos/public/self-service/registration/browser | jq
> GET /api/kratos/public/self-service/registration/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": "4074acf9-5025-469b-be9c-9bc776b4a862",
"type": "browser",
"expires_at": "2021-07-09T10:36:34.04310083Z",
"issued_at": "2021-07-09T09:36:34.04310083Z",
"request_url": "http://playground.projects.oryapis.com/self-service/registration/browser",
"ui": {
"action": "https://playground.projects.oryapis.com/api/kratos/public/self-service/registration?flow=4074acf9-5025-469b-be9c-9bc776b4a862",
"method": "POST",
"nodes": [ /* ... */ ]
}
}
Registration for API Clients and Clients without Browsers
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 Registration 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/api/kratos/public/self-service/registration/api | jq
{
"id": "c7ed95c7-2710-436c-a7d0-09ac771a3e8b",
"type": "api",
"expires_at": "2021-07-09T10:41:34.883536226Z",
"issued_at": "2021-07-09T09:41:34.883536226Z",
"request_url": "http://playground.projects.oryapis.com/self-service/registration/api",
"ui": {
"action": "https://playground.projects.oryapis.com/api/kratos/public/self-service/registration?flow=c7ed95c7-2710-436c-a7d0-09ac771a3e8b",
"method": "POST",
"method": "POST",
"method": "POST",
"nodes": [ /* ... */ ]
}
}
Registration Form Payloads
Fetching the Registration Flow (REST API Reference) is usually only required for browser clients but also works for Registration Flows initialized by API clients. All you need is a valid Registration Flow ID:
flowId=$(curl -s -X GET \
-H "Accept: application/json" \
https://playground.projects.oryapis.com/api/kratos/public/self-service/registration/api | jq -r '.id')
curl -s -X GET \
-H "Accept: application/json" \
"https://playground.projects.oryapis.com/api/kratos/public/self-service/registration/flows?id=$flowId" | jq
{
"id": "260196cc-cd99-40b8-a9b1-8afc06c08afc",
"type": "api",
"expires_at": "2021-07-09T11:05:43.388907Z",
"issued_at": "2021-07-09T10:05:43.388907Z",
"request_url": "http://playground.projects.oryapis.com/self-service/registration/api",
"ui": {
"action": "https://playground.projects.oryapis.com/api/kratos/public/self-service/registration?flow=260196cc-cd99-40b8-a9b1-8afc06c08afc",
"method": "POST",
"nodes": [ /* ... */ ]
}
}
Registration with Username/Email and password
Before you start
Please read the Username / Email & Password Credentials Documentation first.
When the password
method is enabled, it will be part of the methods
payload
in the Registration Flow. Ory Kratos uses the Identity Schema defined in
identity.default_schema_id
to generate a list of form fields and add it to the
Registration Flow. Assuming we're using the Identity Schema from the Quickstart
{
"$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",
"title": "E-Mail",
"minLength": 3,
"ory.sh/kratos": {
"credentials": {
"password": {
"identifier": true
}
},
"verification": {
"via": "email"
},
"recovery": {
"via": "email"
}
}
},
"name": {
"type": "object",
"properties": {
"first": {
"title": "First Name",
"type": "string"
},
"last": {
"title": "Last Name",
"type": "string"
}
}
}
},
"required": [
"email"
],
"additionalProperties": false
}
}
}
the response would look as such:
curl -H "Accept: application/json" -s \
'https://playground.projects.oryapis.com/api/kratos/public/self-service/registration/flows?id=388ae8da-4c44-4765-bfa8-669963d388f5' | jq
{
"id": "388ae8da-4c44-4765-bfa8-669963d388f5",
"type": "browser",
"expires_at": "2021-04-28T09:29:23.713785401Z",
"issued_at": "2021-04-28T09:19:23.713785401Z",
"request_url": "https://playground.projects.oryapis.com/api/kratos/public/self-service/registration/browser",
"ui": {
"action": "https://playground.projects.oryapis.com/api/kratos/public/self-service/registration?flow=388ae8da-4c44-4765-bfa8-669963d388f5",
"method": "POST",
"nodes": [
{
"type": "input",
"group": "default",
"attributes": {
"name": "csrf_token",
"type": "hidden",
"value": "Z9pXCAGz2oK/9Ch8x4U1ehEAKHWVngQTPFDFTlYtbK8lWzXRyDBnl3neZGUIgW44ewDjb5niFdGGM5GERhu01g==",
"required": true,
"disabled": false
},
"messages": null,
"meta": {}
},
{
"type": "input",
"group": "password",
"attributes": {
"name": "traits.email",
"type": "email",
"disabled": false
},
"messages": null,
"meta": {
"label": {
"id": 1070002,
"text": "E-Mail",
"type": "info"
}
}
},
{
"type": "input",
"group": "password",
"attributes": {
"name": "password",
"type": "password",
"required": true,
"disabled": false
},
"messages": null,
"meta": {
"label": {
"id": 1070001,
"text": "Password",
"type": "info"
}
}
},
{
"type": "input",
"group": "password",
"attributes": {
"name": "traits.name.first",
"type": "text",
"disabled": false
},
"messages": null,
"meta": {
"label": {
"id": 1070002,
"text": "First Name",
"type": "info"
}
}
},
{
"type": "input",
"group": "password",
"attributes": {
"name": "traits.name.last",
"type": "text",
"disabled": false
},
"messages": null,
"meta": {
"label": {
"id": 1070002,
"text": "Last Name",
"type": "info"
}
}
},
{
"type": "input",
"group": "password",
"attributes": {
"name": "method",
"type": "submit",
"value": "password",
"disabled": false
},
"messages": null,
"meta": {
"label": {
"id": 1040001,
"text": "Sign up",
"type": "info",
"context": {}
}
}
}
]
}
}
Registration with Google, Facebook, GitHub, ..., OpenID Connect / OAuth 2.0
Before you start
Check out the Sign in with GitHub, Google, ... Guide and learn how to set up this method!
The Social Sign Up Method (oidc
) enables you to use
- GitHub;
- Apple;
- GitLab;
- Google;
- Facebook;
- Ory Hydra;
- Keycloak;
- and every other OpenID Connect Certified Provider
warning
Social Sign Up is not possible for API Clients. It will be possible in a future version, which is partially tracked as kratos#273
If enabled, the method contains an oidc
key with the configured sign in
providers as submit fields:
curl -H "Accept: application/json" -s \
'https://playground.projects.oryapis.com/api/kratos/public/self-service/registration/flows?id=c1f4dfd1-3559-4cd3-b6f6-4a83689a41bd' | jq
{
"id": "c1f4dfd1-3559-4cd3-b6f6-4a83689a41bd",
"type": "browser",
"expires_at": "2021-04-28T10:25:35.011063Z",
"issued_at": "2021-04-28T09:25:35.011063Z",
"request_url": "https://playground.projects.oryapis.com/api/kratos/public/self-service/registration/browser",
"ui": {
"action": "http://127.0.0.1:4455/self-service/registration?flow=c1f4dfd1-3559-4cd3-b6f6-4a83689a41bd",
"method": "POST",
"nodes": [
{
"type": "input",
"group": "default",
"attributes": {
"name": "csrf_token",
"type": "hidden",
"value": "PJO66RnYyYLyksS/bjlveGnmZARtqPvibgJI/0EX3DPZf0Oq0Ln+5KtTHVfcxMDSbXjx32hU9GwpfbwA2eLFWA==",
"required": true,
"disabled": false
},
"messages": null,
"meta": {}
},
{
"type": "input",
"group": "oidc",
"attributes": {
"name": "provider",
"type": "submit",
"value": "github",
"disabled": false
},
"messages": null,
"meta": {
"label": {
"id": 1040002,
"text": "Sign up with github",
"type": "info",
"context": {
"provider": "github"
}
}
}
},
{
"type": "input",
"group": "oidc",
"attributes": {
"name": "provider",
"type": "submit",
"value": "google",
"disabled": false
},
"messages": null,
"meta": {
"label": {
"id": 1040002,
"text": "Sign up with google",
"type": "info",
"context": {
"provider": "google"
}
}
}
}
]
}
}
Registration Form Validation
The form payloads are then submitted to Ory Kratos 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).
Registration with Username/Email and Password
To complete the registration process, the end-user fills out the form which must
include a field marked as the identifier
(ory.sh/kratos.credentials.password.identifier: true
) in the Identity JSON
Schema, for example:
{
"$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
+ }
+ }
+ }
}
},
"required": [
"email"
],
"additionalProperties": false
}
}
}
If the registration payload is invalid (for example the password doesn't match the password policy, the email is missing, ...), the password method includes the validation errors:
- Browser UI
- Missing Email
- Password Policy Violation

curl -H "Accept: application/json" -s \
'https://playground.projects.oryapis.com/api/kratos/public/self-service/registration/flows?id=ba7b883b-80e5-457b-9ef1-c17e6743edd0' \
| jq -r '.ui.nodes[] | select(.attributes.name=="traits.email")'
{
"type": "input",
"group": "password",
"attributes": {
"name": "traits.email",
"type": "email",
"value": "",
"disabled": false
},
"messages": [
{
"id": 4000001,
"text": "\"\" isn't valid \"email\"",
"type": "error"
},
{
"id": 4000001,
"text": "length must be >= 3, but got 0",
"type": "error"
}
],
"meta": {
"label": {
"id": 1070002,
"text": "E-Mail",
"type": "info"
}
}
}
curl -H "Accept: application/json" -s \
'https://playground.projects.oryapis.com/api/kratos/public/self-service/registration/flows?id=ba7b883b-80e5-457b-9ef1-c17e6743edd0' \
| jq -r '.ui.nodes[] | select(.attributes.name=="password")'
{
"type": "input",
"group": "password",
"attributes": {
"name": "password",
"type": "password",
"required": true,
"disabled": false
},
"messages": [
{
"id": 4000005,
"text": "The password can't be used because the password has been found in data breaches and must no longer be used.",
"type": "error",
"context": {
"reason": "the password has been found in data breaches and must no longer be used."
}
}
],
"meta": {
"label": {
"id": 1070001,
"text": "Password",
"type": "info"
}
}
}
When validation errors happen, browser clients receive a HTTP 303 See Other redirect to the Registration Flow UI, containing the Registration Flow ID which includes the error payloads.
For API Clients, the server typically responds with HTTP 400 Bad Request and the Registration Flow in the response payload as JSON.
Registration with Google, Facebook, GitHub, ..., OpenID Connect / OAuth 2.0
Completing the oidc
method requires the user to go through an OAuth 2.0 or
OpenID Connect flow which involves logging into the upstream identity provider
(for example Google) and giving consent.
note
If the user has already sign up with the given provider before, the user will simply be logged in and no new Ory Kratos Identity will be created.
A possible validation error is a missing ID Token:
- Missing ID Token
curl -s -H "Accept: application/json" \
'https://playground.projects.oryapis.com/api/kratos/public/self-service/registration/flows?id=76cec270-1719-4c9e-b09a-4af8281d511e' \
| jq -r '.ui.messages'
[
{
"id": 4000001,
"text": "Authentication failed because no id_token was returned. Please accept the \"openid\" permission and try again.",
"type": "error"
}
]
What may also happen is that the Ory Kratos Identity Schema JSON includes a field which is required but wasn't provided by the upstream identity provider (for example Google). In those cases, the end-user's browser is returned to the registration screen with the form validation errors.
Let's assume the Ory Kratos Identity Schema JSON requires a website
trait
{
"$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
}
}
}
},
+ "website": {
+ "type": "string"
+ }
},
"required": [
"email",
+ "website"
],
"additionalProperties": false
}
}
}
which wasn't provided by the upstream identity provider. This can happen when the user refuses to accept a certain scope (for example "Allow Ory Kratos access to my website") or if the provider simply doesn't have this data (for example the upstream identity provider doesn't collect the user's website).
In that case the end-user ends up at the registration screen and is prompted to provide the missing or invalid fields:
- Browser UI
- Missing Website

curl -H "Accept: application/json" -s \
'https://playground.projects.oryapis.com/api/kratos/public/self-service/registration/flows?id=76cec270-1719-4c9e-b09a-4af8281d511e' \
| jq -r '.ui.nodes[] | select(.attributes.name=="traits.website")'
{
"type": "input",
"group": "oidc",
"attributes": {
"name": "traits.website",
"type": "url",
"disabled": false
},
"messages": [
{
"id": 4000002,
"text": "Property website is missing.",
"type": "error",
"context": {
"property": "website"
}
}
],
"meta": {
"label": {
"id": 1070002,
"text": "Website",
"type": "info"
}
}
}
Successful Registration
Completing the registration behaves differently for Browser and API Clients. The Registration Flow doesn't issue a Ory Kratos Login Session automatically which prevents Account Enumeration Attacks. If these types of attacks aren't your concern, you can add the session hook which will issue a session after successful registration:
# kratos -c path/to/my/config/kratos.yml serve
selfservice:
flows:
registration:
after:
password:
hooks:
- hook: session
oidc:
hooks:
- hook: session # Always enable this for oidc flows or the user experience suffers significantly!
important
It's very important to add the "session" hook to the after oidc
registration
hooks. Otherwise your users need to use the login flow again to be able to get a
session. Account Enumeration Attacks with OpenID Connect sign in flows are
typically not an issue if you follow the guidelines in the
Sign in with GitHub, Google, Facebook, LinkedIn, Microsoft ... Guide!
Server-Side Browser Clients
When the registration is completed successfully, Ory Kratos responds with a HTTP 303 Redirect to the configured redirect URL.
With Auto-Login on Registration
If the session
after hook is enabled, a Set-Cookie
HTTP Header is set
alongside the HTTP 303 redirect which contains the Ory Kratos Login Session
Cookie:
HTTP/1.1 303 See Other
Cache-Control: 0
Location: http://127.0.0.1:4455/
Set-Cookie: csrf_token=b8OebRPTPr5ow23mA5gIZmFNLeuMbv8pZz1jT1Ex7ys=; Path=/; Domain=127.0.0.1; Max-Age=31536000; HttpOnly
Set-Cookie: ory_kratos_session=MTU5OTE2ODc2N3xEdi1CQkFFQ180SUFBUkFCRUFBQVJfLUNBQUVHYzNSeWFXNW5EQThBRFhObGMzTnBiMjVmZEc5clpXNEdjM1J5YVc1bkRDSUFJR055VlROMGRteHhSakJrUzBkbmRUUjBlVFY1V0RCRWFVTnJXVmR6V25oaHx2DICsB6IMbaHSQwnYITUZqr7Qx7CxUlnaneJWH495wQ==; Path=/; Expires=Fri, 04 Sep 2020 21:32:47 GMT; Max-Age=86400; HttpOnly; SameSite=Lax
Vary: Cookie
Date: Thu, 03 Sep 2020 21:32:47 GMT
Content-Length: 0
Now, whenever the browser is making a request (with cookies) to the
http://127.0.0.1/sessions/whoami
endpoint, the session will be returned:
curl -s -H "Cookie: ory_kratos_session=MTU5OTE2ODc2N3xEdi1CQkFFQ180SUFBUkFCRUFBQVJfLUNBQUVHYzNSeWFXNW5EQThBRFhObGMzTnBiMjVmZEc5clpXNEdjM1J5YVc1bkRDSUFJR055VlROMGRteHhSakJrUzBkbmRUUjBlVFY1V0RCRWFVTnJXVmR6V25oaHx2DICsB6IMbaHSQwnYITUZqr7Qx7CxUlnaneJWH495wQ==" \
https://playground.projects.oryapis.com/api/kratos/public/sessions/whoami | jq
{
"id": "ede90ce6-2420-435a-a745-3d8ab1a9636c",
"active": true,
"expires_at": "2020-09-04T21:32:47.5642404Z",
"authenticated_at": "2020-09-03T21:32:47.5881038Z",
"issued_at": "2020-09-03T21:32:47.5642688Z",
"identity": {
"id": "d96e86d9-bc33-4aa5-b865-4ade8a3974b3",
"schema_id": "default",
"schema_url": "https://playground.projects.oryapis.com/api/kratos/public/schemas/default",
"traits": {
"email": "foouser@ory.sh",
"name": {
"first": "foo",
"last": "user"
}
},
"verifiable_addresses": [
{
"id": "81bbdeae-6333-42f2-877e-26c78acb6ea5",
"value": "foouser@ory.sh",
"verified": false,
"via": "email",
"status": "pending",
"verified_at": null
}
],
"recovery_addresses": [
{
"id": "596c1db4-ccaa-4f4e-9623-cb7e768026ad",
"value": "foouser@ory.sh",
"via": "email"
}
]
}
}
Client-Side Browser Clients
When the registration is completed successfully, Ory Kratos responds with a HTTP 200 OK message which contains the identity:
{
"identity": {
"id": "d8baf63b-7ce6-4275-82b9-9ac6d97e5037",
"schema_id": "default",
"schema_url": "https://playground.projects.oryapis.com/api/kratos/public/schemas/default",
"traits": {
"email": "registration-api@user.org"
},
"verifiable_addresses": [
{
"id": "87defb49-ad69-461c-b5f6-56c1ec39dd39",
"value": "registration-api@user.org",
"verified": false,
"via": "email",
"status": "pending",
"verified_at": null
}
],
"recovery_addresses": [
{
"id": "232793b8-8d60-427d-89e6-d0a97a7a172c",
"value": "registration-api@user.org",
"via": "email"
}
]
}
}
With Auto-Login on Registration
If the session
after hook is enabled, a Set-Cookie
HTTP Header is set
alongside the HTTP 303 redirect which contains the Ory Kratos Login Session
Cookie:
HTTP/1.1 303 See Other
Cache-Control: 0
Location: http://127.0.0.1:4455/
Set-Cookie: csrf_token=b8OebRPTPr5ow23mA5gIZmFNLeuMbv8pZz1jT1Ex7ys=; Path=/; Domain=127.0.0.1; Max-Age=31536000; HttpOnly
Set-Cookie: ory_kratos_session=MTU5OTE2ODc2N3xEdi1CQkFFQ180SUFBUkFCRUFBQVJfLUNBQUVHYzNSeWFXNW5EQThBRFhObGMzTnBiMjVmZEc5clpXNEdjM1J5YVc1bkRDSUFJR055VlROMGRteHhSakJrUzBkbmRUUjBlVFY1V0RCRWFVTnJXVmR6V25oaHx2DICsB6IMbaHSQwnYITUZqr7Qx7CxUlnaneJWH495wQ==; Path=/; Expires=Fri, 04 Sep 2020 21:32:47 GMT; Max-Age=86400; HttpOnly; SameSite=Lax
Vary: Cookie
As well as information about the session:
{
"session": {
"id": "6d5ef6f4-ea54-4310-a762-473499835a48",
"active": true,
"expires_at": "2020-09-08T10:12:34.792802227Z",
"authenticated_at": "2020-09-07T10:12:34.797538934Z",
"issued_at": "2020-09-07T10:12:34.792813032Z",
"identity": {
"id": "8a7755df-1aac-4477-a53c-3f16fa059113",
"schema_id": "default",
"schema_url": "https://playground.projects.oryapis.com/api/kratos/public/schemas/default",
"traits": {
"email": "registration-session-api@user.org"
},
"verifiable_addresses": [
{
"id": "95139fe8-3360-4b08-adf6-4cc9b4555d86",
"value": "registration-session-api@user.org",
"verified": false,
"via": "email",
"status": "pending",
"verified_at": null
}
],
"recovery_addresses": [
{
"id": "e6987729-b6fa-48d5-aa15-c0a57edfdfc4",
"value": "registration-session-api@user.org",
"via": "email"
}
]
}
},
"identity": {
"id": "8a7755df-1aac-4477-a53c-3f16fa059113",
"schema_id": "default",
"schema_url": "https://playground.projects.oryapis.com/api/kratos/public/schemas/default",
"traits": {
"email": "registration-session-api@user.org"
},
"verifiable_addresses": [
{
"id": "95139fe8-3360-4b08-adf6-4cc9b4555d86",
"value": "registration-session-api@user.org",
"verified": false,
"via": "email",
"status": "pending",
"verified_at": null
}
],
"recovery_addresses": [
{
"id": "e6987729-b6fa-48d5-aa15-c0a57edfdfc4",
"value": "registration-session-api@user.org",
"via": "email"
}
]
}
}
API Clients
For API Clients, Ory Kratos responds with a JSON payload which includes the signed up identity:
# Inits a Registration Flow
actionUrl=$(\
curl -s -X GET -H "Accept: application/json" \
"https://playground.projects.oryapis.com/api/kratos/public/self-service/registration/api" \
| jq -r '.ui.action'\
)
# Complete Registration Flow with password method
curl -s -X POST -H "Accept: application/json" -H "Content-Type: application/json" \
-d '{"traits.email": "registration-api@user.org", "password": "fhAzi860a", "method": "password"}' \
"$actionUrl" | jq
{
"identity": {
"id": "d8baf63b-7ce6-4275-82b9-9ac6d97e5037",
"schema_id": "default",
"schema_url": "https://playground.projects.oryapis.com/api/kratos/public/schemas/default",
"traits": {
"email": "registration-api@user.org"
},
"verifiable_addresses": [
{
"id": "87defb49-ad69-461c-b5f6-56c1ec39dd39",
"value": "registration-api@user.org",
"verified": false,
"via": "email",
"status": "pending",
"verified_at": null
}
],
"recovery_addresses": [
{
"id": "232793b8-8d60-427d-89e6-d0a97a7a172c",
"value": "registration-api@user.org",
"via": "email"
}
]
}
}
With Auto-Login on Registration
If the session
after hook is enabled, the Ory Kratos Login Session and Ory
Kratos Login Session Token are included in the response:
actionUrl=$(\
curl -s -X GET -H "Accept: application/json" \
"https://playground.projects.oryapis.com/api/kratos/public/self-service/registration/api" \
| jq -r '.ui.action'\
)
curl -s -X POST -H "Accept: application/json" -H "Content-Type: application/json" \
-d '{"traits.email": "registration-api@user.org", "password": "fhAzi860a", "method": "password"}' \
"$actionUrl" | jq
{
"session_token": "9kmrgslvw8ZCyEtSZqOmxEtnfBJIvB31",
"session": {
"id": "6d5ef6f4-ea54-4310-a762-473499835a48",
"active": true,
"expires_at": "2020-09-08T10:12:34.792802227Z",
"authenticated_at": "2020-09-07T10:12:34.797538934Z",
"issued_at": "2020-09-07T10:12:34.792813032Z",
"identity": {
"id": "8a7755df-1aac-4477-a53c-3f16fa059113",
"schema_id": "default",
"schema_url": "https://playground.projects.oryapis.com/api/kratos/public/schemas/default",
"traits": {
"email": "registration-session-api@user.org"
},
"verifiable_addresses": [
{
"id": "95139fe8-3360-4b08-adf6-4cc9b4555d86",
"value": "registration-session-api@user.org",
"verified": false,
"via": "email",
"status": "pending",
"verified_at": null
}
],
"recovery_addresses": [
{
"id": "e6987729-b6fa-48d5-aa15-c0a57edfdfc4",
"value": "registration-session-api@user.org",
"via": "email"
}
]
}
},
"identity": {
"id": "8a7755df-1aac-4477-a53c-3f16fa059113",
"schema_id": "default",
"schema_url": "https://playground.projects.oryapis.com/api/kratos/public/schemas/default",
"traits": {
"email": "registration-session-api@user.org"
},
"verifiable_addresses": [
{
"id": "95139fe8-3360-4b08-adf6-4cc9b4555d86",
"value": "registration-session-api@user.org",
"verified": false,
"via": "email",
"status": "pending",
"verified_at": null
}
],
"recovery_addresses": [
{
"id": "e6987729-b6fa-48d5-aa15-c0a57edfdfc4",
"value": "registration-session-api@user.org",
"via": "email"
}
]
}
}
Code Examples for NodeJS, ReactJS, Go, ...
The Registration User Interface is a route (page / site) in your application (server, native app, single page app) that should render a registration form.
In stark contrast to other Identity Systems, Ory Kratos doesn't render this HTML. Instead, you need to implement the HTML code in your application (for example NodeJS + ExpressJS, Java, PHP, ReactJS, ...), which gives you extreme flexibility and customizability in your user interface flows and designs.
You will use the Registration Flow JSON response to render the registration form UI, which could looks as follows depending on your programming language and web framework:
- Browser UI
- Golang (API Flow)
- ExpressJS
- ReactJS
- React Native

// Copyright © 2022 Ory Corp
// SPDX-License-Identifier: Apache-2.0
import {
defaultConfig,
getUrlForFlow,
isQuerySet,
logger,
redirectOnSoftError,
RouteCreator,
RouteRegistrator,
} from "../pkg"
import { UserAuthCard } from "@ory/elements-markup"
// A simple express handler that shows the registration screen.
export const createRegistrationRoute: RouteCreator =
(createHelpers) => (req, res, next) => {
res.locals.projectName = "Create account"
const {
flow,
return_to,
after_verification_return_to,
login_challenge,
organization,
} = req.query
const { frontend, kratosBrowserUrl, logoUrl, extraPartials } =
createHelpers(req, res)
const initFlowQuery = new URLSearchParams({
...(return_to && { return_to: return_to.toString() }),
...(organization && { organization: organization.toString() }),
...(after_verification_return_to && {
after_verification_return_to: after_verification_return_to.toString(),
}),
})
if (isQuerySet(login_challenge)) {
logger.debug("login_challenge found in URL query: ", { query: req.query })
initFlowQuery.append("login_challenge", login_challenge)
} else {
logger.debug("no login_challenge found in URL query: ", {
query: req.query,
})
}
const initFlowUrl = getUrlForFlow(
kratosBrowserUrl,
"registration",
initFlowQuery,
)
// The flow is used to identify the settings and registration flow and
// return data like the csrf_token and so on.
if (!isQuerySet(flow)) {
logger.debug("No flow ID found in URL query initializing login flow", {
query: req.query,
})
res.redirect(303, initFlowUrl)
return
}
frontend
.getRegistrationFlow({ id: flow, cookie: req.header("Cookie") })
.then(({ data: flow }) => {
// Render the data using a view (e.g. Jade Template):
const initLoginQuery = new URLSearchParams({
return_to:
(return_to && return_to.toString()) || flow.return_to || "",
})
if (flow.oauth2_login_request?.challenge) {
initLoginQuery.set(
"login_challenge",
flow.oauth2_login_request.challenge,
)
}
res.render("registration", {
nodes: flow.ui.nodes,
card: UserAuthCard(
{
flow,
flowType: "registration",
cardImage: logoUrl,
additionalProps: {
loginURL: getUrlForFlow(
kratosBrowserUrl,
"login",
initLoginQuery,
),
},
},
{ locale: res.locals.lang },
),
extraPartial: extraPartials?.registration,
extraContext: res.locals.extraContext,
})
})
.catch(redirectOnSoftError(res, next, initFlowUrl))
}
export const registerRegistrationRoute: RouteRegistrator = (
app,
createHelpers = defaultConfig,
) => {
app.get("/registration", createRegistrationRoute(createHelpers))
}
- Registration View
- Generic Form View
- Example Input Form Element
<div id="signup">
{{{card}}}
{{> js_setup nodes=nodes}}
{{#if extraPartial }}
{{> (extraPartial) }}
{{/if}}
</div>
404: Not Found
404: Not Found
import { RegistrationFlow, UpdateRegistrationFlowBody } from "@ory/client"
import { CardTitle } from "@ory/themes"
import { AxiosError } from "axios"
import type { NextPage } from "next"
import Head from "next/head"
import { useRouter } from "next/router"
import { useEffect, useState } from "react"
// Import render helpers
import { ActionCard, CenterLink, Flow, MarginCard } from "../pkg"
import { handleFlowError } from "../pkg/errors"
// Import the SDK
import ory from "../pkg/sdk"
// Renders the registration page
const Registration: NextPage = () => {
const router = useRouter()
// The "flow" represents a registration process and contains
// information about the form we need to render (e.g. username + password)
const [flow, setFlow] = useState<RegistrationFlow>()
// Get ?flow=... from the URL
const { flow: flowId, return_to: returnTo } = router.query
// In this effect we either initiate a new registration flow, or we fetch an existing registration flow.
useEffect(() => {
// If the router is not ready yet, or we already have a flow, do nothing.
if (!router.isReady || flow) {
return
}
// If ?flow=.. was in the URL, we fetch it
if (flowId) {
ory
.getRegistrationFlow({ id: String(flowId) })
.then(({ data }) => {
// We received the flow - let's use its data and render the form!
setFlow(data)
})
.catch(handleFlowError(router, "registration", setFlow))
return
}
// Otherwise we initialize it
ory
.createBrowserRegistrationFlow({
returnTo: returnTo ? String(returnTo) : undefined,
})
.then(({ data }) => {
setFlow(data)
})
.catch(handleFlowError(router, "registration", setFlow))
}, [flowId, router, router.isReady, returnTo, flow])
const onSubmit = async (values: UpdateRegistrationFlowBody) => {
await router
// On submission, add the flow ID to the URL but do not navigate. This prevents the user loosing
// his data when she/he reloads the page.
.push(`/registration?flow=${flow?.id}`, undefined, { shallow: true })
ory
.updateRegistrationFlow({
flow: String(flow?.id),
updateRegistrationFlowBody: values,
})
.then(async ({ data }) => {
// If we ended up here, it means we are successfully signed up!
//
// You can do cool stuff here, like having access to the identity which just signed up:
console.log("This is the user session: ", data, data.identity)
// continue_with is a list of actions that the user might need to take before the registration is complete.
// It could, for example, contain a link to the verification form.
if (data.continue_with) {
for (const item of data.continue_with) {
switch (item.action) {
case "show_verification_ui":
await router.push("/verification?flow=" + item.flow.id)
return
}
}
}
// If continue_with did not contain anything, we can just return to the home page.
await router.push(flow?.return_to || "/")
})
.catch(handleFlowError(router, "registration", setFlow))
.catch((err: AxiosError) => {
// If the previous handler did not catch the error it's most likely a form validation error
if (err.response?.status === 400) {
// Yup, it is!
setFlow(err.response?.data)
return
}
return Promise.reject(err)
})
}
return (
<>
<Head>
<title>Create account - Ory NextJS Integration Example</title>
<meta name="description" content="NextJS + React + Vercel + Ory" />
</Head>
<MarginCard>
<CardTitle>Create account</CardTitle>
<Flow onSubmit={onSubmit} flow={flow} />
</MarginCard>
<ActionCard>
<CenterLink data-testid="cta-link" href="/login">
Sign in
</CenterLink>
</ActionCard>
</>
)
}
export default Registration
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package main
import (
"context"
"github.com/ory/kratos/examples/go/pkg"
ory "github.com/ory/client-go"
)
// If you use Open Source this would be:
//
// var client = pkg.NewSDKForSelfHosted("http://127.0.0.1:4433")
var client = pkg.NewSDK("playground")
func initRegistration() *ory.SuccessfulNativeRegistration {
ctx := context.Background()
// Initialize the flow
flow, res, err := client.FrontendAPI.CreateNativeRegistrationFlow(ctx).Execute()
pkg.SDKExitOnError(err, res)
// If you want, print the flow here:
//
// pkg.PrintJSONPretty(flow)
email, password := pkg.RandomCredentials()
// Submit the registration flow
result, res, err := client.FrontendAPI.UpdateRegistrationFlow(ctx).Flow(flow.Id).UpdateRegistrationFlowBody(
ory.UpdateRegistrationFlowWithPasswordMethodAsUpdateRegistrationFlowBody(&ory.UpdateRegistrationFlowWithPasswordMethod{
Method: "password",
Password: password,
Traits: map[string]interface{}{"email": email},
}),
).Execute()
pkg.SDKExitOnError(err, res)
return result
}
func main() {
pkg.PrintJSONPretty(
initRegistration(),
)
}
// This file renders the registration screen.
import { RegistrationFlow, UpdateRegistrationFlowBody } from "@ory/client"
import { useFocusEffect, useNavigation } from "@react-navigation/native"
import { StackScreenProps } from "@react-navigation/stack"
import React, { useCallback, useContext, useState } from "react"
import { Platform } from "react-native"
import { SessionContext } from "../../helpers/auth"
import { getNodeId, handleFormSubmitError } from "../../helpers/form"
import { newOrySdk } from "../../helpers/sdk"
import { AuthContext } from "../AuthProvider"
import AuthLayout from "../Layout/AuthLayout"
import ProjectPicker from "../Layout/ProjectPicker"
import { RootStackParamList } from "../Navigation"
import { SelfServiceFlow } from "../Ory/Ui"
import { ProjectContext } from "../ProjectProvider"
import AuthSubTitle from "../Styled/AuthSubTitle"
import NavigationCard from "../Styled/NavigationCard"
import StyledCard from "../Styled/StyledCard"
import * as AuthSession from "expo-auth-session"
import { logSDKError } from "../../helpers/axios"
type Props = StackScreenProps<RootStackParamList, "Registration">
const Registration = ({ navigation }: Props) => {
const [flow, setFlow] = useState<RegistrationFlow | undefined>(undefined)
const { project } = useContext(ProjectContext)
const { setSession, isAuthenticated } = useContext(AuthContext)
const initializeFlow = () =>
newOrySdk(project)
.createNativeRegistrationFlow({
// If you do use social sign in, please add the following URLs to your allowed return to URLs.
// If you the app is running on an emulator or physical device: exp://localhost:8081
// If you are using the web version: http://localhost:19006 (or whatever port you are using)
// If that does not work, please see the documentation of makeRedirectURI for more information: https://docs.expo.dev/versions/latest/sdk/auth-session/#authsessionmakeredirecturioptions
// If you don't use Social sign in, you can comment out the following line.
returnTo: AuthSession.makeRedirectUri({
preferLocalhost: true,
path: "/Callback",
}),
returnSessionTokenExchangeCode: true,
})
// The flow was initialized successfully, let's set the form data:
.then(({ data: flow }) => {
setFlow(flow)
console.log("Setting registration flow", flow)
})
.catch(logSDKError)
// When the component is mounted, we initialize a new use login flow:
useFocusEffect(
useCallback(() => {
if (isAuthenticated) {
navigation.navigate("Home")
return
}
initializeFlow()
return () => setFlow(undefined)
}, [project]),
)
const refetchFlow = () =>
newOrySdk(project)
.getRegistrationFlow({ id: flow!.id })
.then(({ data: f }) => setFlow({ ...flow, ...f })) // merging ensures we don't lose the code
.catch(logSDKError)
const setSessionAndRedirect = (session: SessionContext) => {
setSession(session)
setTimeout(() => {
navigation.navigate("Home")
}, 100)
}
// This will update the registration flow with the user provided input:
const onSubmit = async (
payload: UpdateRegistrationFlowBody,
): Promise<void> => {
if (!flow) {
return
}
newOrySdk(project)
.updateRegistrationFlow({
flow: flow.id,
updateRegistrationFlowBody: payload,
})
.then(({ data }) => {
// Ory Kratos can be configured in such a way that it requires a login after
// registration. You could handle that case by navigating to the Login screen
// but for simplicity we'll just print an error here:
if (!data.session_token || !data.session) {
const err = new Error(
"It looks like you configured Ory Idnetities to not issue a session automatically after registration. This edge-case is currently not supported in this example app. You can find more information on enabling this feature here: https://www.ory.sh/kratos/docs/next/self-service/flows/user-registration#successful-registration",
)
return Promise.reject(err)
}
const s: SessionContext = {
session: data.session,
session_token: data.session_token,
}
let verificationFlow = false
if (data.continue_with) {
for (const c of data.continue_with) {
switch (c.action) {
case "show_verification_ui": {
console.log("got a verfification flow, navigating to it", c)
verificationFlow = true
navigation.navigate("Verification", {
flowId: c.flow.id,
})
break
}
case "set_ory_session_token": {
// Right now, this is redundant, and is just supposed to show that the session token is also included
// in the continue_with elements.
console.log(
"found an ory session token, storing it for further use",
)
s.session_token = c.ory_session_token
break
}
}
}
}
// Let's log the user in!
setSession(s)
if (!verificationFlow) {
navigation.navigate("Home")
}
})
.catch(
handleFormSubmitError(
flow,
setFlow,
initializeFlow,
setSessionAndRedirect,
refetchFlow,
),
)
}
if (!flow) {
// TODO: Show loading indicator?
return null
}
return (
<RegistrationUI navigation={navigation} flow={flow} onSubmit={onSubmit} />
)
}
type RegistrationUIProps = {
flow: RegistrationFlow
onSubmit: (payload: UpdateRegistrationFlowBody) => Promise<void>
navigation: Props["navigation"]
}
function RegistrationUI({ flow, onSubmit, navigation }: RegistrationUIProps) {
return (
<AuthLayout>
<StyledCard>
<AuthSubTitle>Create an account</AuthSubTitle>
<SelfServiceFlow
textInputOverride={(field, props) => {
switch (getNodeId(field)) {
case "traits.email":
return {
autoCapitalize: "none",
autoCompleteType: "email",
textContentType: "username",
autoCorrect: false,
}
case "password":
const iOS12Plus =
Platform.OS === "ios" &&
parseInt(String(Platform.Version), 10) >= 12
return {
textContentType: iOS12Plus ? "newPassword" : "password",
secureTextEntry: true,
}
}
return props
}}
flow={flow}
onSubmit={onSubmit}
/>
</StyledCard>
<NavigationCard
description="Already have an account?"
cta="Sign in!"
onPress={() => navigation.navigate("Login", {})}
/>
<ProjectPicker />
</AuthLayout>
)
}
export default Registration
- Generic Form View
- Example Input Form Element
404: Not Found
404: Not Found
Hooks
Ory Kratos allows you to configure hooks that run before and after a Registration Flow. This may be helpful if you'd like to restrict registration to IPs coming from your internal network or other logic.
For more information about hooks please read the Hook Documentation.
API Examples
Registering New User Information
Using the /self-service/registration
with POST
allows the creation of new
user information
actionUrl=$(curl -s -X GET -H "Accept: application/json" \
https:/playground.projects.oryapis.com/api/kratos/public/self-service/registration/api \
| jq -r '.ui.action')
curl -s -X POST -H "Content-Type: application/json" \
-d '{"traits.email": "iminvisibleagain@google.com", "password": "__MY__PASSWORD__", "method": "password"}' \
"$actionUrl" | jq
On successful response will get JSON response like the following:
{
"session_token": "UA8rB8ydXyMyiIWcX8VeSWjUB7Z21YF1",
"session": {
"id": "5a55e850-6eea-4e34-a716-c2f5dbea6db6",
"active": true,
"expires_at": "2021-07-15T08:41:26.339781706Z",
"authenticated_at": "2021-07-14T08:41:26.379062124Z",
"issued_at": "2021-07-14T08:41:26.339798436Z",
"identity": {
"id": "bd1fbe06-7273-462c-ad8c-d077bfc370b9",
"schema_id": "default",
"schema_url": "https://playground.projects.oryapis.com/api/kratos/public/schemas/default",
"state": "active",
"state_changed_at": "2021-07-14T08:41:26.29470737Z",
"traits": {
"email": "iminvisibleagain@google.com"
},
// ...
}
}
}