Login
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:
Supported login methods are
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. Visit the Social Sign In guide.passkey
for signing in with a Passkey. Visit the Passkey guide.code
for signing in with a code via Email or SMS. Visit the Code via Email / SMS guide.webauthn
(legacy) for signing in with Webauthn. This method is supported for backwards compatibility, use Passkey instead.
- Ory Network
- Ory Kratos
You can choose which methods to use in the Ory Identities configuration or in the Console UI.
selfservice:
methods:
password:
enabled: true
oidc:
enabled: true
# ...
You can choose which methods to use in the Ory Identities configuration:
selfservice:
methods:
password:
enabled: true
oidc:
enabled: true
# ...
Initialize login 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
andapp.bar.com
will work;ory.bar.com
andbar.com
will work;ory.bar.com
andnot-bar.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:
- Ory Network
- Ory Kratos
The Ory Network 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 Identities config:
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.
info
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 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
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 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
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 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
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
- GitHub;
- Apple;
- GitLab;
- Google;
- Facebook;
- Ory OAuth2 & OpenID Connect (Ory Hydra);
- Keycloak;
- and every other OpenID Connect Certified Provider
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:
- Browser UI
- Missing Email
- Wrong Credentials

curl -H "Accept: application/json" -s \
'https://playground.projects.oryapis.com/self-service/login/flows?id=a2d6e166-5153-42a6-8943-ef99eba8ab2e' \
| jq -r '.ui.nodes[] | select(.attributes.name=="identifier")'
{
"type": "input",
"group": "password",
"attributes": {
"name": "identifier",
"type": "text",
"value": "",
"required": true,
"disabled": false
},
"messages": [
{
"id": 4000001,
"text": "length must be >= 1, but got 0",
"type": "error"
}
],
"meta": {
"label": {
"id": 1070004,
"text": "ID",
"type": "info"
}
}
}
curl -H "Accept: application/json" -s \
'https://playground.projects.oryapis.com/self-service/login/flows?id=a2d6e166-5153-42a6-8943-ef99eba8ab2e' \
| jq -r '.ui.messages'
[
{
"id": 4000006,
"text": "The provided credentials are invalid, check for spelling mistakes in your password or username, email address, or phone number.",
"type": "error",
"context": {}
}
]
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.
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:
- 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": "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 (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": "api@user.org",
"name": {
"last": "User",
"first": "API"
}
},
"verifiable_addresses": [
{
"id": "f877db6c-7dfb-45e3-bbeb-ac8349348128",
"value": "api@user.org",
"verified": false,
"via": "email",
"verified_at": null,
"expires_at": "2020-08-24T14:35:59.125873Z"
}
],
"recovery_addresses": [
{
"id": "065a908c-82be-4110-bf67-9910f36242b7",
"value": "api@user.org",
"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": "api@user.org", "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": "api@user.org",
"name": {
"last": "User",
"first": "API"
}
},
"verifiable_addresses": [
{
"id": "f877db6c-7dfb-45e3-bbeb-ac8349348128",
"value": "api@user.org",
"verified": false,
"via": "email",
"verified_at": null,
"expires_at": "2020-08-24T14:35:59.125873Z"
}
],
"recovery_addresses": [
{
"id": "065a908c-82be-4110-bf67-9910f36242b7",
"value": "api@user.org",
"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": "api@user.org",
"name": {
"first": "API",
"last": "User"
}
},
"verifiable_addresses": [
{
"id": "3ba119c6-4e9a-466c-8910-40b238229aa6",
"value": "api@user.org",
"verified": false,
"via": "email",
"status": "pending",
"verified_at": null
}
],
"recovery_addresses": [
{
"id": "9dec10d0-1079-4a5d-b1be-cd15418c640a",
"value": "api@user.org",
"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.
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 examplehttps://playground.projects.oryapis.com/self-service/login/browser?refresh=true
)/self-service/login/api
for API Clients (for examplehttps://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:
- Browser UI
- Golang (API Flow)
- Express.js
- React.js
- React Native

import {
defaultConfig,
getUrlForFlow,
isQuerySet,
logger,
redirectOnSoftError,
RouteCreator,
RouteRegistrator,
} from "../pkg"
import { LoginFlow } from "@ory/client"
import { UserAuthCard } from "@ory/elements-markup"
import path from "path"
import { URLSearchParams } from "url"
export const createLoginRoute: RouteCreator =
(createHelpers) => async (req, res, next) => {
res.locals.projectName = "Sign in"
const {
flow,
aal = "",
refresh = "",
return_to = "",
organization = "",
via = "",
login_challenge,
} = req.query
const { frontend, kratosBrowserUrl, logoUrl, extraPartials } =
createHelpers(req, res)
const initFlowQuery = new URLSearchParams({
aal: aal.toString(),
refresh: refresh.toString(),
return_to: return_to.toString(),
organization: organization.toString(),
via: via.toString(),
})
if (isQuerySet(login_challenge)) {
logger.debug("login_challenge found in URL query: ", { query: req.query })
initFlowQuery.append("login_challenge", login_challenge)
}
const initFlowUrl = getUrlForFlow(kratosBrowserUrl, "login", 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
}
// It is probably a bit strange to have a logout URL here, however this screen
// is also used for 2FA flows. If something goes wrong there, we probably want
// to give the user the option to sign out!
const getLogoutUrl = async (loginFlow: LoginFlow) => {
let logoutUrl = ""
try {
logoutUrl = await frontend
.createBrowserLogoutFlow({
cookie: req.header("cookie"),
returnTo:
(return_to && return_to.toString()) || loginFlow.return_to || "",
})
.then(({ data }) => data.logout_url)
return logoutUrl
} catch (err) {
logger.error("Unable to create logout URL", { error: err })
}
}
const redirectToVerificationFlow = (loginFlow: LoginFlow) => {
// we will create a new verification flow and redirect the user to the verification page
frontend
.createBrowserVerificationFlow({
returnTo:
(return_to && return_to.toString()) || loginFlow.return_to || "",
})
.then(({ headers, data: verificationFlow }) => {
// we need the csrf cookie from the verification flow
if (headers["set-cookie"]) {
res.setHeader("set-cookie", headers["set-cookie"])
}
// encode the verification flow id in the query parameters
const verificationParameters = new URLSearchParams({
flow: verificationFlow.id,
message: JSON.stringify(loginFlow.ui.messages),
})
const baseUrl = req.path.split("/")
// get rid of the last part of the path (e.g. "login")
baseUrl.pop()
// redirect to the verification page with the custom message
res.redirect(
303,
// join the base url with the verification path
path.join(
req.baseUrl,
"verification?" + verificationParameters.toString(),
),
)
})
.catch(
redirectOnSoftError(
res,
next,
getUrlForFlow(
kratosBrowserUrl,
"verification",
new URLSearchParams({
return_to:
(return_to && return_to.toString()) ||
loginFlow.return_to ||
"",
}),
),
),
)
}
return frontend
.getLoginFlow({ id: flow, cookie: req.header("cookie") })
.then(async ({ data: flow }) => {
if (flow.ui.messages && flow.ui.messages.length > 0) {
// the login requires that the user verifies their email address before logging in
if (flow.ui.messages.some(({ id }) => id === 4000010)) {
// we will create a new verification flow and redirect the user to the verification page
return redirectToVerificationFlow(flow)
}
}
// Render the data using a view (e.g. Jade Template):
const initRegistrationQuery = new URLSearchParams({
return_to:
(return_to && return_to.toString()) || flow.return_to || "",
})
if (flow.oauth2_login_request?.challenge) {
initRegistrationQuery.set(
"login_challenge",
flow.oauth2_login_request.challenge,
)
}
let initRecoveryUrl = ""
const initRegistrationUrl = getUrlForFlow(
kratosBrowserUrl,
"registration",
initRegistrationQuery,
)
if (!flow.refresh) {
initRecoveryUrl = getUrlForFlow(
kratosBrowserUrl,
"recovery",
new URLSearchParams({
return_to:
(return_to && return_to.toString()) || flow.return_to || "",
}),
)
}
let logoutUrl: string | undefined = ""
if (flow.requested_aal === "aal2" || flow.refresh) {
logoutUrl = await getLogoutUrl(flow)
}
res.render("login", {
nodes: flow.ui.nodes,
card: UserAuthCard(
{
flow,
flowType: "login",
cardImage: logoUrl,
additionalProps: {
forgotPasswordURL: initRecoveryUrl,
signupURL: initRegistrationUrl,
logoutURL: logoutUrl,
loginURL: initFlowUrl,
},
},
{ locale: res.locals.lang },
),
extraPartial: extraPartials?.login,
extraContext: res.locals.extraContext,
})
})
.catch(redirectOnSoftError(res, next, initFlowUrl))
}
export const registerLoginRoute: RouteRegistrator = (
app,
createHelpers = defaultConfig,
) => {
app.get("/login", createLoginRoute(createHelpers))
}
- Login View
- Generic Form View
- Example Input Form Element
<div id="login">
{{{card}}}
{{> js_setup nodes=nodes}}
{{#if extraPartial }}
{{> (extraPartial) }}
{{/if}}
</div>
404: Not Found
404: Not Found
import { LoginFlow, UpdateLoginFlowBody } from "@ory/client"
import { CardTitle } from "@ory/themes"
import { AxiosError } from "axios"
import type { NextPage } from "next"
import Head from "next/head"
import Link from "next/link"
import { useRouter } from "next/router"
import { useEffect, useState } from "react"
import { ActionCard, CenterLink, LogoutLink, Flow, MarginCard } from "../pkg"
import { handleGetFlowError, handleFlowError } from "../pkg/errors"
import ory from "../pkg/sdk"
const Login: NextPage = () => {
const [flow, setFlow] = useState<LoginFlow>()
// Get ?flow=... from the URL
const router = useRouter()
const {
return_to: returnTo,
flow: flowId,
// Refresh means we want to refresh the session. This is needed, for example, when we want to update the password
// of a user.
refresh,
// AAL = Authorization Assurance Level. This implies that we want to upgrade the AAL, meaning that we want
// to perform two-factor authentication/verification.
aal,
} = router.query
// This might be confusing, but we want to show the user an option
// to sign out if they are performing two-factor authentication!
const onLogout = LogoutLink([aal, refresh])
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
.getLoginFlow({ id: String(flowId) })
.then(({ data }) => {
setFlow(data)
})
.catch(handleGetFlowError(router, "login", setFlow))
return
}
// Otherwise we initialize it
ory
.createBrowserLoginFlow({
refresh: Boolean(refresh),
aal: aal ? String(aal) : undefined,
returnTo: returnTo ? String(returnTo) : undefined,
})
.then(({ data }) => {
setFlow(data)
})
.catch(handleFlowError(router, "login", setFlow))
}, [flowId, router, router.isReady, aal, refresh, returnTo, flow])
const onSubmit = (values: UpdateLoginFlowBody) =>
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(`/login?flow=${flow?.id}`, undefined, { shallow: true })
.then(() =>
ory
.updateLoginFlow({
flow: String(flow?.id),
updateLoginFlowBody: values,
})
// We logged in successfully! Let's bring the user home.
.then(() => {
if (flow?.return_to) {
window.location.href = flow?.return_to
return
}
router.push("/")
})
.then(() => {})
.catch(handleFlowError(router, "login", 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>Sign in - Ory NextJS Integration Example</title>
<meta name="description" content="NextJS + React + Vercel + Ory" />
</Head>
<MarginCard>
<CardTitle>
{(() => {
if (flow?.refresh) {
return "Confirm Action"
} else if (flow?.requested_aal === "aal2") {
return "Two-Factor Authentication"
}
return "Sign In"
})()}
</CardTitle>
<Flow onSubmit={onSubmit} flow={flow} />
</MarginCard>
{aal || refresh ? (
<ActionCard>
<CenterLink data-testid="logout-link" onClick={onLogout}>
Log out
</CenterLink>
</ActionCard>
) : (
<>
<ActionCard>
<Link href="/registration" passHref>
<CenterLink>Create account</CenterLink>
</Link>
</ActionCard>
<ActionCard>
<Link href="/recovery" passHref>
<CenterLink>Recover your account</CenterLink>
</Link>
</ActionCard>
</>
)}
</>
)
}
export default Login
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 performLogin() *ory.SuccessfulNativeLogin {
ctx := context.Background()
// Create a temporary user
email, password := pkg.RandomCredentials()
_, _ = pkg.CreateIdentityWithSession(client, email, password)
// Initialize the flow
flow, res, err := client.FrontendAPI.CreateNativeLoginFlow(ctx).Execute()
pkg.SDKExitOnError(err, res)
// If you want, print the flow here:
//
// pkg.PrintJSONPretty(flow)
// Submit the form
result, res, err := client.FrontendAPI.UpdateLoginFlow(ctx).Flow(flow.Id).UpdateLoginFlowBody(
ory.UpdateLoginFlowWithPasswordMethodAsUpdateLoginFlowBody(&ory.UpdateLoginFlowWithPasswordMethod{
Method: "password",
Password: password,
PasswordIdentifier: &email,
}),
).Execute()
pkg.SDKExitOnError(err, res)
return result
}
func main() {
pkg.PrintJSONPretty(
performLogin(),
)
}
// This file renders the login screen.
import { LoginFlow, UpdateLoginFlowBody } from "@ory/client"
import { useFocusEffect } from "@react-navigation/native"
import { StackScreenProps } from "@react-navigation/stack"
import React, { useContext, useState } from "react"
import { SessionContext } from "../../helpers/auth"
import { logSDKError } from "../../helpers/axios"
import { 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"
type Props = StackScreenProps<RootStackParamList, "Login">
const Login = ({ navigation, route }: Props) => {
const { project } = useContext(ProjectContext)
const { setSession, sessionToken } = useContext(AuthContext)
const [flow, setFlow] = useState<LoginFlow | undefined>(undefined)
const initializeFlow = () =>
newOrySdk(project)
.createNativeLoginFlow({
aal: route.params.aal,
refresh: route.params.refresh,
xSessionToken: sessionToken,
// 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,
})
.then(({ data: f }) => setFlow(f))
.catch(logSDKError)
const refetchFlow = () =>
newOrySdk(project)
.getLoginFlow({ id: flow!.id })
.then(({ data: f }) => setFlow({ ...flow, ...f })) // merging ensures we don't lose the code
.catch(logSDKError)
// When the component is mounted, we initialize a new use login flow:
useFocusEffect(
React.useCallback(() => {
initializeFlow()
return () => {
setFlow(undefined)
}
}, [project]),
)
const setSessionAndRedirect = (session: SessionContext) => {
setSession(session)
setTimeout(() => {
navigation.navigate("Home")
}, 100)
}
// This will update the login flow with the user provided input:
const onSubmit = (payload: UpdateLoginFlowBody) =>
flow
? newOrySdk(project)
.updateLoginFlow({
flow: flow.id,
updateLoginFlowBody: payload,
xSessionToken: sessionToken,
})
.then(({ data }) => Promise.resolve(data as SessionContext))
// Looks like everything worked and we have a session!
.then(setSessionAndRedirect)
.catch(
handleFormSubmitError(
flow,
setFlow,
initializeFlow,
setSessionAndRedirect,
refetchFlow,
),
)
: Promise.resolve()
return (
<AuthLayout>
<StyledCard>
<AuthSubTitle>Sign in to your account</AuthSubTitle>
<SelfServiceFlow flow={flow} onSubmit={onSubmit} />
</StyledCard>
<NavigationCard
testID="nav-signup"
description="Need an account?"
cta="Sign up!"
onPress={() => navigation.navigate("Registration")}
/>
<NavigationCard
testID="nav-recover"
description="Forgot your password?"
cta="Reset it!"
onPress={() => navigation.navigate("Recovery")}
/>
<ProjectPicker />
</AuthLayout>
)
}
export default Login
- Generic Form View
- Example Input Form Element
404: Not Found
404: Not Found
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.