Skip to main content

User settings and profile updates

info

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

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

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

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 (for example change the first name). The updated traits must be valid against the Identity Schema defined for its identity traits.

You can choose which methods to use in the Ory Identities configuration:

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).
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 any of these fields are changed, the Ory 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 Session is older than the specified amount, the user is prompted to re-authenticate similar to the GitHub sudo mode:

This end-user experience 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 Session using the API-based Login Flow.

Initialize settings flow

INFO

Ory and your UI must be on the hosted on same top level domain. You can't host Ory and your UI on separate top level domains:

  • ory.bar.com and app.bar.com will work;
  • ory.bar.com and bar.com will work;
  • ory.bar.com and not-bar.com will not work.

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 Session Token (for API-based flows) or Ory 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=dev+docs.$(date +%s)@ory.sh
password=$(openssl rand -base64 12)

actionUrl=$(curl -s -H "Accept: application/json" \
'https://playground.projects.oryapis.com/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

User and profile settings for server-side browser clients

The settings flow for browser clients relies on HTTP redirects between Ory Identities, your Settings UI, and the end-user's browser:

The Authentication 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.

Before we start, we need to log in the user we created:

# We use this cookie jar to initiate the settings flow in the next step!
cookieJar=$(mktemp)

# Initialize the flow
flow=$( \
curl -s -H "Accept: application/json" --cookie $cookieJar --cookie-jar $cookieJar \
'https://playground.projects.oryapis.com/self-service/login/browser' \
)

# Get the action URL
actionUrl=$(echo $flow | jq -r '.ui.action')

# Get the CSRF Token
csrfToken=$(echo $flow | jq -r '.ui.nodes[] | select(.attributes.name=="csrf_token") | .attributes.value')

# Complete the login (with the user we created before)
session=$( \
curl -s --cookie $cookieJar --cookie-jar $cookieJar -X POST \
-H "Accept: application/json" -H "Content-Type: application/json" \
--data '{ "identifier": "'$username'", "password": "'$password'", "method": "password", "csrf_token": "'$csrfToken'" }' \
"$actionUrl" \
)

echo $session | jq

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

curl -s -i -X GET \
--cookie $cookieJar --cookie-jar $cookieJar \
-H "Accept: text/html" \
https://playground.projects.oryapis.com/self-service/settings/browser

HTTP/2 303
date: Fri, 09 Jul 2021 11:07:48 GMT
content-type: text/html; charset=utf-8
content-length: 120
location: https://playground.projects.oryapis.com/hosted/settings?flow=95efce11-0bd2-4f55-abce-180ced8b69d7
cache-control: private, no-cache, no-store, must-revalidate
vary: Origin
vary: Cookie
strict-transport-security: max-age=15724800; includeSubDomains

<a href="https://playground.projects.oryapis.com/hosted/settings?flow=95efce11-0bd2-4f55-abce-180ced8b69d7">See Other</a>.

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

You can configure which login URL to use in the Ory Identities config:

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

User and Profile Settings for Client-Side (AJAX) Browser Clients

The Settings 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 Settings Flow, point the Browser to the initialization endpoint:

curl -v -s -X GET \
--cookie $cookieJar --cookie-jar $cookieJar \
-H "Accept: application/json" \
https://playground.projects.oryapis.com/self-service/settings/browser | jq

> GET /self-service/settings/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/settings/browser",
"ui": {
"action": "https://playground.projects.oryapis.com/self-service/settings?flow=4074acf9-5025-469b-be9c-9bc776b4a862",
"method": "POST",
"nodes": [ /* ... */ ]
}
}

User and profile settings updates for API clients and clients without browsers

DANGER

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 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" \
-H "Authorization: bearer $sessionToken" \
https://playground.projects.oryapis.com/self-service/settings/api | jq

{
"id": "20d5c364-663f-47e6-bd06-2944aa288f55",
"type": "api",
"expires_at": "2021-07-09T12:10:36.109455077Z",
"issued_at": "2021-07-09T11:10:36.109455077Z",
"request_url": "http://playground.projects.oryapis.com/self-service/settings/api",
"ui": {
"action": "https://playground.projects.oryapis.com/self-service/settings?flow=20d5c364-663f-47e6-bd06-2944aa288f55",
"method": "POST",
"nodes": [ /* ... */ ]
},
"identity": {
"id": "7130f345-8506-4c13-869b-d0e8341ff585",
"schema_id": "default",
"schema_url": "https://playground.projects.oryapis.com/schemas/default",
"state": "active",
"state_changed_at": "2021-07-09T11:06:02.23703Z",
"traits": {
"email": "[email protected]"
},
"verifiable_addresses": [
{
"id": "a5c0a49b-aa8d-44fd-ae20-361136a7260e",
"value": "[email protected]",
"verified": false,
"via": "email",
"status": "sent",
"verified_at": null,
"created_at": "2021-07-09T11:06:02.244136Z",
"updated_at": "2021-07-09T11:06:02.244136Z"
}
],
"recovery_addresses": [
{
"id": "ac4b8521-905c-4492-9b9c-c877d94ca7db",
"value": "[email protected]",
"via": "email",
"created_at": "2021-07-09T11:06:02.249405Z",
"updated_at": "2021-07-09T11:06:02.249405Z"
}
],
"created_at": "2021-07-09T11:06:02.239075Z",
"updated_at": "2021-07-09T11:06:02.239075Z"
},
"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:

flowId=$(curl -s -X GET \
-H "Accept: application/json" \
-H "Authorization: bearer $sessionToken" \
https://playground.projects.oryapis.com/self-service/settings/api | jq -r '.id')

curl -s -X GET \
-H "Accept: application/json" \
-H "Authorization: bearer $sessionToken" \
"https://playground.projects.oryapis.com/self-service/settings/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/self-service/registration?flow=260196cc-cd99-40b8-a9b1-8afc06c08afc",
"method": "POST",
"nodes": [ /* ... */ ]
}
}

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" \
https://playground.projects.oryapis.com/self-service/settings/api | jq -r '.ui.nodes[] | select(.group=="profile")'

{
"type": "input",
"group": "profile",
"attributes": {
"name": "traits.email",
"type": "email",
"value": "[email protected]",
"disabled": false
},
"messages": [],
"meta": {
"label": {
"id": 1070002,
"text": "E-Mail",
"type": "info"
}
}
}
{
"type": "input",
"group": "profile",
"attributes": {
"name": "method",
"type": "submit",
"value": "profile",
"disabled": false
},
"messages": [],
"meta": {
"label": {
"id": 1070003,
"text": "Save",
"type": "info"
}
}
}

The form fields depend on the Identity Schema.

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" \
https://playground.projects.oryapis.com/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": [],
"meta": {
"label": {
"id": 1070001,
"text": "Password",
"type": "info"
}
}
}
{
"type": "input",
"group": "password",
"attributes": {
"name": "method",
"type": "submit",
"value": "password",
"disabled": false
},
"messages": [],
"meta": {
"label": {
"id": 1070003,
"text": "Save",
"type": "info"
}
}
}
Before you start

Check out the social sign-in documentation 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" \
'https://playground.projects.oryapis.com/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"
}
}
}
}
danger

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

Settings 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).

When validation errors happen, browser clients receive a HTTP 303 See Other redirect to the Settings 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 (for example updates their first name or email address). Possible validation errors include JSON Schema validation errors (for example "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 doesn't 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 (for example Google) to be linked is already linked with another identity in the system.

Successful settings update

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

Server-side browser clients

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

curl -s -X GET \
--cookie $cookieJar --cookie-jar $cookieJar \
-H "Accept: application/json" \
'https://playground.projects.oryapis.com/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": "https://playground.projects.oryapis.com/self-service/settings/browser",
"ui": {
"action": "https://playground.projects.oryapis.com/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": "https://playground.projects.oryapis.com/schemas/default",
"traits": {
"email": "[email protected]"
},
"verifiable_addresses": [
{
"id": "d2214ea2-8b0e-49c0-a9e0-9998ea4527aa",
"value": "[email protected]",
"verified": false,
"via": "email",
"status": "pending",
"verified_at": null
}
],
"recovery_addresses": [
{
"id": "29ef1378-0f13-4f2e-a9cf-683320771d5b",
"value": "[email protected]",
"via": "email"
}
]
},
"state": "success"
}

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

Client-side browser clients

When the update is completed successfully, Ory Identities response with a HTTP 200 OK message containing the updated identity:

password=ByS8NWuFSkDgMjbe

# Initialize the flow
flow=$( \
curl -s -H "Accept: application/json" --cookie $cookieJar --cookie-jar $cookieJar \
'https://playground.projects.oryapis.com/self-service/settings/browser' \
)

# Get the action URL
actionUrl=$(echo $flow | jq -r '.ui.action')

# Get the CSRF Token
csrfToken=$(echo $flow | jq -r '.ui.nodes[] | select(.attributes.name=="csrf_token") | .attributes.value')

# Complete the login (with the user we created before)
session=$( \
curl -s --cookie $cookieJar --cookie-jar $cookieJar -X POST \
-H "Accept: application/json" -H "Content-Type: application/json" \
--data '{"password": "'$password'", "method": "password", "csrf_token": "'$csrfToken'" }' \
"$actionUrl" \
)

{
"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": "https://playground.projects.oryapis.com/self-service/settings/browser",
"ui": {
"action": "https://playground.projects.oryapis.com/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": "https://playground.projects.oryapis.com/schemas/default",
"traits": {
"email": "[email protected]"
},
"verifiable_addresses": [
{
"id": "d2214ea2-8b0e-49c0-a9e0-9998ea4527aa",
"value": "[email protected]",
"verified": false,
"via": "email",
"status": "pending",
"verified_at": null
}
],
"recovery_addresses": [
{
"id": "29ef1378-0f13-4f2e-a9cf-683320771d5b",
"value": "[email protected]",
"via": "email"
}
]
},
"state": "success"
}

API clients and clients without browsers

For API clients, Ory Identities responds with a JSON payload which includes the updated identity:

password=ByS8NWuFSkDgMjbe

actionUrl=$(curl -s -H "Accept: application/json" \
-H "Authorization: bearer $sessionToken" \
'https://playground.projects.oryapis.com/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": "https://playground.projects.oryapis.com/self-service/settings/browser",
"ui": {
"action": "https://playground.projects.oryapis.com/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": "https://playground.projects.oryapis.com/schemas/default",
"traits": {
"email": "[email protected]"
},
"verifiable_addresses": [
{
"id": "d2214ea2-8b0e-49c0-a9e0-9998ea4527aa",
"value": "[email protected]",
"verified": false,
"via": "email",
"status": "pending",
"verified_at": null
}
],
"recovery_addresses": [
{
"id": "29ef1378-0f13-4f2e-a9cf-683320771d5b",
"value": "[email protected]",
"via": "email"
}
]
},
"state": "success"
}

Show verification form after updating a verifiable address

info

Showing the verification form after a settings update is currently only supported on native or SPA clients.

For SPA and native clients, the response from the registration endpoint contains a continue_with field. This field lists possible actions the user might need to take next. For example, an object containing the ID of the verification flow created as part of the settings flow:

{
"continue_with": [
{
"action": "verification_ui",
"flow": {
"id": "d859f6af-1dfe-453e-9320-d572e10edeea",
"verifiable_address": "[email protected]"
}
}
// Other items
]
}

You can use this ID to fetch the verification flow information, using the getVerificationFlow API method.

Code Examples for Node.js, React.js, Go, ...

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

Hooks

Ory Identities 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.