Skip to main content

User login

info

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

There are two Login Flow types supported in Ory Identities:

  • Flows where the user sits in front of the Browser and the application is
    • a server-side application (Node.js, Java, ...)
    • a client-side application (React.js, Angular, ...)
  • Flows where API interaction is required (mobile app, Smart TV, ...)

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

Two login methods are supported:

  • password for signing in with an email / username and password;
  • oidc for signing in using a social sign in provider such as Google or Facebook (check out the set up guide).

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

config.yml
selfservice:
methods:
password:
enabled: true
oidc:
enabled: true
# ...

Initialize login flow

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 and ui.bar.com will work;
  • kratos.bar.com and bar.com will work;
  • kratos.bar.com and not-ar.com will not work.

The first step is to initialize the Login Flow. This allows pre-login hooks to run, set up Anti-CSRF tokens, and more.

Login for server-side browser clients

The Login Flow for browser clients relies on HTTP redirects between Ory Identities, your Login 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.

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

curl -s -i -X GET \
-H "Accept: text/html" \
https://playground.projects.oryapis.com/self-service/login/browser

HTTP/2 303
date: Fri, 09 Jul 2021 10:23:52 GMT
content-type: text/html; charset=utf-8
content-length: 121
location: https://playground.projects.oryapis.com/hosted/login?flow=3fc63726-8461-43f4-974a-5579ff4174f1
cache-control: private, no-cache, no-store, must-revalidate
set-cookie: aHR0cHM6Ly9wbGF5Z3JvdW5kLnByb2plY3RzLm9yeWFwaXMuY29tL2FwaS9rcmF0b3MvcHVibGlj_csrf_token=Lk9swSOlimGS9LI5HslOyEKGL4hMQzWHnwnQpm9HGAA=; 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/login?flow=3fc63726-8461-43f4-974a-5579ff4174f1">See Other</a>.

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

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

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

Login for client-side (AJAX) browser clients

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

To initialize the Login Flow, call the login initialization endpoint and set Accept: application/json:

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

> GET /self-service/login/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 10:25:12 GMT
< content-type: application/json; charset=utf-8
< content-length: 1359
< cache-control: private, no-cache, no-store, must-revalidate
< set-cookie: aHR0cHM6Ly9wbGF5Z3JvdW5kLnByb2plY3RzLm9yeWFwaXMuY29tL2FwaS9rcmF0b3MvcHVibGlj_csrf_token=UlKMcLe00G8B9GjC7D1I5rvQ6P79Q0YpzKb4lo7uLtw=; 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": "ff0c97c4-a7bb-49a5-a8a6-ebf174877fa5",
"type": "browser",
"expires_at": "2021-07-09T11:25:12.136099226Z",
"issued_at": "2021-07-09T10:25:12.136099226Z",
"request_url": "http://playground.projects.oryapis.com/self-service/login/browser",
"ui": {
"action": "https://playground.projects.oryapis.com/self-service/login?flow=ff0c97c4-a7bb-49a5-a8a6-ebf174877fa5",
"method": "POST",
"nodes": [ /* ... */ ]
},
"created_at": "2021-07-09T10:25:12.137554Z",
"updated_at": "2021-07-09T10:25:12.137554Z",
"forced": false
}

Login 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 Login Flow for API clients doesn't use HTTP Redirects and can be summarized as follows:

To initialize the API flow, the client calls the API-flow initialization endpoint (REST API Reference) which returns a JSON response:

curl -s -X GET \
-H "Accept: application/json" \
https://playground.projects.oryapis.com/self-service/login/api | jq

{
"id": "9d17f37b-b60b-44f5-9812-4829a89810f7",
"type": "api",
"expires_at": "2021-07-09T11:26:04.019418543Z",
"issued_at": "2021-07-09T10:26:04.019418543Z",
"request_url": "http://playground.projects.oryapis.com/self-service/login/api",
"ui": {
"action": "https://playground.projects.oryapis.com/self-service/login?flow=9d17f37b-b60b-44f5-9812-4829a89810f7",
"method": "POST",
"nodes": [ /* ... */ ]
}
}

Login Flow Payloads

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

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

curl -s -X GET \
-H "Accept: application/json" \
"https://playground.projects.oryapis.com/self-service/login/flows?id=$flowId" | jq

{
"id": "d8c4a887-ccb0-4a1a-882a-7708e0bf3501",
"type": "api",
"expires_at": "2021-07-09T11:26:50.2356Z",
"issued_at": "2021-07-09T10:26:50.2356Z",
"request_url": "http://playground.projects.oryapis.com/self-service/login/api",
"ui": {
"action": "https://playground.projects.oryapis.com/self-service/login?flow=d8c4a887-ccb0-4a1a-882a-7708e0bf3501",
"method": "POST",
"nodes": [ /* ... */ ]
}
}

Login with username/email and password

Before you start

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

curl -H "Accept: application/json" -s \
'https://playground.projects.oryapis.com/self-service/self-service/login/flows?id=42e26bc5-8014-400c-b463-dc5c3738c242' | jq

{
"id": "42e26bc5-8014-400c-b463-dc5c3738c242",
"type": "browser",
"expires_at": "2021-04-28T10:04:44.506336771Z",
"issued_at": "2021-04-28T09:54:44.506336771Z",
"request_url": "https://playground.projects.oryapis.com/self-service/login/browser",
"ui": {
"action": "https://playground.projects.oryapis.com/self-service/login?flow=42e26bc5-8014-400c-b463-dc5c3738c242",
"method": "POST",
"nodes": [
{
"type": "input",
"group": "default",
"attributes": {
"name": "csrf_token",
"type": "hidden",
"value": "8RygCHIdyMXVc3jxIAf/6uAuv/jBJLo5mt6nXdcB/JOzncLRu5510BNZNOjvA6Soii504s1Yq/sgvfOXxzck6g==",
"required": true,
"disabled": false
},
"messages": null,
"meta": {}
},
{
"type": "input",
"group": "password",
"attributes": {
"name": "identifier",
"type": "text",
"value": "",
"required": true,
"disabled": false
},
"messages": null,
"meta": {
"label": {
"id": 1070004,
"text": "ID",
"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": 1010001,
"text": "Sign in",
"type": "info",
"context": {}
}
}
}
]
},
"forced": false
}

Login with Google, Facebook, GitHub, ..., OpenID Connect / OAuth 2.0

Before you start

Check out the social sign-in documentation and learn how to set up this method!

The social sign-in method (oidc) enables you to use

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/self-service/self-service/login/flows?id=d6340737-89f2-4b01-a848-79007de6f430' \
| jq

{
"id": "d6340737-89f2-4b01-a848-79007de6f430",
"type": "browser",
"expires_at": "2021-04-28T11:05:01.382156Z",
"issued_at": "2021-04-28T10:05:01.382156Z",
"request_url": "https://playground.projects.oryapis.com/self-service/login/browser",
"ui": {
"action": "http://127.0.0.1:4455/self-service/login?flow=d6340737-89f2-4b01-a848-79007de6f430",
"method": "POST",
"nodes": [
{
"type": "input",
"group": "default",
"attributes": {
"name": "csrf_token",
"type": "hidden",
"value": "7Y2PgQlka1Zjn8wVRUnvCqeSs9hzsUECnk9YjULACyMIYXbCwAVcMDpeFf33tECgowwmA3ZNTozZMKxy2jUSSA==",
"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": 1010002,
"text": "Sign in with github",
"type": "info",
"context": {
"provider": "github"
}
}
}
}
]
},
"forced": false
}

Login form validation

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

  • An HTTP 303 See Other redirect pointing to the Login UI for Browser Clients;
  • An application/json response for API Clients and Client-Side Browser applications for example for Single Page Apps.

Login with username/email and password

To complete the login, the end-user fills out their identifier (username, email, phone number, ...) and the password. Possible validation errors include a missing identifier or password, or invalid credentials:

User Login HTML Form with validation errors

When validation errors happen, browser clients receive a HTTP 303 See Other redirect to the Login UI, containing the Login Flow ID which includes the error payloads.

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

Login 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 (such as Google) and giving consent.

note

If the user has never signed in with the given provider before, a new account will be created. It's also possible to link upstream identities (such as Google profile) with an existing Ory Identities identity.

A possible validation error is a missing ID Token:

curl -s -H "Accept: application/json" \
'https://playground.projects.oryapis.com/self-service/login/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"
}
]

Successful login

Completing the login behaves differently for Browser and API Clients.

Server-side browser clients

When the login is completed successfully, Ory Identities responds with a HTTP 303 Redirect to the configured redirect URL. Alongside the HTTP 303 Redirect is a Set-Cookie header which contains the Ory 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/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/schemas/default",
"traits": {
"email": "[email protected]",
"name": {
"first": "foo",
"last": "user"
}
},
"verifiable_addresses": [
{
"id": "81bbdeae-6333-42f2-877e-26c78acb6ea5",
"value": "[email protected]",
"verified": false,
"via": "email",
"status": "pending",
"verified_at": null
}
],
"recovery_addresses": [
{
"id": "596c1db4-ccaa-4f4e-9623-cb7e768026ad",
"value": "[email protected]",
"via": "email"
}
]
}
}

Client-side browser clients (AJAX)

When the login is completed successfully, Ory Identities responds with a HTTP 200 OK which includes a Set-Cookie header and a JSON response (see below):

{
"session": {
"id": "8f660ce3-69ec-4aeb-9fda-f9230dc3243f",
"active": true,
"expires_at": "2020-08-25T13:42:15.7411522Z",
"authenticated_at": "2020-08-24T13:42:15.7411522Z",
"issued_at": "2020-08-24T13:42:15.7412042Z",
"identity": {
"id": "bf32596a-f853-47c4-91e6-a3f41cf4949d",
"schema_id": "default",
"schema_url": "https://playground.projects.oryapis.com/schemas/default",
"traits": {
"email": "[email protected]",
"name": {
"last": "User",
"first": "API"
}
},
"verifiable_addresses": [
{
"id": "f877db6c-7dfb-45e3-bbeb-ac8349348128",
"value": "[email protected]",
"verified": false,
"via": "email",
"verified_at": null,
"expires_at": "2020-08-24T14:35:59.125873Z"
}
],
"recovery_addresses": [
{
"id": "065a908c-82be-4110-bf67-9910f36242b7",
"value": "[email protected]",
"via": "email"
}
]
}
}
}

API clients

For API Clients, Ory Identities responds with a JSON payload which includes the identity which just authenticated, the session, and the Ory Session Token:

# Inits a Login Flow
actionUrl=$(\
curl -s -X GET -H "Accept: application/json" \
"https://playground.projects.oryapis.com/self-service/login/api" \
| jq -r '.ui.action'\
)

# Complete Login Flow with password method
curl -s -X POST -H "Accept: application/json" -H "Content-Type: application/json" \
-d '{"identifier": "[email protected]", "password": "iohuasf0897zAJHf", "method": "password"}' \
"$actionUrl" | jq

{
"session_token": "oFZzgLpsacUpUy2cvQPtrGa2046WcXCR",
"session": {
"id": "8f660ce3-69ec-4aeb-9fda-f9230dc3243f",
"active": true,
"expires_at": "2020-08-25T13:42:15.7411522Z",
"authenticated_at": "2020-08-24T13:42:15.7411522Z",
"issued_at": "2020-08-24T13:42:15.7412042Z",
"identity": {
"id": "bf32596a-f853-47c4-91e6-a3f41cf4949d",
"schema_id": "default",
"schema_url": "https://playground.projects.oryapis.com/schemas/default",
"traits": {
"email": "[email protected]",
"name": {
"last": "User",
"first": "API"
}
},
"verifiable_addresses": [
{
"id": "f877db6c-7dfb-45e3-bbeb-ac8349348128",
"value": "[email protected]",
"verified": false,
"via": "email",
"verified_at": null,
"expires_at": "2020-08-24T14:35:59.125873Z"
}
],
"recovery_addresses": [
{
"id": "065a908c-82be-4110-bf67-9910f36242b7",
"value": "[email protected]",
"via": "email"
}
]
}
}
}

The Ory Session Token can be checked at the http://127.0.0.1/sessions/whoami endpoint:

curl -s -H "Authorization: Bearer svX8bE9HTiVpMr7r55TtKtcOkLRhAq1a" \
https://playground.projects.oryapis.com/sessions/whoami | jq

{
"id": "d09fc470-9e11-4e70-855f-0dc1aee7e501",
"active": true,
"expires_at": "2020-09-05T10:52:52.1350455Z",
"authenticated_at": "2020-09-04T10:52:52.1472702Z",
"issued_at": "2020-09-04T10:52:52.1350737Z",
"identity": {
"id": "9ee8fb81-9d5c-47a7-9cee-28a0f64dccbb",
"schema_id": "default",
"schema_url": "https://playground.projects.oryapis.com/schemas/default",
"traits": {
"email": "[email protected]",
"name": {
"first": "API",
"last": "User"
}
},
"verifiable_addresses": [
{
"id": "3ba119c6-4e9a-466c-8910-40b238229aa6",
"value": "[email protected]",
"verified": false,
"via": "email",
"status": "pending",
"verified_at": null
}
],
"recovery_addresses": [
{
"id": "9dec10d0-1079-4a5d-b1be-cd15418c640a",
"value": "[email protected]",
"via": "email"
}
]
}
}

Refreshing a session

In some cases it's required to refresh a login session. This is the case when updating one's password. Refreshing a session updates the authenticated_at time.

info

Refreshing a session won't log the user out, unless another user signs in.

To refresh a session, append ?refresh=true to:

  • /self-service/login/browser for browser Clients (for example https://playground.projects.oryapis.com/self-service/login/browser?refresh=true)
  • /self-service/login/api for API Clients (for example https://playground.projects.oryapis.com/self-service/login/api?refresh=true)

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

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

User Login HTML Form

Hooks

Ory Identities allows you to configure hooks that run before and after a Login Flow. This may be helpful if you'd like to restrict logins to IPs coming from your internal network or other logic.

For more information about hooks please read the Hook Documentation.