Skip to main content
Version: Next

User Settings and Profile Updates

info

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

Ory Kratos allows users to update their own settings and profile information using two principal flows:

  • 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 Settings Flow is composed of several high-level steps summarized in this state diagram:

Currently, three settings methods are supported:

  • password for updating the password used to sign in;
  • oidc for un-/linking from social sign in providers such as Google or Facebook;
  • profile for updating an identity's traits (e.g. change the first name). The updated traits must be valid against the Identity JSON Schema defined for its Identity Traits.

These methods are dis/enabled in the Ory Kratos config:

path/to/my/kratos/config.yml
selfservice:
methods:
password:
enabled: true
oidc:
enabled: true
profile:
enabled: true

Updating Privileged Fields#

Most Settings Flow Methods allow the user to update fields which are considered protected:

  • The password method allows updating the password, which is a protected field;
  • The profile method allows updating protected fields such as
    • the username or email address used to sign in;
    • the recovery email address;
  • The oidc method allows linking and unlinking from Google, Facebook, Github, ... which is considered a privileged action (check out the set up guide).

If any of these fields are changed, the Ory Kratos Login Session must not be older than the configured privileged_session_max_age value:

path/to/kratos/config.yml
selfservice:
flows:
settings:
# Sessions older than a minute requires the user to sign in again before
# the password is changed.
privileged_session_max_age: 1m

If the Ory Kratos Login Session is older than the specified amount, the user is prompted to re-authenticate similar to the GitHub sudo mode:

This end-user experience currently works only for Browser-based Settings Flows. API-based flows will simply return a 403 Forbidden status message which require you to request a new Ory Kratos Login session using the API-based Login Flow.

Initialize Settings Flow#

The first step is to initialize the settings flow. This allows pre-settings hooks to run, set up Anti-CSRF tokens, and more. Each Settings Flow also has a state field which can either be success or show_form:

Keep in mind that initializing a Settings Flow requires a valid Ory Kratos Login Session Token (for API-based flows) or Ory Kratos Login Session Cookie (for Browser-based flows)!

Before we start with the examples below, let's create a user using the API-based Registration Flow. This will yield a session token which then use to perform the Settings Flow on this page:

username=example.user@ory.sh
password=sBdHzGp9hAx2Hf2m
actionUrl=$(curl -s -H "Accept: application/json" \
'http://127.0.0.1:4433/self-service/registration/api' | jq -r '.ui.action')
session=$(curl -s -X POST -H "Accept: application/json" -H "Content-Type: application/json" \
--data '{ "traits.email": "'$username'", "password": "'$password'", "method": "password" }' \
"$actionUrl")
sessionToken=$(echo $session | jq -r '.session_token')
echo $sessionToken

Profile Updates for Browser Clients#

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

curl -s -v -X GET \
-H "Accept: text/html" \
http://127.0.0.1:4433/self-service/settings/browser
> GET /self-service/settings/browser HTTP/1.1
> Host: 127.0.0.1:4433
> 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: http://127.0.0.1:4455/settings?flow=df607aa1-d555-4b2a-b3e4-0f5a1d2fe6f3
< Set-Cookie: csrf_token=y4Ocu6V83BapwJwbPw/pnlRHHw40DZbjq5iuDrxl0Ds=; Path=/; Domain=127.0.0.1; Max-Age=31536000; HttpOnly
<
<a href="http://127.0.0.1:4455/settings?flow=df607aa1-d555-4b2a-b3e4-0f5a1d2fe6f3">Found</a>.

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

path/to/config/kratos.yml
selfservice:
flows:
settings:
# becomes http://127.0.0.1:4455/settings?flow=df607aa1-d555-4b2a-b3e4-0f5a1d2fe6f3
ui_url: http://127.0.0.1:4455/settings

Settings 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 Settings 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:

$ sessionToken=...
$ curl -s -X GET \
-H "Authorization: Bearer $sessionToken" \
-H "Accept: application/json" \
http://127.0.0.1:4433/self-service/settings/api | jq
{
"id": "34b4fa55-f3a3-4b16-9091-b25db5644411",
"type": "api",
"expires_at": "2021-04-28T12:31:36.139073277Z",
"issued_at": "2021-04-28T11:31:36.139073277Z",
"request_url": "http://127.0.0.1:4433/self-service/settings/api",
"ui": {
"action": "http://127.0.0.1:4433/self-service/settings?flow=34b4fa55-f3a3-4b16-9091-b25db5644411",
"method": "POST",
"nodes": [
{
"type": "input",
"group": "default",
"attributes": {
"name": "csrf_token",
"type": "hidden",
"value": "",
"required": true,
"disabled": false
},
"messages": null,
"meta": {}
},
{
"type": "input",
"group": "profile",
"attributes": {
"name": "traits.email",
"type": "email",
"value": "example.user@ory.sh",
"disabled": false
},
"messages": null,
"meta": {
"label": {
"id": 1070002,
"text": "E-Mail",
"type": "info"
}
}
},
{
"type": "input",
"group": "profile",
"attributes": {
"name": "traits.name.first",
"type": "text",
"disabled": false
},
"messages": null,
"meta": {
"label": {
"id": 1070002,
"text": "First Name",
"type": "info"
}
}
},
{
"type": "input",
"group": "profile",
"attributes": {
"name": "traits.name.last",
"type": "text",
"disabled": false
},
"messages": null,
"meta": {
"label": {
"id": 1070002,
"text": "Last Name",
"type": "info"
}
}
},
{
"type": "input",
"group": "profile",
"attributes": {
"name": "method",
"type": "submit",
"value": "profile",
"disabled": false
},
"messages": null,
"meta": {
"label": {
"id": 1070003,
"text": "Save",
"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": "method",
"type": "submit",
"value": "password",
"disabled": false
},
"messages": null,
"meta": {
"label": {
"id": 1070003,
"text": "Save",
"type": "info"
}
}
}
]
},
"identity": {
"id": "5b23b651-c186-4398-8717-f15ac72cbc7e",
"schema_id": "default",
"schema_url": "http://127.0.0.1:4433/schemas/default",
"traits": {
"email": "example.user@ory.sh"
},
"verifiable_addresses": [
{
"id": "d2214ea2-8b0e-49c0-a9e0-9998ea4527aa",
"value": "example.user@ory.sh",
"verified": false,
"via": "email",
"status": "pending",
"verified_at": null
}
],
"recovery_addresses": [
{
"id": "29ef1378-0f13-4f2e-a9cf-683320771d5b",
"value": "example.user@ory.sh",
"via": "email"
}
]
},
"state": "show_form"
}

Settings Flow Payloads#

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

$ curl -s -X GET \
-H "Authorization: Bearer $sessionToken" \
-H "Accept: application/json" \
'http://127.0.0.1:4433/self-service/settings/flows?id=f71743cd-700d-4a30-9275-8edc90de07cc' | jq
{
"id": "f71743cd-700d-4a30-9275-8edc90de07cc",
"type": "browser",
"expires_at": "2021-04-28T12:39:36.804397011Z",
"issued_at": "2021-04-28T11:39:36.804397011Z",
"request_url": "http://127.0.0.1:4433/self-service/settings/browser",
"ui": {
"action": "http://127.0.0.1:4433/self-service/settings?flow=f71743cd-700d-4a30-9275-8edc90de07cc",
"method": "POST",
"nodes": [
{
"type": "input",
"group": "default",
"attributes": {
"name": "csrf_token",
"type": "hidden",
"value": "4V8Av+NDdjl0X9hJ2+ChOuqZJK9I3SoCJHFRec8nA8heiSsgZ/5SE0yN1s/YlEJcE2B7k1x9r4t3A8vRfGGrrw==",
"required": true,
"disabled": false
},
"messages": null,
"meta": {}
},
{
"type": "input",
"group": "profile",
"attributes": {
"name": "traits.email",
"type": "email",
"value": "example.user@ory.sh",
"disabled": false
},
"messages": null,
"meta": {
"label": {
"id": 1070002,
"text": "E-Mail",
"type": "info"
}
}
},
{
"type": "input",
"group": "profile",
"attributes": {
"name": "traits.name.first",
"type": "text",
"disabled": false
},
"messages": null,
"meta": {
"label": {
"id": 1070002,
"text": "First Name",
"type": "info"
}
}
},
{
"type": "input",
"group": "profile",
"attributes": {
"name": "traits.name.last",
"type": "text",
"disabled": false
},
"messages": null,
"meta": {
"label": {
"id": 1070002,
"text": "Last Name",
"type": "info"
}
}
},
{
"type": "input",
"group": "profile",
"attributes": {
"name": "method",
"type": "submit",
"value": "profile",
"disabled": false
},
"messages": null,
"meta": {
"label": {
"id": 1070003,
"text": "Save",
"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": "method",
"type": "submit",
"value": "password",
"disabled": false
},
"messages": null,
"meta": {
"label": {
"id": 1070003,
"text": "Save",
"type": "info"
}
}
}
]
},
"identity": {
"id": "5b23b651-c186-4398-8717-f15ac72cbc7e",
"schema_id": "default",
"schema_url": "http://127.0.0.1:4433/schemas/default",
"traits": {
"email": "example.user@ory.sh"
},
"verifiable_addresses": [
{
"id": "d2214ea2-8b0e-49c0-a9e0-9998ea4527aa",
"value": "example.user@ory.sh",
"verified": false,
"via": "email",
"status": "pending",
"verified_at": null
}
],
"recovery_addresses": [
{
"id": "29ef1378-0f13-4f2e-a9cf-683320771d5b",
"value": "example.user@ory.sh",
"via": "email"
}
]
},
"state": "show_form"
}

Update Profile#

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

$ curl -s -X GET \
-H "Authorization: Bearer $sessionToken" \
-H "Accept: application/json" \
http://127.0.0.1:4433/self-service/settings/api | jq -r '.ui.nodes[] | select(.group=="profile")'
{
"type": "input",
"group": "profile",
"attributes": {
"name": "traits.email",
"type": "email",
"value": "example.user@ory.sh",
"disabled": false
},
"messages": null,
"meta": {
"label": {
"id": 1070002,
"text": "E-Mail",
"type": "info"
}
}
}
{
"type": "input",
"group": "profile",
"attributes": {
"name": "traits.name.first",
"type": "text",
"disabled": false
},
"messages": null,
"meta": {
"label": {
"id": 1070002,
"text": "First Name",
"type": "info"
}
}
}
{
"type": "input",
"group": "profile",
"attributes": {
"name": "traits.name.last",
"type": "text",
"disabled": false
},
"messages": null,
"meta": {
"label": {
"id": 1070002,
"text": "Last Name",
"type": "info"
}
}
}
{
"type": "input",
"group": "profile",
"attributes": {
"name": "method",
"type": "submit",
"value": "profile",
"disabled": false
},
"messages": null,
"meta": {
"label": {
"id": 1070003,
"text": "Save",
"type": "info"
}
}
}

The form fields depend on the Identity's Schema JSON.

Update Password#

Before you start

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

$ curl -s -X GET \
-H "Authorization: Bearer $sessionToken" \
-H "Accept: application/json" \
http://127.0.0.1:4433/self-service/settings/api | jq -r '.ui.nodes[] | select(.group=="password")'
{
"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": "method",
"type": "submit",
"value": "password",
"disabled": false
},
"messages": null,
"meta": {
"label": {
"id": 1070003,
"text": "Save",
"type": "info"
}
}
}

Link and Unlink from 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!

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

$ curl -s -X GET \
-H "Authorization: Bearer $sessionToken" \
-H "Accept: application/json" \
'http://127.0.0.1:4433/self-service/settings/flows?id=14cbf9a7-0c71-46e9-b3b9-806cb7859145' | \
jq -r '.ui.nodes[] | select(.group=="oidc")'
{
"type": "input",
"group": "oidc",
"attributes": {
"name": "link",
"type": "submit",
"value": "github",
"disabled": false
},
"messages": null,
"meta": {
"label": {
"id": 1050002,
"text": "Link github",
"type": "info",
"context": {
"provider": "github"
}
}
}
}
{
"type": "input",
"group": "oidc",
"attributes": {
"name": "unlink",
"type": "submit",
"value": "google",
"disabled": false
},
"messages": null,
"meta": {
"label": {
"id": 1050003,
"text": "Unlink google",
"type": "info",
"context": {
"provider": "google"
}
}
}
}
warning

Social Sign In is currently not possible for API Clients. It will be possible in a future version, which is partially tracked as kratos#273

Settings Flow Form Rendering#

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

Profile Settings HTML Form

Settings Form Validation#

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

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

When validation errors happen, browser clients receive a HTTP 302 Found redirect to the Settings Flow UI, containing the Settings Flow ID which includes the error payloads. For API Clients, the server typically responds with HTTP 400 Bad Request application/json and the Settings Flow in the response payload as JSON.

Update Profile#

To complete the profile update, the end-user fills out the presented profile form (e.g. updates their first name or email address). Possible validation errors include JSON Schema validation errors (e.g. "format": "email" not respected):

User Profile HTML Form with validation errors

Update Password#

To change the password, the end-user fills out the presented form and provides a new password. Possible validation errors include not providing the password or providing a password which does not match the password policy:

User Registration HTML Form with validation errors

Un-/Linking from/with Google, Facebook, GitHub, ..., OpenID Connect / OAuth 2.0#

To link or unlink from an OpenID Connect or OAuth2 provider such as Google, GitHub, Facebook, the user either clicks the unlink or link button depending on the interaction.

There are no expected validation errors except for an error where the profile (e.g. Google) to be linked is already linked with another identity in the system. This will currently result in a system error but will be a validation error in the future (tracked as kratos#694).

Successful Settings Update#

Completing the settings update behaves differently for Browser and API Clients.

Browser Clients#

When the profile update is completed successfully, Ory Kratos responds with a HTTP 302 Redirect to the Settings UI which now contains the success state (state: success) as well as the updated identity:

curl -s -X GET \
-H "Authorization: Bearer $sessionToken" \
-H "Accept: application/json" \
'http://127.0.0.1:4433/self-service/settings/flows?id=f71743cd-700d-4a30-9275-8edc90de07cc' | \
jq
{
"id": "f71743cd-700d-4a30-9275-8edc90de07cc",
"type": "browser",
"expires_at": "2021-04-28T12:39:36.804397011Z",
"issued_at": "2021-04-28T11:39:36.804397011Z",
"request_url": "http://127.0.0.1:4433/self-service/settings/browser",
"ui": {
"action": "http://127.0.0.1:4433/self-service/settings?flow=f71743cd-700d-4a30-9275-8edc90de07cc",
"method": "POST",
"nodes": [
{
"type": "input",
"group": "default",
"attributes": {
"name": "csrf_token",
"type": "hidden",
"value": "ZujkLxomQjFczI0iVkG7E8+yGQ3ouIF2E9msj9gvHPjZPs+wnptmG2Qeg6RVNVh1NktGMfwYBP9AqzYna2m0nw==",
"required": true,
"disabled": false
},
"messages": null,
"meta": {}
},
{
"type": "input",
"group": "profile",
"attributes": {
"name": "traits.email",
"type": "email",
"value": "notanemail",
"disabled": false
},
"messages": null,
"meta": {
"label": {
"id": 1070002,
"text": "E-Mail",
"type": "info"
}
}
},
{
"type": "input",
"group": "profile",
"attributes": {
"name": "traits.name.first",
"type": "text",
"value": "",
"disabled": false
},
"messages": null,
"meta": {
"label": {
"id": 1070002,
"text": "First Name",
"type": "info"
}
}
},
{
"type": "input",
"group": "profile",
"attributes": {
"name": "traits.name.last",
"type": "text",
"value": "",
"disabled": false
},
"messages": null,
"meta": {
"label": {
"id": 1070002,
"text": "Last Name",
"type": "info"
}
}
},
{
"type": "input",
"group": "profile",
"attributes": {
"name": "method",
"type": "submit",
"value": "profile",
"disabled": false
},
"messages": null,
"meta": {
"label": {
"id": 1070003,
"text": "Save",
"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": "method",
"type": "submit",
"value": "password",
"disabled": false
},
"messages": null,
"meta": {
"label": {
"id": 1070003,
"text": "Save",
"type": "info"
}
}
}
],
"messages": [
{
"id": 1050001,
"text": "Your changes have been saved!",
"type": "info"
}
]
},
"identity": {
"id": "5b23b651-c186-4398-8717-f15ac72cbc7e",
"schema_id": "default",
"schema_url": "http://127.0.0.1:4433/schemas/default",
"traits": {
"email": "example.user@ory.sh"
},
"verifiable_addresses": [
{
"id": "d2214ea2-8b0e-49c0-a9e0-9998ea4527aa",
"value": "example.user@ory.sh",
"verified": false,
"via": "email",
"status": "pending",
"verified_at": null
}
],
"recovery_addresses": [
{
"id": "29ef1378-0f13-4f2e-a9cf-683320771d5b",
"value": "example.user@ory.sh",
"via": "email"
}
]
},
"state": "success"
}

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

API Clients#

For API Clients, Ory Kratos responds with a JSON payload which includes the updated identity:

$ password=ByS8NWuFSkDgMjbe
$ actionUrl=$(curl -s -H "Accept: application/json" \
-H "Authorization: bearer $sessionToken" \
'http://127.0.0.1:4433/self-service/settings/api' | jq -r '.ui.action')
$ curl -s -X POST -H "Accept: application/json" -H "Content-Type: application/json" \
-H "Authorization: bearer $sessionToken" \
-d '{"password": "'$password'", "method": "password"}' \
"$actionUrl" | jq
{
"id": "f71743cd-700d-4a30-9275-8edc90de07cc",
"type": "browser",
"expires_at": "2021-04-28T12:39:36.804397011Z",
"issued_at": "2021-04-28T11:39:36.804397011Z",
"request_url": "http://127.0.0.1:4433/self-service/settings/browser",
"ui": {
"action": "http://127.0.0.1:4433/self-service/settings?flow=f71743cd-700d-4a30-9275-8edc90de07cc",
"method": "POST",
"nodes": [
// ...
],
"messages": [
{
"id": 1050001,
"text": "Your changes have been saved!",
"type": "info"
}
]
},
"identity": {
"id": "5b23b651-c186-4398-8717-f15ac72cbc7e",
"schema_id": "default",
"schema_url": "http://127.0.0.1:4433/schemas/default",
"traits": {
"email": "example.user@ory.sh"
},
"verifiable_addresses": [
{
"id": "d2214ea2-8b0e-49c0-a9e0-9998ea4527aa",
"value": "example.user@ory.sh",
"verified": false,
"via": "email",
"status": "pending",
"verified_at": null
}
],
"recovery_addresses": [
{
"id": "29ef1378-0f13-4f2e-a9cf-683320771d5b",
"value": "example.user@ory.sh",
"via": "email"
}
]
},
"state": "success"
}

Hooks#

Ory Kratos allows you to configure hooks that run before and after a profile update was successful. For more information about hooks please read the Hook Documentation.

Last updated on by aeneasr