<@U04UQ68083H> I running the command ```docker run...
# ory-copilot
q
@chilly-king-10285 I running the command
Copy code
docker run --rm -it -e ORY_PROJECT_API_KEY=<project_key> -e ORY_PROJECT=<projectID> oryd/ory tunnel --dev <http://localhost:5173>
But it still asks me to do a browser login
c
A message from kapa.ai
Based on the information provided in the knowledge sources, it appears that you're encountering an issue with the Ory Tunnel command. The Ory Tunnel is designed to allow your application and Ory to run on the same domain during local development. However, the command you're using seems to be slightly different from the recommended format. To address your issue, I suggest trying the following command instead:
Copy code
ory tunnel --project <project-id-or-slug> --dev <http://localhost:5173>
This command format is consistent with the examples provided in the [Ory Tunnel documentation](https://www.ory.sh/docs/cli/ory-tunnel). If you want to use API keys for authentication in automated environments (like CI/CD pipelines) to avoid browser login, you should set the API keys as environment variables. Here's how you can do it:
Copy code
export ORY_PROJECT_API_KEY=<project-api-key> ory tunnel --project <project-id-or-slug> --dev --quiet <http://localhost:5173>
The
--quiet
flag prevents the browser window from opening, as mentioned in the [Ory Tunnel documentation](https://www.ory.sh/docs/cli/ory-tunnel#connecting-in-automated-environments). If you're still experiencing issues after trying these suggestions, it might be helpful to check if you're using the latest version of the Ory CLI and ensure that your project ID and API key are correct.
q
@chilly-king-10285 I want a function to be called on successful logins from the Experience UI
c
A message from kapa.ai
To call a function (such as a webhook or custom logic) on successful logins from the Ory Account Experience UI, you should use Ory Actions (also referred to as "hooks" in some documentation). Ory Actions allow you to trigger external logic or webhooks in response to events like user login, regardless of whether the login happens via the Experience UI or a custom UI. How to trigger a function on successful login: 1. Configure an "after login" action using the Ory Console or the Ory CLI. - The most flexible action is the
web_hook
, which lets you call any external service or function you want. 2. Set up your webhook endpoint to receive and process the event. Example: Add a webhook to be called after login You can add a webhook using the Ory CLI like this:
Copy code
bash ory patch identity-config <project_id> \   --add '/selfservice/flows/login/after/hooks=[{"hook": "web_hook", "config": {"url": "<https://your-webhook-endpoint>"}}]'
This will call your function (the webhook) every time a user logs in successfully, including logins from the Experience UI. Where to configure: - In the Ory Console: Go to Authentication → Actions and add an "after login" action with the type "webhook" and your endpoint URL. - Or use the CLI as shown above. References: - [Ory Actions overview and configuration](https://www.ory.sh/docs/kratos/hooks/configure-hooks#available-actions) - [Customize login and registration behavior](https://www.ory.sh/docs/identities/sign-in/actions#customize-login-and-registration-behavior) If you need to trigger more complex logic, your webhook endpoint can execute any function you want when it receives the login event. Note: - The Experience UI is fully compatible with Ory Actions/hooks, so this approach works regardless of which UI is used. - If you need to trigger the function only for specific login methods (e.g., password, OIDC), you can further customize the hook configuration as described in the documentation. Let me know if you need an example webhook payload or more details!
q
@chilly-king-10285 is it possible to deactivate a user, and potenatially reactivate it?
c
A message from kapa.ai
Yes, it is possible to deactivate a user (identity) in Ory, and you can also reactivate them later. Ory supports two main identity states: - active: The identity can use self-service flows such as sign-in. - inactive: The identity cannot use self-service flows. When an identity is set to
inactive
, they are prevented from logging in. You can change the state back to
active
to allow the user to log in again. This can be done using the Ory SDKs or API by patching the identity's state. Here is an example using the JavaScript SDK:
Copy code
js import { Configuration, IdentityApi } from "@ory/client" import { JsonPatchOpEnum } from "@ory/client/api"  const identity = new IdentityApi(   new Configuration({     basePath: `https://${process.env.ORY_PROJECT_SLUG}.<http://projects.oryapis.com|projects.oryapis.com>`,     accessToken: process.env.ORY_API_KEY,   }), )  export async function setState(   identityId: string,   state: "active" | "inactive", ) {   return await identity     .patchIdentity({       id: identityId,       jsonPatch: [         {           op: JsonPatchOpEnum.Replace,           value: state,           path: "/state",         },       ],     })     .then(({ data }) => data) }
You can use a similar approach with other SDKs or directly via the API. This allows you to both deactivate and reactivate users as needed [Identity state](https://www.ory.sh/docs/identities/model/activate-deactivate-user-login-state/) [Disable User and Revoke all sessions](https://github.com/ory/network/issues/415).
q
@chilly-king-10285 I create users with
Copy code
async def create_user(self, email: str, user_id: str, company: Company) -> OryUser:
        request_body = {
            "schema_id": "<preset://email>",
            "traits": {
                "email": email
            },
            "metadata_public": {
                "userId": user_id,
                "tenantId": company.tenant_name
            }
        }
        return await <http://self.httpClient.post|self.httpClient.post>(
            f"{self.ory_base_uri}/admin/identities",
            data=request_body,
            headers={'Authorization': f"Bearer {self.config.ory_api_key}", 'Content-Type': 'application/json'},
            map_to=OryUser
        )
how do I get it to send an invite link via email to the user in question
c
A message from kapa.ai
To send an invite link via email to a user after creating them, you need to: 1. Create the user (as you are already doing). 2. Generate a recovery link for that user using the Admin API. 3. Manually send the recovery link to the user's email address. Currently, Ory does not automatically send the invite/recovery link via email when you create a user or generate a recovery link through the Admin API. You must handle the email sending yourself after obtaining the link [Invite users](https://www.ory.sh/docs/kratos/manage-identities/invite-users/) [Magic links](https://www.ory.sh/docs/kratos/manage-identities/account-recovery/#magic-links). Example flow: 1. Create the user (as in your code). 2. Generate the recovery link:
Copy code
python    response = await <http://self.httpClient.post|self.httpClient.post>(        f"{self.ory_base_uri}/admin/recovery/link",        data={            "identity_id": created_user.id,            "expires_in": "12h"        },        headers={            'Authorization': f"Bearer {self.config.ory_api_key}",            'Content-Type': 'application/json'        }    )    recovery_link = response['recovery_link']
3. Send the recovery link to the user's email using your own email service. > It is currently not possible to send the recovery link directly to a user's email, this feature is tracked as [#595](https://github.com/ory/kratos/issues/595) [Invite users](https://www.ory.sh/docs/kratos/manage-identities/invite-users/). Summary: You must send the invite/recovery link yourself after generating it with the Admin API. Ory does not send this email automatically.
q
@chilly-king-10285 can I filter a user by trait?
c
A message from kapa.ai
I'm sorry, but the knowledge sources do not explicitly mention support for filtering users (identities) by trait (such as email or custom fields) using the Ory Admin API. The available documentation and a related GitHub issue indicate that while you can list identities and filter by credential type (like username or email), there is currently no built-in way to filter identities by arbitrary traits directly via the API. Workarounds suggested include fetching all identities and filtering on the application side, or making parallel requests for known IDs, but these are not ideal for large datasets [Retrieve subset of identities through admin API](https://github.com/ory/network/issues/305).
q
@chilly-king-10285 how do I change a user's password via the API
c
A message from kapa.ai
To change a user's password via the API in Ory, you should use the Settings Flow with the
password
method. This is a self-service flow designed for end-users to update their own passwords. The flow can be initiated for both browser-based and API clients. For API clients (e.g., mobile apps, backend services): 1. Initialize the settings flow (authenticated as the user whose password you want to change). 2. Submit the new password using the flow's action URL and the
password
method. Example API call:
Copy code
bash curl -s -X POST -H "Accept: application/json" -H "Content-Type: application/json" \     -H "Authorization: bearer $sessionToken" \     -d '{"password": "newPassword123", "method": "password"}' \     "$actionUrl"
-
$sessionToken
is the user's session token. -
$actionUrl
is obtained from the initialized settings flow. - The payload must include the new password and set
"method": "password"
. Note: - This flow is intended for users to change their own passwords. There is no documented admin API endpoint to directly set or change another user's password after creation; password changes are handled through the self-service settings flow [Settings and profile updates](https://www.ory.sh/docs/kratos/self-service/flows/user-settings/) [Update password](https://www.ory.sh/docs/kratos/self-service/flows/user-settings/#update-password-1). If you need to reset a user's password as an admin (for example, if they forgot it), you should use the account recovery flow instead. If you need to import or set a password during identity creation or update, you can do so by providing the password in the credentials field, but this is not intended for regular password changes [Importing credentials](https://www.ory.sh/docs/kratos/manage-identities/import-user-accounts-identities/#importing-credentials).
q
@chilly-king-10285 how would I initiate this flow with
Copy code
export const ory = new FrontendApi(
  new Configuration({
    basePath: Config.getAuthBasePath(),
    baseOptions: {
      withCredentials: true,
    },
  }),
);
c
A message from kapa.ai
To initiate a settings flow (for example, to change a user's password) using the Ory JavaScript SDK and your
FrontendApi
instance, you should use the
createBrowserSettingsFlow
(for browser-based apps) or
createNativeSettingsFlow
(for native/mobile apps) method. These methods are the updated way to start a settings flow, as described in the SDK v1 upgrade guide [SDK V1 upgrade guide](https://www.ory.sh/docs/guides/upgrade/sdk-v1/#frontend-apis). Example for a browser-based app:
Copy code
js const flow = await ory.frontend.createBrowserSettingsFlow()   .then(({ data }) => data)
Example for a native app:
Copy code
js const flow = await ory.frontend.createNativeSettingsFlow()   .then(({ data }) => data)
After initializing the flow, you will receive a flow object containing the flow ID and the action URL. You can then use this information to submit the new password as described previously. References: - The method names and usage are confirmed in the [SDK V1 upgrade guide](https://www.ory.sh/docs/guides/upgrade/sdk-v1/#frontend-apis). - The flow for updating settings (including password) is described in the [settings flow documentation](https://www.ory.sh/docs/kratos/self-service/flows/user-settings/#user-and-profile-settings-for-server-side-browser-clients). If you need to fetch an existing flow (for example, after a redirect), use:
Copy code
js const flow = await ory.frontend.getSettingsFlow({ id: flowId })   .then(({ data }) => data)
This approach is fully supported by the Ory SDK and aligns with the documented flows.
q
@chilly-king-10285 I find the docs hard to follow. I created a settings flow with
Copy code
const flow = await ory.createBrowserSettingsFlow()
I get two nodes as a result of password group
Copy code
{
                "type": "input",
                "group": "password",
                "attributes": {
                    "name": "password",
                    "type": "password",
                    "required": true,
                    "autocomplete": "new-password",
                    "disabled": false,
                    "node_type": "input"
                },
                "messages": [],
                "meta": {
                    "label": {
                        "id": 1070001,
                        "text": "Password",
                        "type": "info"
                    }
                }
            },
            {
                "type": "input",
                "group": "password",
                "attributes": {
                    "name": "method",
                    "type": "submit",
                    "value": "password",
                    "disabled": false,
                    "node_type": "input"
                },
                "messages": [],
                "meta": {
                    "label": {
                        "id": 1070003,
                        "text": "Save",
                        "type": "info"
                    }
                }
            }
How do I reset the password now?
c
A message from kapa.ai
To reset the password after creating the settings flow and receiving the password nodes, you need to submit the form data to the
action
URL provided in the flow's
ui
object. The payload should include the new password and the method, as specified by the nodes. Here's how you can do it: 1. Extract the action URL and method from the flow: -
flow.ui.action
(the URL to POST to) -
flow.ui.method
(should be "POST") 2. Build the payload using the node attributes: - `"password"`: the new password you want to set - `"method"`: must be
"password"
3. Send the POST request:
Copy code
js await ory.updateSettingsFlow({   flow: flow.id,   updateSettingsFlowBody: {     password: "your-new-password",     method: "password"   } })
Or, if you are submitting directly to the action URL (for example, from a form):
Copy code
js fetch(flow.ui.action, {   method: flow.ui.method,   credentials: "include",   headers: {     "Content-Type": "application/json"   },   body: JSON.stringify({     password: "your-new-password",     method: "password"   }) })
Summary: - Use the
action
URL from the flow. - POST the new password and
"method": "password"
as the payload. This process is described in the [settings flow payloads documentation](https://www.ory.sh/docs/kratos/self-service/flows/user-settings/#settings-flow-payloads) and the [update password section](https://www.ory.sh/docs/kratos/self-service/flows/user-settings/#update-password-1). If you encounter validation errors (e.g., password too short), they will be returned in the flow's response, attached to the relevant node as messages [Update password](https://www.ory.sh/docs/kratos/self-service/flows/user-settings/#update-password-1).
q
@chilly-king-10285 I tried what you suggested and got this error back:
Copy code
{
  "error": {
    "id": "security_csrf_violation",
    "code": 403,
    "status": "Forbidden",
    "request": "8d739fa2-d7e8-96e2-9c74-91e84e9f2318",
    "reason": "Please retry the flow and optionally clear your cookies. The request was rejected to protect you from Cross-Site-Request-Forgery (CSRF) which could cause account takeover, leaking personal information, and other serious security issues.",
    "details": {
      "docs": "<https://www.ory.sh/kratos/docs/debug/csrf>",
      "hint": "The anti-CSRF cookie was found but the CSRF token was not included in the HTTP request body (csrf_token) nor in the HTTP Header (X-CSRF-Token).",
      "reject_reason": "The HTTP Cookie Header was set and a CSRF token was sent but they do not match. We recommend deleting all cookies for this domain and retrying the flow."
    },
    "message": "the request was rejected to protect you from Cross-Site-Request-Forgery"
  }
}
c
A message from kapa.ai
The error message you received indicates a CSRF (Cross-Site Request Forgery) violation. According to the knowledge sources, this happens when the anti-CSRF cookie is present, but the CSRF token is either missing from the request body or does not match the cookie value. Ory requires both the CSRF cookie and the CSRF token to be sent and to match for security reasons. How to fix this: 1. Include the CSRF token in your request body. - The CSRF token is provided as a hidden input field in the flow's UI nodes, typically named
csrf_token
. - You must extract this value from the flow and include it in your payload when submitting the password change. 2. Example:
Copy code
js    await ory.updateSettingsFlow({      flow: flow.id,      updateSettingsFlowBody: {        password: "your-new-password",        method: "password",        csrf_token: csrfTokenFromUiNode // <-- this must be set!      }    })
Where
csrfTokenFromUiNode
is the value from the node with
name: "csrf_token"
in
flow.ui.nodes
. 3. Why this is necessary: Ory uses the synchronizer token pattern for CSRF protection. The value in the hidden input (
csrf_token
) must match the anti-CSRF cookie sent by the browser. If they do not match, or if the token is missing, you will get a 403 error [Defenses against cross-site request forgery](https://www.ory.sh/docs/kratos/concepts/security/#defenses-against-cross-site-request-forgery) [Custom UI CSRF handling](https://www.ory.sh/docs/kratos/bring-your-own-ui/custom-ui-basic-integration/#debug). 4. If you continue to get this error: - Try clearing your cookies for the domain and retrying the flow, as mismatched or stale cookies can also cause this error. - Make sure you are sending all cookies and the CSRF token together in the same request. References: - [Ory Kratos CSRF troubleshooting](https://www.ory.sh/docs/kratos/debug/csrf) - [Bring-your-own-UI CSRF handling](https://www.ory.sh/docs/kratos/bring-your-own-ui/custom-ui-basic-integration/#debug) - [Security concepts: CSRF](https://www.ory.sh/docs/kratos/concepts/security/#defenses-against-cross-site-request-forgery) Summary: You must extract the
csrf_token
from the flow's UI nodes and include it in your request body when submitting the password change. This is required for CSRF protection in Ory.
q
@chilly-king-10285 my fetch looks like this
Copy code
await fetch(data.ui.action, {
      method: data.ui.method,
      credentials: "include",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        password: newPassword,
        method: "password",
        csrf_token: csrfNode.attributes.node_type === 'input' && csrfNode.attributes.value,
      })
    })
but I get a 403
c
A message from kapa.ai
Your fetch code is almost correct, but the 403 CSRF error suggests that either the CSRF token is missing/incorrect, or the cookies are not being sent or do not match the token. Here are the key points to check, based on the knowledge sources: 1. CSRF Token Extraction: Make sure you are extracting the CSRF token from the correct node. The CSRF token is found in a node with
attributes.name === "csrf_token"
and
attributes.type === "hidden"
. Double-check that
csrfNode
is the correct node and that
csrfNode.attributes.value
is the actual token value. 2. Cookies: The request must include the session and CSRF cookies. Using
credentials: "include"
is correct for browser fetch, but ensure that the cookies are actually present in the browser for the domain you are calling. If you are running on localhost with Ory Tunnel, make sure the tunnel is set up correctly and both your app and Ory are on the same (sub)domain, as cookies are domain-specific [CSRF troubleshooting](https://www.ory.sh/docs/troubleshooting/csrf/). 3. Payload: The payload must include: - `password`: the new password - `method`: "password" - `csrf_token`: the value from the CSRF node Example:
Copy code
js    {      password: newPassword,      method: "password",      csrf_token: csrfNode.attributes.value    }
4. Clear Cookies and Retry: If you see the error "The HTTP Cookie Header was set and a CSRF token was sent but they do not match," clear your cookies for the domain and retry the flow, as stale or mismatched cookies can cause this [CSRF troubleshooting](https://www.ory.sh/docs/troubleshooting/csrf/). 5. Check Domain and Tunnel: If you are using Ory Tunnel, ensure your frontend and Ory are on the same domain (e.g., via the tunnel URL), as CORS and cookie issues can otherwise occur [Bring-your-own-ui CSRF debug](https://www.ory.sh/docs/kratos/bring-your-own-ui/custom-ui-basic-integration/#debug). Summary of what to check: - The CSRF token is correctly extracted and sent. - Cookies are present and sent with the request. - The frontend and Ory are on the same domain (especially in local dev). - If you get a mismatch error, clear cookies and retry the flow. If all of the above are correct and you still get a 403, it is likely a cookie or domain issue. Double-check your tunnel and browser cookie state. References: - [CSRF troubleshooting](https://www.ory.sh/docs/troubleshooting/csrf/) - [Bring-your-own-ui CSRF debug](https://www.ory.sh/docs/kratos/bring-your-own-ui/custom-ui-basic-integration/#debug)
q
@chilly-king-10285 how wouldl I use the ory client to setup 2FA?
c
A message from kapa.ai
To set up 2FA (two-factor authentication) using the Ory client, you need to enable and configure a second-factor method (such as TOTP, WebAuthn, SMS, or Lookup Secrets) in your Ory project, and then use the Ory client to initiate and complete the relevant self-service flows. General steps: 1. Enable 2FA methods in your Ory project configuration For example, to enable TOTP (Time-based One-Time Passwords), update your configuration:
Copy code
yaml    selfservice:      methods:        totp:          config:            issuer: <http://ExampleIssuer.com|ExampleIssuer.com>          enabled: true
You can do this via the Ory Console or CLI [Time-based one-time passwords (TOTP)](https://www.ory.sh/docs/kratos/mfa/totp/). 2. Initiate the settings flow using the Ory client In your application, use the Ory client to create a settings flow for the authenticated user:
Copy code
js    const flow = await ory.createBrowserSettingsFlow();
This flow will include nodes for enabling 2FA methods (e.g., TOTP setup) if they are enabled in your config. 3. Present the 2FA setup UI to the user For TOTP, the flow will include a QR code and a field to enter the code from the authenticator app. For WebAuthn, the flow will prompt for device registration. The UI nodes for these are included in the flow response [Settings and profile updates](https://www.ory.sh/docs/kratos/self-service/flows/user-settings/). 4. Submit the 2FA setup data using the Ory client When the user completes the 2FA setup (e.g., scans the QR code and enters the code), submit the data back to Ory using the flow’s action URL or the client’s update method:
Copy code
js    await ory.updateSettingsFlow({      flow: flow.id,      updateSettingsFlowBody: {        totp_code: "123456", // or the relevant field for your 2FA method        method: "totp"      }    });
Make sure to include the CSRF token if required. Supported 2FA methods: - TOTP (Authenticator apps) - WebAuthn (FIDO2, YubiKey, biometrics) - SMS/Email one-time codes - Lookup Secrets (backup codes) References: - [2FA Overview](https://www.ory.sh/docs/kratos/mfa/overview/) - [TOTP Setup](https://www.ory.sh/docs/kratos/mfa/totp/) - [WebAuthn Setup](https://www.ory.sh/docs/kratos/mfa/webauthn-fido-yubikey/) - [Settings Flow](https://www.ory.sh/docs/kratos/self-service/flows/user-settings/) If you need a code example for a specific 2FA method, let me know which one!
q
How do I 1. Enable 2FA methods in your Ory project configuration
@chilly-king-10285 how do I 1. Enable 2FA methods in your Ory project configuration
c
A message from kapa.ai
To enable 2FA (multi-factor authentication) methods in your Ory project configuration, you can use either the Ory Console or the Ory CLI. Here’s how you can do it for common 2FA methods: --- ### Using the Ory Console 1. Log in to the [Ory Console](https://console.ory.sh/). 2. Select your workspace and project. 3. Navigate to the Authentication tab. 4. Click on Two-factor auth in the sidebar. 5. Enable the desired 2FA methods (such as TOTP, WebAuthn, One-Time Codes, or Lookup Secrets) by toggling their respective switches and saving your changes. --- ### Using the Ory CLI 1. Download your current identity configuration:
Copy code
bash     ory get identity-config --project <project-id> --workspace <workspace-id> --format yaml > identity-config.yaml
2. Edit the configuration file to enable the desired 2FA methods. For example, to enable TOTP and Lookup Secrets:
Copy code
yaml     selfservice:       methods:         totp:           config:             issuer: <http://ExampleIssuer.com|ExampleIssuer.com>           enabled: true         lookup_secret:           enabled: true
3. Update your configuration:
Copy code
bash     ory update identity-config --project <project-id> --workspace <workspace-id> --file identity-config.yaml
--- You can enable other methods (like WebAuthn or One-Time Codes) in a similar way by setting their
enabled
property to
true
in the configuration file or toggling them in the Console. For more details and method-specific options, refer to the relevant sections in the Ory documentation [Multi-factor authentication](https://www.ory.sh/docs/identities/get-started/mfa/) [Time-based one-time passwords (TOTP)](https://www.ory.sh/docs/kratos/mfa/totp/) [WebAuthn and FIDO2 (YubiKey)](https://www.ory.sh/docs/kratos/mfa/webauthn-fido-yubikey/) [Lookup Secrets](https://www.ory.sh/docs/kratos/mfa/lookup-secrets/).
q
@chilly-king-10285 I would like to enable SMS/Email one-time codes so that users can optionally choose to have this extra layer of authentication. When I enable it in Authentication -> two factor auth -> one-time codes, it enforces the email code. How do I make it optional and let the user choose whether they want it and where do they want the code to be sent?
c
A message from kapa.ai
Based on the knowledge sources, when you enable SMS/Email one-time codes for multi-factor authentication (MFA) in Ory, the enforcement behavior depends on your project’s configuration—specifically, the
required_aal
(Authentication Assurance Level) setting for flows like settings or login. If you set
required_aal: highest_available
for a flow (such as the settings flow), Ory will require users who have a second factor configured to use it, but will not force users without a second factor to enroll. This means MFA is optional: users can choose to set up a second factor (like SMS/Email one-time codes) in their account settings, and only those who have set it up will be required to use it for higher-assurance actions. Users who have not set up a second factor can continue with just their primary authentication method. How to make one-time code MFA optional: 1. Enable one-time code MFA in the Ory Console under Authentication → Two-factor auth → One-time codes. 2. Set the flow’s
required_aal
to
highest_available
(not
aal2
), for example in the settings flow:
Copy code
yaml    selfservice:      flows:        settings:          required_aal: highest_available
This ensures that only users who have enrolled a second factor will be prompted for it, while others are not forced to enroll or use it [Account recovery and password reset configuration](https://www.ory.sh/docs/kratos/self-service/flows/account-recovery-password-reset/#configuration). 3. Let users enroll their preferred second factor (SMS or Email) in their account settings. The method (SMS or Email) is determined by your identity schema and which traits (email, phone) are available and configured for the user [SMS and Email 2FA](https://www.ory.sh/docs/kratos/mfa/mfa-via-sms/). Where is the code sent? - The code is sent to the trait (email or phone) that the user has configured and that is marked as an identifier for the
code
method in your identity schema. Users can only receive codes via channels (email or SMS) that are enabled and present in their identity. Summary: - Set
required_aal: highest_available
to make MFA optional. - Users can choose to enroll in SMS/Email one-time code MFA in their settings. - The code is sent to the email or phone number configured in the user’s identity schema. If you want users to choose between SMS or Email, ensure both are present and properly configured in your identity schema. The user will then be able to enroll the method they prefer [SMS and Email 2FA](https://www.ory.sh/docs/kratos/mfa/mfa-via-sms/). If you need more granular control (such as letting users pick between multiple available channels at login), the knowledge sources do not provide explicit details on customizing this selection UI or logic.
q
@chilly-king-10285 how do I change 2. Set the flow’s
required_aal
to
highest_available
(not
aal2
), for example in the settings flow:
c
A message from kapa.ai
To change the flow’s
required_aal
to
highest_available
(instead of
aal2
), you need to update your Ory project’s configuration for the relevant self-service flow (such as the settings flow). You can do this either via the Ory Console or the Ory CLI. Using the Ory Console: 1. Go to the [Ory Console](https://console.ory.sh/). 2. Select your project. 3. Navigate to Authentication → Sessions or the specific flow you want to configure (e.g., Settings). 4. Find the Authenticator Assurance Level (AAL) setting and set it to
highest_available
. 5. Save your changes. Using the Ory CLI: 1. Download your current configuration:
Copy code
bash    ory get identity-config --project <project-id> --workspace <workspace-id> --format yaml > identity-config.yaml
2. Edit the configuration file and set the desired flow’s
required_aal
to
highest_available
. For example:
Copy code
yaml    selfservice:      flows:        settings:          required_aal: highest_available
3. Upload the updated configuration:
Copy code
bash    ory update identity-config --project <project-id> --workspace <workspace-id> --file identity-config.yaml
This configuration ensures that only users who have set up a second factor will be prompted for it, making MFA optional for others [Step-up authentication](https://www.ory.sh/docs/kratos/mfa/step-up-authentication/) [Account recovery and password reset configuration](https://www.ory.sh/docs/kratos/self-service/flows/account-recovery-password-reset/#configuration).
q
@chilly-king-10285 I get this
Copy code
ory get identity-config 
The requested action was forbidden
I have the right ORY_PROJECT_API_KEY and ORY_SDK_URL configured
c
A message from kapa.ai
If you are receiving
The requested action was forbidden
when running
ory get identity-config
, even though you have set the correct
ORY_PROJECT_API_KEY
and
ORY_SDK_URL
, it likely means that your API key does not have the required permissions for this operation. According to the knowledge sources, there are two types of API keys in Ory Network: - Project API Keys: Used to manage project data (like identities). - Workspace API Keys: Used to manage project configuration (such as identity config). To get or update the identity configuration, you need a Workspace API Key, not a Project API Key. The Project API Key does not have sufficient permissions for configuration management operations like
ory get identity-config
 [Authorizing Ory Network Workspace Admin APIs](https://www.ory.sh/docs/concepts/personal-access-token/#authorizing-ory-network-workspace-admin-apis). What you should do: 1. Create a Workspace API Key in the Ory Console under Workspace settings → API Keys. 2. Set the environment variable:
Copy code
bash    export ORY_WORKSPACE_API_KEY=<your-workspace-api-key>
3. Run your command again (you do not need to set
ORY_SDK_URL
for this operation). Summary: You are getting this error because a Project API Key cannot be used to fetch or update project configuration. Use a Workspace API Key instead [Authorizing Ory Network Workspace Admin APIs](https://www.ory.sh/docs/concepts/personal-access-token/#authorizing-ory-network-workspace-admin-apis).
q
@chilly-king-10285 what are the possible values for required_aal and what do they mean?
c
A message from kapa.ai
The possible values for
required_aal
are: - aal1: Requires only one authentication factor (e.g., password). This is the basic level of authentication. - aal2: Requires two authentication factors (e.g., password + TOTP or another second factor). This enforces multi-factor authentication. - aal3: Represents an even higher level of assurance, typically involving hardware-backed authenticators or similar advanced methods. - highest_available: Enforces the highest Authenticator Assurance Level (AAL) that the user has configured. If the user has set up a second factor, it will be required; if not, only the first factor is required. This is the default in Ory Network for strict multi-factor authentication enforcement. A higher number means it is harder for an attacker to compromise the account. For more details, see the [Ory documentation on credentials and AAL](https://www.ory.sh/kratos/docs/concepts/credentials) and [step-up authentication](https://www.ory.sh/docs/kratos/mfa/step-up-authentication/) [Step-up authentication](https://www.ory.sh/docs/kratos/mfa/step-up-authentication/).
q
@chilly-king-10285 if I set it to aal1, can users decide wether they should login with password of password + TOTP?
c
A message from kapa.ai
If you set
required_aal
to
aal1
, users will only be required to authenticate with a single factor (such as a password or OIDC). Even if a user has configured a second factor (like TOTP), the system will not require them to use it for login or the relevant flow. The user will not be prompted for the second factor, and login with just the password will be sufficient. In other words, with
aal1
, users cannot choose to use both password and TOTP for a higher level of assurance during login—the system will only enforce the first factor. If you want users who have set up a second factor to be required to use it, you should use
highest_available
or
aal2
as the value for
required_aal
 [Step-up authentication](https://www.ory.sh/docs/kratos/mfa/step-up-authentication/) [Check AAL](https://www.ory.sh/docs/identities/get-started/mfa/#check-aal). Summary: - `aal1`: Only one factor is required, even if the user has set up a second factor. - Users cannot opt-in to use both password and TOTP at login if
aal1
is set.
q
@chilly-king-10285 I have required_aal: highest_available. If I enable One-Time Codes it forces users to login with email code after password authentication, why is that? Are all my users setup to have email 2fa? and how do I check that?
c
A message from kapa.ai
When you set
required_aal: highest_available
and enable One-Time Codes (email or SMS) for MFA, Ory will require users who have a second factor configured to use it. However, if you notice that all users are being forced to use an email code after password authentication—even those who have not explicitly enrolled in 2FA—this is likely due to how your identity schema and credential configuration are set up. Why is this happening? - If your identity schema marks the email trait as an identifier for the
code
credential (e.g.,
"credentials": { "code": { "identifier": true, "via": "email" } }
), then every user with an email address is considered to have the code credential available for MFA. - With
required_aal: highest_available
, Ory will prompt for the second factor (the email code) for all users who have this credential available, not just those who have explicitly enrolled in 2FA. Are all my users set up to have email 2FA? - If your schema is configured as above, then yes—all users with an email address are effectively set up to use email as a second factor, even if they did not explicitly enroll. This is because the system treats the presence of the email trait and the credential configuration as "MFA available" for those users [SMS and Email 2FA](https://www.ory.sh/docs/kratos/mfa/mfa-via-sms/). How do I check which users have email 2FA? - The knowledge sources do not provide a direct API or method to list which users have 2FA enabled for email codes. However, if your identity schema is set up to always provide the code credential via email, then all users with an email address are considered to have email 2FA available. - If you want only some users to have email 2FA, you need to adjust your schema so that the code credential is not always available, or use a different trait for the code credential that is only set when a user enrolls in 2FA. Summary: With your current configuration, all users with an email address are treated as having email 2FA available, which is why Ory enforces the second factor for everyone when
required_aal: highest_available
is set [SMS and Email 2FA](https://www.ory.sh/docs/kratos/mfa/mfa-via-sms/). If you want 2FA to be optional, you need to adjust your identity schema and credential configuration accordingly.
q
@chilly-king-10285 this is what my config looks like
Copy code
clients:
  web_hook:
    header_allowlist:
    - Accept
    - Accept-Encoding
    - Accept-Language
    - Content-Length
    - Content-Type
    - Origin
    - Priority
    - Referer
    - Sec-Ch-Ua
    - Sec-Ch-Ua-Mobile
    - Sec-Ch-Ua-Platform
    - Sec-Fetch-Dest
    - Sec-Fetch-Mode
    - Sec-Fetch-Site
    - Sec-Fetch-User
    - True-Client-Ip
    - User-Agent
    - X-Forwarded-Host
    - Ory-Base-Url-Rewrite
    - Ory-Base-Url-Rewrite-Token
    - X-Ory-Original-Host
    - Ory-No-Custom-Domain-Redirect
cookies:
  domain: <redacted>
  path: /
  same_site: Lax
courier:
  delivery_strategy: smtp
  smtp:
    connection_uri: <redacted>
    from_address: <redacted>
    from_name: <redacted>
    headers: {}
  templates:
    login_code:
      valid:
        email:
          body: {}
        sms:
          body: {}
    recovery:
      invalid:
        email:
          body: {}
      valid:
        email:
          body: {}
    recovery_code:
      invalid:
        email:
          body: {}
      valid:
        email:
          body: {}
    registration_code:
      valid:
        email:
          body: {}
        sms:
          body: {}
    verification:
      invalid:
        email:
          body: {}
      valid:
        email:
          body: {}
    verification_code:
      invalid:
        email:
          body: {}
      valid:
        email:
          body: {}
        sms:
          body: {}
feature_flags:
  cacheable_sessions: false
  cacheable_sessions_max_age: 2m30s
  password_profile_registration_node_group: password
identity:
  default_schema_id: <redacted>
  schemas: <redacted>
oauth2_provider:
  override_return_to: true
preview:
  default_read_consistency_level: strong
security:
  account_enumeration:
    mitigate: false
selfservice:
  allowed_return_urls: <redacted>
  default_browser_return_url: /ui/welcome
  flows:
    error:
      ui_url: <redacted>
    login:
      after:
        code:
          hooks: []
        default_browser_return_url: <redacted>
        hooks: []
        lookup_secret:
          hooks: []
        oidc:
          hooks: []
        passkey:
          hooks: []
        password:
          hooks: []
        saml:
          hooks: []
        totp:
          hooks: []
        webauthn:
          hooks: []
      before:
        hooks: []
      style: unified
      ui_url: <redacted>
    logout:
      after:
        default_browser_return_url: <redacted>
    recovery:
      after:
        default_browser_return_url: <redacted>
        hooks: []
      before:
        hooks: []
      enabled: true
      notify_unknown_recipients: false
      ui_url: <redacted>
      use: code
    registration:
      after:
        code:
          hooks:
          - hook: session
        default_browser_return_url: <redacted>
        hooks: []
        oidc:
          hooks:
          - hook: session
        passkey:
          hooks:
          - hook: session
        password:
          hooks:
          - hook: session
        saml:
          hooks:
          - hook: session
        webauthn:
          hooks:
          - hook: session
      before:
        hooks: []
      enable_legacy_one_step: true
      enabled: false
      login_hints: true
      style: unified
      ui_url: <redacted>
    settings:
      after:
        default_browser_return_url: <redacted>
        hooks: []
        lookup_secret:
          hooks: []
        oidc:
          hooks: []
        passkey:
          hooks: []
        password:
          hooks: []
        profile:
          hooks: []
        saml:
          hooks: []
        totp:
          hooks: []
        webauthn:
          hooks: []
      before:
        hooks: []
      privileged_session_max_age: 15m0s
      required_aal: highest_available
      ui_url: <redacted>
    verification:
      after:
        default_browser_return_url: <redacted>
        hooks: []
      before:
        hooks: []
      enabled: true
      notify_unknown_recipients: false
      ui_url: <redacted>
      use: code
  methods:
    b2b:
      config:
        organizations: []
    captcha:
      config:
        provider: cf-turnstile
      enabled: false
    code:
      config:
        missing_credential_fallback_enabled: true
      mfa_enabled: false
    link:
      config:
        base_url: <redacted>
      enabled: true
    lookup_secret: {}
    oidc:
      config:
        providers: []
      enabled: false
    passkey:
      config:
        rp:
          display_name: <redacted>
          id: <redacted>
          origins:
          - <redacted>
    password:
      config: {}
      enabled: true
    profile: {}
    totp:
      config:
        issuer: <redacted>
      enabled: true
    webauthn:
      config:
        passwordless: false
        rp:
          display_name: <redacted>
          id: <redacted>
          origins:
          - <redacted>
serve:
  admin:
    base_url: <redacted>
    request_log:
      disable_for_health: true
  public:
    base_url: <redacted>
    cors:
      enabled: false
    request_log:
      disable_for_health: true
session:
  cookie:
    domain: <redacted>
    name: <redacted>
    path: /
    same_site: Lax
  lifespan: 72h0m0s
  whoami:
    required_aal: highest_available
    tokenizer:
      templates: {}
I dont see "credentials": { "code": { "identifier": true, "via": "email" } }, so not sure why all users are enforced to login with email code. In the ory console, there's this warning in the on-time codes toggle
Copy code
Your configuration requires users to have a specific AAL level after logging in or when accessing self service settings. Enabling Enable one-time code MFA will force users to solve the MFA challenge before they are completely logged in or can access the self service settings.
c
A message from kapa.ai
Based on your configuration and the information from the knowledge sources, here’s what’s happening: - You have
required_aal: highest_available
set for both the settings flow and the session (
session.whoami.required_aal
). - In your
selfservice.methods.code
section, you have
mfa_enabled: false
, which should mean one-time code MFA is not enforced. However, you mentioned that in the Ory Console, you enabled "Enable one-time code MFA," and the warning appears. Key points from the knowledge sources: - When you enable one-time code MFA and set
required_aal: highest_available
, Ory will require users who have a second factor available to use it. If the system considers all users to have a second factor (for example, if the identity schema or configuration makes the code credential available to all users), then all users will be forced to use the code as MFA [SMS and Email 2FA](https://www.ory.sh/docs/kratos/mfa/mfa-via-sms/). - The warning in the Ory Console is expected: "Your configuration requires users to have a specific AAL level after logging in or when accessing self service settings. Enabling Enable one-time code MFA will force users to solve the MFA challenge before they are completely logged in or can access the self service settings." This means that, with your current settings, enabling one-time code MFA will enforce the MFA challenge for all users. Why is this happening even though you don’t see
credentials: { code: ... }
in your config?
- The knowledge sources indicate that if the code method is enabled and the required AAL is set to
highest_available
, Ory may treat all users as having the second factor available, depending on how the identity schema and code method are set up. If the code method is globally enabled, and the system can send a code to the user (e.g., via their email trait), it may consider the second factor available for all users [Github Issue #409](https://github.com/ory/network/issues/409). How to make one-time code MFA optional: - Set
mfa_enabled: false
for the code method (which you have already done). - If you want users to opt-in to MFA, you may need to adjust your identity schema or the way the code method is configured so that not all users are considered to have the second factor available. The knowledge sources do not provide a detailed step-by-step for this, but they do mention that the presence of the code credential (e.g., via email) can make Ory treat all users as MFA-enabled [Github Issue #409](https://github.com/ory/network/issues/409). Summary: With your current configuration and the Console toggle enabled, Ory enforces the MFA challenge for all users because it considers the second factor available to everyone. This is expected behavior given the warning in the Console and the way Ory handles the code method and AAL settings. If you want to make MFA optional, you may need to further adjust your schema or code method configuration, but the knowledge sources do not provide explicit instructions for this scenario.
q
@chilly-king-10285 I enabled totp and now my nodes look like this when calling
ory.createBrowserSettingsFlow()
Copy code
[
  {
    "type": "input",
    "group": "default",
    "attributes": {
      "name": "csrf_token",
      "type": "hidden",
      "value": "Dpu46kKZDJ/Vm+yXz0gZHgRYoF9/J2YvfeFN8X0WW6+MbUtEpL67E4d4Wey67lzH7BUZKqwoYkAn/YsUHCARxw==",
      "required": true,
      "disabled": false,
      "node_type": "input"
    },
    "messages": [],
    "meta": {}
  },
  {
    "type": "input",
    "group": "profile",
    "attributes": {
      "name": "traits.email",
      "type": "email",
      "value": "<mailto:[email protected]|[email protected]>",
      "required": true,
      "autocomplete": "email",
      "disabled": false,
      "node_type": "input"
    },
    "messages": [],
    "meta": {
      "label": {
        "id": 1070002,
        "text": "E-Mail",
        "type": "info",
        "context": {
          "title": "E-Mail"
        }
      }
    }
  },
  {
    "type": "input",
    "group": "profile",
    "attributes": {
      "name": "method",
      "type": "submit",
      "value": "profile",
      "disabled": false,
      "node_type": "input"
    },
    "messages": [],
    "meta": {
      "label": {
        "id": 1070003,
        "text": "Save",
        "type": "info"
      }
    }
  },
  {
    "type": "input",
    "group": "password",
    "attributes": {
      "name": "password",
      "type": "password",
      "required": true,
      "autocomplete": "new-password",
      "disabled": false,
      "node_type": "input"
    },
    "messages": [],
    "meta": {
      "label": {
        "id": 1070001,
        "text": "Password",
        "type": "info"
      }
    }
  },
  {
    "type": "input",
    "group": "password",
    "attributes": {
      "name": "method",
      "type": "submit",
      "value": "password",
      "disabled": false,
      "node_type": "input"
    },
    "messages": [],
    "meta": {
      "label": {
        "id": 1070003,
        "text": "Save",
        "type": "info"
      }
    }
  },
  {
    "type": "input",
    "group": "lookup_secret",
    "attributes": {
      "name": "lookup_secret_regenerate",
      "type": "submit",
      "value": "true",
      "disabled": false,
      "node_type": "input"
    },
    "messages": [],
    "meta": {
      "label": {
        "id": 1050008,
        "text": "Generate new backup recovery codes",
        "type": "info"
      }
    }
  },
  {
    "type": "img",
    "group": "totp",
    "attributes": {
      "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEAEAAAAAApiSv5AAAHMklEQVR4nOydwY7jOA8G//4x7//Ks7dODgRNhZ+dXVTVaTCxZHVSEAhKov78/fs/AfP/bw9AvosCwFEAOAoA58/rnz8/iQ6roPLVc//peX/nY8mQHvP0W8v/Rs4AcBQAjgLAUQA4f6r/PA+ezkOc855fvZwHRedhYx+49m3P33Ee3KV+I2cAOAoARwHgKACcMgh8sQnuztv2wV0flvVj6Xs+H8s0kKsC1/695+HgLgB3BoCjAHAUAI4CwLkIAu9js9jZt82Eb32L/rl+zNO2z+zWdAaAowBwFACOAsD5WhD4Ypqb65m2yOyqm4ah5zx9UMcZAI4CwFEAOAoA5yIIzIQkmxzZNLSa7h087+XF5vhG33YTQO5+I2cAOAoARwHgKACcMgjMZMv6nqf78DL/dz6W/rnNgvTmuerTHc4AcBQAjgLAUQA4b0Hg9ysGZvbNpU/z9vThW//eJ84EX+EMAEcB4CgAHAWA83OeyepJZ9+mb0tX1Ztm3+5bIs7k/zwdLC0KAEcB4CgAnIsg8O3B4yCm4rzn+xZ3p+Ob9vKttlUv8z2GzgBwFACOAsBRADhlEPjiieBkGgT2PfdjydzIkT6BfB6Ab84i1y2cAeAoABwFgKMAcH7SYd74xYv83+bT8xFMx7c5LDIdQc8nOwadAeAoABwFgKMAcMq7g6e3b/Rtz/NXT1TaO78xpGq7Wa6u3jG9dyQTmr7jDABHAeAoABwFgDMuFn0eGG56Se/wy9Qi7FtsKhVmjsl8gjMAHAWAowBwFADOajk4vdcvk8NLL7emA7RMjjGTgXQGwKMAcBQAjgLAucgEni8Rn2fVnjg2sunlRZ/rq57r39b30vc3fVv/f84AeBQAjgLAUQA4F3sC71um3JR+zhRKSVfuSwdyU3b1BJ0B4CgAHAWAowBwViViNpm7TdW/+04qp3OM/dsyLXahrjMAHAWAowBwFADOxbVx0+MM54FI+iTw9B3ni6f3hbpT7vxOnQHgKAAcBYCjAHDGmcD7rky77y6SKff95dP3bsLpflRXOAPAUQA4CgBHAeB8cDBkk4PKhH73nde97xhK36JiU3JmHpA6A8BRADgKAEcB4FxkAit2xYnbwSyyYP34zskEpD3pO0aqnvvnnAHwKAAcBYCjAHAu6gSehyQ9mWXUiidGes5918tNuQrPnQHgKAAcBYCjAHDKEjEvNveEZDKL5/2d91y9I7OkW/V8PqrNSeqrXpwB4CgAHAWAowBwyj2B6YXcTImYzTGUzZgzvfTh5X2lc8wESosCwFEAOAoAZ1wipmcTNk772xyF2OzmS4902kv6OTOBUqAAcBQAjgLAKfcEZi5Zq0jf5lGR2V+XLjmdPtFcYbFoOUYB4CgAHAWAc3Ew5O3BcKmWTKCUPtvcv23TX8WmmHVmAdkZAI8CwFEAOAoA5+JgSMWmdMmUzKVtmfCyf25T8Hn6TWbKadc4A8BRADgKAEcB4JR7Aqchyaa8zKYAckU6i5gpf92T2eXYH525whkAjgLAUQA4CgBnfG3cW5NIiegncnjP/kX9+J4o/fzJ8rIzABwFgKMAcBQAzsW1cT2bEOc815fJfU17yZRqOV+4nvYyDQyvfhlnADgKAEcB4CgAnPiNIZmbMe47m5vOqlW9bArrbA7guBwsxygAHAWAowBwPjgdnD4PW31acV8w1vdS8UTtwE0+cT4CZwA4CgBHAeAoAJzx6eDzDF+aTYmYzW7DzDsyC+bVcz1XgbAzABwFgKMAcBQAzsWewPMbKjIlUyo2RaD7PXfnbftdek8UhvF0sERQADgKAEcB4HxwOvitcWQ3X99f1fN9d4J860xwz51FuZ0B4CgAHAWAowBwxqeDM1X6ptnBdKGZ/tPpou19ZbL7gHT6XX2yUO8MAEcB4CgAHAWAc3F3cLpK3/S5TfHk87H0LTLVC6eks35X73AGgKMAcBQAjgLAuQgCK+5cnPz3jGVT5qUawdNFoKfPOQPAUQA4CgBHAeBc7Al8kcl9ZYqd9GzycNVYpu+4LzuYxiBQflEAOAoARwHgjOsEftD1l5Z50yeQ+7FU3FcJcNrLHGcAOAoARwHgKACccZ3AKX3BkswiZiaLmFmqPef8irj+ub7nCjOB8osCwFEAOAoApzwdnFmC3Zxp7Z/rR5pZ4J4GY+dt79sD+UkO1BkAjgLAUQA4CgCn3BOYuSMjs5BbDvqBHXnnBzS+v4vwk2/XGQCOAsBRADgKAGdcJzBD5gbdF5s84fS9m0/7CoTToPJ8sfiFxaKlRQHgKAAcBYDzSBC4CV2q56bh0Sa32ZMuKr0pnb3725wB4CgAHAWAowBwQncH923Pd9VNg7tpf/2oMkFWNZbzYx5PFJo2Eyi/KAAcBYCjAHDKIDBzTvi+07eZkKkaS798mx7fZrm64pOg0hkAjgLAUQA4CgDnxjqB8l/AGQCOAsBRADgKAEcB4PwTAAD//66no/ZJijN4AAAAAElFTkSuQmCC",
      "id": "totp_qr",
      "width": 256,
      "height": 256,
      "node_type": "img"
    },
    "messages": [],
    "meta": {
      "label": {
        "id": 1050005,
        "text": "Authenticator app QR code",
        "type": "info"
      }
    }
  },
  {
    "type": "text",
    "group": "totp",
    "attributes": {
      "text": {
        "id": 1050006,
        "text": "HWQDQP5QZQJQ4ZLG6FOMBX4SPMZBCWGG",
        "type": "info",
        "context": {
          "secret": "HWQDQP5QZQJQ4ZLG6FOMBX4SPMZBCWGG"
        }
      },
      "id": "totp_secret_key",
      "node_type": "text"
    },
    "messages": [],
    "meta": {
      "label": {
        "id": 1050017,
        "text": "This is your authenticator app secret. Use it if you can not scan the QR code.",
        "type": "info"
      }
    }
  },
  {
    "type": "input",
    "group": "totp",
    "attributes": {
      "name": "totp_code",
      "type": "text",
      "required": true,
      "disabled": false,
      "node_type": "input"
    },
    "messages": [],
    "meta": {
      "label": {
        "id": 1070006,
        "text": "Verify code",
        "type": "info"
      }
    }
  },
  {
    "type": "input",
    "group": "totp",
    "attributes": {
      "name": "method",
      "type": "submit",
      "value": "totp",
      "disabled": false,
      "node_type": "input"
    },
    "messages": [],
    "meta": {
      "label": {
        "id": 1070003,
        "text": "Save",
        "type": "info"
      }
    }
  }
]
Given that, how do I configure sms otp for this user
c
A message from kapa.ai
To configure SMS OTP (one-time code) for a user, you need to ensure two things: 1. Your identity schema must support SMS as a credential for the code method. 2. The user must have a phone number trait in their identity. ### 1. Identity Schema Configuration Your identity schema should include a phone number trait with the correct configuration for SMS OTP. Here’s an example from the knowledge sources:
Copy code
json {   "traits": {     "phone": {       "type": "string",       "format": "tel",       "<http://ory.sh/kratos|ory.sh/kratos>": {         "credentials": {           "code": {             "identifier": true,             "via": "sms"           }         },         "recovery": {           "via": "sms"         },         "verification": {           "via": "sms"         }       }     }   } }
This configuration ensures that the phone number can be used for SMS OTP, recovery, and verification flows ([Phone number and password](https://www.ory.sh/docs/kratos/manage-identities/customize-identity-schema/#phone-number-and-password)). ### 2. Enable SMS OTP in Ory - In the Ory Console, go to Authentication → Two-factor auth → One-time codes and enable it. - Make sure you have configured an SMS delivery provider under Authentication → SMS configuration ([Send SMS to your users](https://www.ory.sh/docs/kratos/emails-sms/sending-sms/)). ### 3. User Experience Once the above is set up: - When the user goes to their account settings (using the Experience UI or your custom UI), they should see an option to enroll SMS OTP if their identity has a phone number trait. - The UI nodes for SMS OTP will appear similarly to how TOTP nodes appear, but for the
code
method and referencing the phone number. ### 4. If you do not see SMS OTP nodes If you do not see the SMS OTP nodes in the settings flow: - Double-check that the user’s identity has a valid phone number trait. - Ensure your identity schema is configured as shown above. - Make sure the SMS channel is properly configured in your Ory project. Note: The knowledge sources do not provide a step-by-step UI screenshot for SMS OTP enrollment, but the process is analogous to TOTP: the user will be prompted to enter a code sent to their phone when enrolling or using SMS OTP ([Enable SMS one-time-code (OTP)](https://www.ory.sh/docs/kratos/passwordless/one-time-code/#enable-sms-one-time-code-otp)). --- Summary: - Update your identity schema to support SMS OTP. - Ensure the user has a phone number trait. - Enable and configure SMS OTP and an SMS provider in Ory. - The user will then be able to enroll SMS OTP via the settings flow. If you have done all of the above and still do not see the SMS OTP option, the knowledge sources do not provide further troubleshooting steps.
q
@chilly-king-10285 given the schema I shared with you, what kind of 2fa can I setup for the user?
c
A message from kapa.ai
Based on the configuration you shared, the following 2FA (two-factor authentication) methods are available for your users: - TOTP (Time-based One-Time Passwords): Your config has TOTP enabled (
totp.enabled: true
), and the settings flow nodes you shared include the TOTP setup (QR code, secret, and code input). This means users can enroll and use TOTP as a second factor [Time-based one-time passwords (TOTP)](https://www.ory.sh/docs/kratos/mfa/totp/). - Lookup Secrets (Backup Codes): The settings flow nodes include an option to generate backup recovery codes, which means users can set up and use lookup secrets as a 2FA fail-safe [Lookup Secrets - a MFA fail-safe](https://www.ory.sh/docs/kratos/mfa/lookup-secrets/). - Password: While not a second factor, password authentication is enabled and present in your flow. What is not available based on your config and nodes: - SMS/Email One-Time Codes: Although your config has the code method present,
mfa_enabled
is set to
false
for the code method, and your settings flow nodes do not show any UI for SMS or email one-time codes. Additionally, your identity schema (as shown in your config) does not include a phone number trait or code credential via SMS/email, so users cannot enroll in SMS or email OTP as a second factor [SMS and Email 2FA](https://www.ory.sh/docs/kratos/mfa/mfa-via-sms/). - WebAuthn (Passkeys, Security Keys): Your config has WebAuthn enabled, but your settings flow nodes do not show any WebAuthn enrollment options. This may be due to the identity schema or other project settings, but based on the nodes you provided, WebAuthn is not currently available for this user. Summary: With your current configuration and schema, users can set up TOTP and lookup secrets as 2FA methods. SMS/Email OTP and WebAuthn are not available for enrollment based on the nodes and schema you provided. If you want to enable additional 2FA methods, you will need to update your identity schema and configuration accordingly.
q
@chilly-king-10285 can I define different themes for UI ory experience and show a specific one depending on the subdomain?
c
A message from kapa.ai
Yes, you can define different themes and UI base URLs for the Ory Account Experience or your custom UI depending on the subdomain, using the Multibrand feature in Ory Network. With Multibrand, you can add multiple custom domains to your Ory project and assign a different custom UI base URL (and thus a different theme or branding) to each domain. This allows you to serve a unique user experience per subdomain or brand. For example, you can have: -
<http://ory.mufflers.com|ory.mufflers.com>
with its own theme and UI base URL -
<http://ory.carseats.com|ory.carseats.com>
with a different theme and UI base URL -
<http://ory.wipers.com|ory.wipers.com>
with yet another theme and UI base URL Each domain can have its own cookie domain and custom UI base URL, so users visiting different subdomains will see the corresponding theme and UI. You can configure this in the Ory Console under Branding → Theming and Branding → UI URLs for each custom domain [Configure Ory to use your UI: Multiple custom domains](https://www.ory.sh/docs/kratos/bring-your-own-ui/configure-ory-to-use-your-ui#multiple-custom-domains). > "With the Multibrand feature enabled for your workspace, you can add multiple custom domains to each Ory Network project. You can add a different custom UI base URL of each of the custom domains registered in the project. This allows for a high degree of flexibility, where each of the domains serves its own set of UI views that have a common path makeup but different base URL." > — [Configure Ory to use your UI: Multiple custom domains](https://www.ory.sh/docs/kratos/bring-your-own-ui/configure-ory-to-use-your-ui#multiple-custom-domains) If you are interested in enabling Multibrand, you may need to contact Ory for access, as noted in the documentation.
q
@chilly-king-10285 when I send an invite to my users they often end up clicking the link, leave and clicking it again. This results in a link already used error. Is it possiblet o configure Ory so that the link is valid until the user has set their password?
c
A message from kapa.ai
I'm sorry, but the knowledge sources do not contain explicit information about configuring Ory so that an invite (recovery) link remains valid until the user has set their password, regardless of how many times the link is clicked. The documentation states that recovery links (used for invites) are valid for a configurable period (e.g.,
expires_in: "12h"
), but it also notes that the link contains a token that is invalidated after use, which is why users see a "link already used" error if they click it again after completing the flow [Invite users](https://www.ory.sh/docs/kratos/manage-identities/invite-users/) [Magic links](https://www.ory.sh/docs/kratos/manage-identities/account-recovery/#magic-links). There is no mention in the knowledge sources of a configuration option to keep the link valid until the password is set, or to allow multiple uses of the same link. The current behavior is that the link is single-use and expires either after use or after the configured time period, whichever comes first. If this behavior is critical for your use case, you may need to implement a workaround, such as generating a new invite link if the user requests it again. However, the knowledge sources do not provide a built-in solution for this scenario.
q
@chilly-king-10285 my customer is not receiving email. When I looks at the delivery status I get the folowing gomail: could not send email 1: 554 Message rejected: Email address is not verified. The following identities failed the check in region EU-WEST-2: [email protected]
c
A message from kapa.ai
A message from kapa.ai
The error message you are seeing—`554 Message rejected: Email address is not verified. The following identities failed the check in region EU-WEST-2`—indicates that your SMTP provider is rejecting the email because the sender address (or possibly the recipient address) has not been verified with the provider. This is not an Ory-specific error, but rather an issue with your SMTP/email provider configuration. According to the Ory documentation, if you use a custom SMTP server, you must ensure that the sender address (
from_address
) is properly verified with your SMTP provider. Many providers (such as AWS SES, Mailgun, etc.) require you to verify sender identities before you can send emails from them [Use a custom server to send Ory Identity messages to users](https://www.ory.sh/docs/kratos/emails-sms/sending-emails-smtp). What you should do: - Log in to your SMTP/email provider's dashboard. - Verify the sender email address or domain you are using in your Ory configuration. - After verification, try sending the email again. If you are using the default Ory Network SMTP, this error should not occur unless you have switched to a custom SMTP server and the sender address is not verified. If you continue to have issues, check your SMTP provider's documentation for how to verify sender identities, as this is a common requirement for outbound email delivery [Use a custom server to send Ory Identity messages to users](https://www.ory.sh/docs/kratos/emails-sms/sending-emails-smtp).
The error message you are seeing—`554 Message rejected: Email address is not verified. The following identities failed the check in region EU-WEST-2`—indicates that your SMTP provider is rejecting the email because the sender address (or possibly the recipient address) has not been verified with the provider. According to the Ory documentation, when using a custom SMTP server, you must ensure that the sender address (
from_address
) is properly verified with your SMTP provider. Many providers require you to verify sender identities before you can send emails from them. If the sender address is not verified, the SMTP provider will reject the email, resulting in the error you are seeing [Use a custom server to send Ory Identity messages to users](https://www.ory.sh/docs/kratos/emails-sms/sending-emails-smtp). What you should do: - Log in to your SMTP/email provider's dashboard. - Verify the sender email address or domain you are using in your Ory configuration. - After verification, try sending the email again. If you are using the default Ory Network SMTP, this error should not occur unless you have switched to a custom SMTP server and the sender address is not verified. If you continue to have issues, check your SMTP provider's documentation for how to verify sender identities, as this is a common requirement for outbound email delivery.
q
@chilly-king-10285 how do I look what smtp server is configured for my ory instance?
c
A message from kapa.ai
To see which SMTP server is configured for your Ory instance, you need to check the
connection_uri
in your Ory project's identity configuration under the
courier.smtp
section. You can do this using the Ory CLI:
Copy code
bash ory get identity-config --project <project-id> --workspace <workspace-id> --format jsonpath="courier.smtp"
This command will output the SMTP configuration, including the
connection_uri
, which shows the SMTP server currently in use [Setting SMTP security settings](https://github.com/ory/network/issues/212). If you do not have access to the CLI or the required permissions, you can also view the SMTP configuration in the Ory Console under Authentication → Email configuration [Send emails using your SMTP server](https://www.ory.sh/docs/kratos/emails-sms/sending-emails-smtp).
q
@chilly-king-10285 where can I see my cookies expiry time?
c
A message from kapa.ai
You can see your Ory session cookie expiry time in two main ways: 1. Browser Developer Tools: When you log in through the browser, open your browser’s developer tools, go to the "Application" (Chrome) or "Storage" (Firefox) tab, and look under "Cookies" for your domain. There you will see the expiry time for each cookie, including the Ory session cookie. 2. Configuration Reference: The expiry time of the Ory session cookie is determined by your project’s session lifespan configuration. By default, the session cookie’s
max-age
is set to the session lifespan you configure. You can set this in your Ory configuration under
session.lifespan
. For example, if you set:
Copy code
yaml    session:      cookie:        domain: <http://your-domain.com|your-domain.com>        name: ory_session_{name}        path: /        persistent: false        same_site: Lax      lifespan: 720h
The cookie will expire after 720 hours (30 days). If
max-age
is not set, the browser deletes the cookie when the session ends or the browser is closed [Session lifespan](https://www.ory.sh/docs/kratos/session-management/session-lifespan). Summary: - Check the expiry in your browser’s developer tools for the actual value. - The configured expiry is set in your Ory project’s session lifespan setting. If you need to change the expiry, update the
session.lifespan
value in your Ory configuration.
q
@chilly-king-10285
c
Sorry @quiet-painting-32952, I'm not sure what you mean. Please try again. 👋
q
@chilly-king-10285 how do I configure the cookie expiry in ory console?
c
A message from kapa.ai
To configure the cookie expiry in the Ory Console, you need to set the session lifespan. The session cookie’s expiry (
max-age
) is determined by the session lifespan you configure. Steps: 1. Go to the [Ory Console](https://console.ory.sh/). 2. Navigate to your project. 3. Go to Session Settings. 4. Enter your desired session lifespan (for example,
720h
for 30 days). 5. Click Save. The session cookie will then expire according to the session lifespan you set. By default, the cookie’s
max-age
matches the session lifespan. If you want to further customize cookie behavior (such as domain, path, or SameSite), you can do so in the advanced configuration, but the expiry is always tied to the session lifespan [Session lifespan](https://www.ory.sh/docs/kratos/session-management/session-lifespan#configure-session-settings-in-ory-console). If you need to set a different cookie
max-age
than the session lifespan, you must set
session.cookie.persistent: false
in the configuration, but this is typically managed via the CLI or configuration file, not directly in the Console [Session lifespan](https://www.ory.sh/docs/kratos/session-management/session-lifespan). > Summary: > In the Ory Console, set the session lifespan under Session Settings to control the cookie expiry.
q
@chilly-king-10285 is it possible to invite a user with enforce MFA requirement but just for that user?
c
A message from kapa.ai
Based on the knowledge sources, Ory’s MFA (multi-factor authentication) enforcement is configured at the flow or project level using the
required_aal
setting (such as
aal1
,
aal2
, or
highest_available
). This means that MFA requirements are generally applied to all users or to all users who have a second factor configured, not to individual users only [Step-up authentication](https://www.ory.sh/docs/kratos/mfa/step-up-authentication/). There is no documented way in the knowledge sources to enforce MFA for just a single invited user while allowing others to log in without MFA. The system enforces MFA based on the configuration and the user’s enrolled factors, not on a per-user flag or invite. Summary: It is not possible, according to the knowledge sources, to invite a user and enforce MFA just for that user while keeping it optional or disabled for others. MFA enforcement is controlled at the flow or project level, not per user.
q
@chilly-king-10285 I use the ORY Account experience UI for the login page. When on the login page the url looks like this: https://auth.example.com/ui/login?flow=7940a394-9ddb-477f-a9cc-217e8dba62ea There's been a few cases where users bookmark that url so they can quickly access our website (including the flow). Next time the use the bookmark, since the flow is expired, they are shown this error:
Copy code
{
  "error": {
    "code": 403,
    "status": "Forbidden",
    "request": "b7f05762-f0ea-95d7-851a-63c7bc9d1508",
    "message": "Access credentials are not sufficient to access this resource"
  }
}
Is it possible to just redirect them to the login page with a new flow id when this happens?
c
A message from kapa.ai
The knowledge sources confirm that this is a known issue: when a user bookmarks a login URL containing a flow ID and later revisits it after the flow has expired, they encounter an error instead of being redirected to a fresh login flow. This behavior has been reported by other users as well, and the expected user experience would be to automatically redirect to a new flow or a more user-friendly page. Currently, the Ory Account Experience UI does not automatically redirect users to a new login flow when the bookmarked (and now expired) flow is accessed. Users see an error and must manually navigate to the login page to start a new flow. This limitation is acknowledged in Ory's issue tracker, and there is interest in improving the experience so that expired flows result in a transparent redirect to a new flow, but as of now, this is not the default behavior [If user sits on Account-Experience login page, they get a login flow expired error](https://github.com/ory/network/issues/417). Summary: - There is currently no built-in way in the Ory Account Experience UI to automatically redirect users to a new login flow if they access an expired flow via a bookmarked URL. - This is a known issue and may be improved in the future, but for now, users will see an error and need to manually navigate to the login page to start a new flow. If you require this behavior immediately, you would need to implement a custom UI that detects expired flows and programmatically redirects users to a new flow initialization endpoint.
q
@chilly-king-10285 Noticed something new. When the user clicks on their bookmakr, they're first redirected to https://auth.afternoon.co.uk/ui/welcome and then the error I shown above is displayed. Looks like there's a default behaviour to redirect to https://auth.afternoon.co.uk/ui/welcome, can this default uri redirection be changed?
c
A message from kapa.ai
Yes, the default URI redirection after a flow error (such as an expired flow) can be changed in your Ory configuration. The knowledge sources explain that the default redirect URL is controlled by the
selfservice.default_browser_return_url
setting, and you can also set a specific error UI URL using
selfservice.flows.error.ui_url
. If these are not set, users may end up on the default welcome page or see an error. To change the default redirection, update your configuration as follows:
Copy code
yaml selfservice:   default_browser_return_url: <https://your-app.com/dashboard>   flows:     error:       ui_url: <https://your-app.com/error>
You can also set these values using environment variables: -
SELFSERVICE_DEFAULT_BROWSER_RETURN_URL
-
SELFSERVICE_FLOWS_ERROR_UI_URL
This will ensure that, instead of being redirected to
/ui/welcome
, users are sent to your specified URL after an error or when a flow expires [Default return to](https://www.ory.sh/docs/kratos/fallback/default_return_to) [Error UI](https://www.ory.sh/docs/kratos/fallback/error). If you are using the Ory Console, you can configure these URLs in the relevant settings sections for redirects and error handling. Summary: Yes, you can change the default URI redirection by setting
selfservice.default_browser_return_url
and/or
selfservice.flows.error.ui_url
in your Ory configuration. This will override the default behavior of redirecting to
/ui/welcome
after a flow error.
q
@chilly-king-10285 how do I set the selfservice default_browser_return_url? Can this be achieved via the portal?
c
A message from kapa.ai
Yes, you can set the
selfservice.default_browser_return_url
via the Ory Console (portal). According to the documentation, this setting controls where users are redirected by default after completing self-service flows or when a flow expires. In the Ory Console, you can configure this under Browser Redirects. How to do it: 1. Go to the [Ory Console](https://console.ory.sh/). 2. Select your project. 3. Navigate to Browser Redirects. 4. Set the Default Redirect URL to your desired address. 5. Save your changes. This will update the
selfservice.default_browser_return_url
and control the default redirection behavior for your project [Default return to](https://www.ory.sh/docs/kratos/fallback/default_return_to).
q
@chilly-king-10285 can I change my environment from development to staging?
c
A message from kapa.ai
Based on the knowledge sources, you cannot change the environment of an existing Ory Network project from development to staging (or to any other environment). The environment (development, staging, production) is set when you create the project and cannot be changed afterwards. If you need a project in a different environment, you must create a new project with the desired environment setting [Create Ory Network projects](https://www.ory.sh/docs/migrate-to-ory#create-ory-network-projects) [API Reference: createProject](https://raw.githubusercontent.com/ory/docs/master/docs/reference/api.json). If you need to migrate configuration or data, you can export your configuration from the old project and import it into the new one using the Ory CLI [Ory Identities CLI](https://www.ory.sh/docs/guides/cli/identity-cli).
q
@chilly-king-10285 will the value of my cookie change during my session or stay the same
c
A message from kapa.ai
The value of your Ory session cookie can change during your session. According to the knowledge sources, the session can be updated and changed at any point in time, for example, when the user updates their profile or performs actions like refreshing the session or adding another authentication factor. When such changes occur, Ory may issue a new session cookie with updated information. This ensures that the session state remains consistent and secure [Session-management overview](https://www.ory.sh/docs/kratos/session-management/overview#json-web-token-jwt-support). So, while the session cookie may remain the same during periods of inactivity, it is possible and expected for its value to change if the session is refreshed or updated.
q
@chilly-king-10285 I'm trying to set a default redirect url for my development environment. My website is running on localhost:3000 but when I try to enter that in the Ory console for the browser redirect I get an error saying: The value must be a valid URL or path.
c
A message from kapa.ai
When setting a default redirect URL in the Ory Console, the value must be a valid URL with a scheme (e.g.,
http://
or
https://
). According to the knowledge sources,
<http://localhost:3000>
is considered a valid URL for local development, and it is supported by Ory for redirect purposes [Invalid URL](https://www.ory.sh/docs/concepts/redirects#troubleshooting). If you are seeing an error, double-check that you are entering the full URL, including the scheme (
http://
). For example:
Copy code
<http://localhost:3000>
If you are still encountering issues, it may be a temporary validation bug in the Ory Console UI. The knowledge sources also mention that there was a previous issue where the Console did not accept
localhost
as a valid redirect URI, but this has since been resolved [Ory Cloud OAuth2 Client error configuring localhost for redirect URI](https://github.com/ory/network/issues/282). Summary: - Make sure you enter the full URL:
<http://localhost:3000>
- The value must include the scheme (
http://
) - If the error persists, it may be a UI bug, as
localhost
URLs are supported for development. If you continue to have trouble, try using the Ory CLI to set the redirect URL, as this has been a reliable workaround in the past.
q
@chilly-king-10285 I setup social sign in with google. I'm able to go through the whole flow, however at the end of the flow it does a request to whoami with the acquare cookie, and gets a 401. What could be wrong?
c
A message from kapa.ai
A 401 response from the
/sessions/whoami
endpoint after completing the Google social sign-in flow typically means that the session cookie is missing, invalid, or not being sent correctly with the request. Based on the knowledge sources, here are the most common causes and troubleshooting steps: 1. Cookie Domain or Path Issues: If your frontend and Ory are not on the same domain (or subdomain), the browser may not send the session cookie with the request to
/sessions/whoami
. Make sure your Ory configuration sets the cookie domain appropriately so that cookies are shared between your app and Ory [Cookie settings](https://www.ory.sh/docs/kratos/guides/configuring-cookies) [Cookie-based security model](https://www.ory.sh/docs/security-model#http-cookies). 2. CORS and Credentials: If you are making AJAX requests from your frontend, ensure you are sending requests with
credentials: "include"
and that CORS is configured to allow credentials. Otherwise, cookies will not be sent with the request [CSRF troubleshooting](https://www.ory.sh/docs/troubleshooting/csrf#ory-oauth2) [Check session status](https://www.ory.sh/docs/identities/sign-in/check-session-token-cookie-api). 3. Session Not Persisted: If Ory Kratos is configured with
session.cookie.persistent: false
, the session may not persist across requests, especially after redirects. Setting
session.cookie.persistent: true
can resolve this issue [Github Issue #194](https://github.com/ory/network/issues/194). 4. Secrets Rotation or Mismatch: If the secrets used to encrypt cookies have changed or are not consistent, all cookies will be invalid and users will be signed out [Cookie settings](https://www.ory.sh/docs/kratos/guides/configuring-cookies). 5. Social Sign-In Redirect URI Issues: If the redirect URI used in your social sign-in provider does not match the one configured in Ory, the flow may not complete correctly, resulting in missing or invalid cookies [Social sign-in troubleshooting](https://www.ory.sh/docs/troubleshooting/troubleshooting-social-sign-in#redirect_uri-mismatch). What to check: - Ensure your frontend and Ory are on the same (sub)domain and the cookie domain is set correctly. - Use
credentials: "include"
in your frontend requests. - Check that
session.cookie.persistent
is enabled if you want persistent sessions. - Make sure your secrets have not changed unexpectedly. - Verify that the social sign-in redirect URI matches exactly. If you have checked all of the above and the issue persists, inspect the network requests in your browser’s developer tools to see if the session cookie is being set and sent as expected. If you need more targeted help, please provide details about your domain setup, cookie configuration, and how you are making the
/sessions/whoami
request.
q
@chilly-king-10285 I clicked the invitation link and was directed to the UI exprience registration screen. There I can either add a password or link social sign in. I cliked on google for social sign in and after completing the flow got this error: When reporting this error, please include the following information: ID:
b9ae853b-8576-4c24-9278-67e19b85bb2a
Time:
Wed, 01 Oct 2025 11:57:01 GMT
Message:
The request was initiated by someone else and has been blocked for security reasons. Please go back and try again.
c
A message from kapa.ai
The error message you encountered—"The request was initiated by someone else and has been blocked for security reasons"—typically indicates a security check failure, most likely related to CSRF (Cross-Site Request Forgery) or session state mismatch during the registration or social sign-in flow. Based on the knowledge sources, this can happen if: - The invitation (recovery) flow was started in one browser or device, but the social sign-in was completed in another, causing a mismatch in session or CSRF tokens. - The flow was started, then the user left the page or the flow expired, and then tried to continue or complete the flow after it was no longer valid. - Cookies or session data were cleared or blocked, breaking the link between the invitation flow and the social sign-in attempt. What you can do: - Make sure to complete the invitation and social sign-in flow in the same browser and device, without switching tabs, browsers, or devices. - Avoid refreshing the page or navigating away during the flow. - If you see this error, go back to the original invitation link and try the process again from the start. The knowledge sources do not provide a more detailed explanation or a specific configuration fix for this error, but it is consistent with security measures designed to prevent session hijacking or CSRF attacks [CSRF troubleshooting](https://www.ory.sh/docs/troubleshooting/csrf/). If the problem persists, it may be helpful to clear cookies and try again, or contact Ory support with the error ID for further investigation.
q
@chilly-king-10285 I fixed the issue above but now getting When reporting this error, please include the following information: ID:
8951d188-75ca-4d9a-9997-4181cb1efb1a
Time:
Wed, 01 Oct 2025 12:03:14 GMT
Message:
No active session was found in this request.
c
A message from kapa.ai
The error message
No active session was found in this request.
indicates that Ory could not find a valid session for your request. According to the knowledge sources, this typically happens when: - The session cookie is missing, invalid, or not being sent with the request. - The session has expired or was never established. - There is a domain or path mismatch preventing the cookie from being sent. - The flow was completed in a different browser, device, or incognito window, so the session is not available. What you can do: - Ensure you are completing the flow in the same browser and tab where you started it. - Make sure cookies are enabled and not being blocked by browser settings or extensions. - If you are running your app and Ory on different domains or ports, check that the cookie domain is configured correctly so the session cookie is sent with requests [Check session status](https://www.ory.sh/docs/identities/sign-in/check-session-token-cookie-api). - If the session has expired, restart the flow from the beginning by clicking the invitation link again. If the issue persists, inspect your browser’s developer tools to see if the session cookie is being set and sent with requests to Ory. If not, review your Ory cookie and domain configuration. The knowledge sources do not provide a more specific fix for this error, but it is a common symptom of session or cookie issues in Ory flows.
q
@chilly-king-10285 I fixed the issue above. No when I complete the flow I'm redirected a Ory UI to set my password. I'd like to just redirect the user to my app instead
c
A message from kapa.ai
To redirect the user to your app after completing the flow (instead of showing the Ory UI to set a password), you should configure the post-registration or post-verification redirect URLs in your Ory project. You can do this in two ways: 1. Set the
return_to
or
after_verification_return_to
query parameter
when initializing the registration flow. For example:
Copy code
https://<your-ory-project>/self-service/registration/browser?return_to=<https://your-app.com/dashboard>
Or, if you want to control where the user is sent after email verification:
Copy code
https://<your-ory-project>/self-service/registration/browser?after_verification_return_to=<https://your-app.com/dashboard>
This will redirect the user to your specified URL after the flow is completed, instead of the default Ory UI [User Registration Documentation](https://www.ory.sh/docs/kratos/self-service/flows/user-registration) [Post-verification redirection](https://www.ory.sh/docs/kratos/concepts/browser-redirect-flow-completion#post-verification-redirection). 2. Set the default redirect URL in the Ory Console: - Go to the [Ory Console](https://console.ory.sh/). - Navigate to your project. - Go to Browser Redirects. - Set the Default Redirect or Post-Registration Redirect to your app’s URL (e.g., `https://your-app.com/dashboard`). - Save your changes. This will ensure that, unless overridden by a query parameter, users are redirected to your app after completing the flow [Default return to](https://www.ory.sh/docs/kratos/fallback/default_return_to). Note: If you want to skip the password setup entirely for users who register via social sign-in, make sure your registration flow and hooks are configured to issue a session and redirect immediately after successful social sign-in, rather than prompting for a password. If you need more granular control, you can also use the Ory Actions or webhooks to implement custom logic after registration, but the above methods are the standard way to control post-flow redirection in Ory. If you need to redirect after verification (e.g., after clicking a link in an email), use the
after_verification_return_to
parameter as described above [Post-verification redirection](https://www.ory.sh/docs/kratos/concepts/browser-redirect-flow-completion#post-verification-redirection). If you have set these and still see the Ory UI, double-check that your redirect URLs are allow-listed in your Ory project settings [Advanced redirects](https://www.ory.sh/docs/kratos/bring-your-own-ui/custom-ui-advanced-integration).
q
@chilly-king-10285 now I get the error: Could not update settings: Requested return_to URL "https://portal.staging.afternoon.co.uk" is not allowed.
c
A message from kapa.ai
The error message: > Requested return_to URL "https://portal.staging.afternoon.co.uk" is not allowed. means that the URL you are trying to redirect to after the flow is not in your project's allow list of return URLs. Ory enforces this allow list to prevent open redirect attacks. How to fix: You need to add
<https://portal.staging.afternoon.co.uk>
to your project's allowed return URLs. To do this: 1. Go to the [Ory Console](https://console.ory.sh/). 2. Select your project. 3. Navigate to Browser Redirects or the section for Allowed Return URLs. 4. Add
<https://portal.staging.afternoon.co.uk>
to the list. 5. Save your changes. After this, Ory will allow redirection to that URL. This requirement is described in the documentation: > Because Ory Identities prevents Open Redirect Attacks, you need to whitelist the domain in your Ory Identities config when using the
?return_to=
query parameter. For other return urls specified inside the configuration it isn't necessary to add the domain to the whitelist. [Redirection configuration](https://www.ory.sh/docs/kratos/concepts/browser-redirect-flow-completion#redirection) If you do not add the URL to the allow list, Ory will block the redirect and show the error you encountered.
q
@chilly-king-10285 the Ory UI experience login screen is very flaky when running with playwright, is this a known issue?
c
A message from kapa.ai
I'm sorry, but the knowledge sources do not contain any explicit information about issues with the Ory Account Experience UI login screen being flaky when running with Playwright. There is no mention of Playwright-specific problems or test automation flakiness in the provided documentation or issue reports. If you are experiencing instability, it may be related to how Playwright handles browser sessions, cookies, or CSRF tokens, but this is not addressed in the knowledge sources.
q
@chilly-king-10285 I've setup social sign in for microsoft. My app registration is set to account in any direcoty and personal accounts. When I try to do the oauth flow I get the following error: Sorry, but we’re having trouble signing you in. AADSTS50020: User account '[email protected]' from identity provider 'live.com' does not exist in tenant 'Default Directory' and cannot access the application 'c3cc98e1-8be6-482c-9bc4-910f2e33f5c7'(Afternoon) in that tenant. The account needs to be added as an external user in the tenant first. Sign out and sign in again with a different Azure Active Directory user account.
c
A message from kapa.ai
The error message you are seeing: > AADSTS50020: User account '<[email protected]>' from identity provider 'live.com' does not exist in tenant 'Default Directory' and cannot access the application ... The account needs to be added as an external user in the tenant first. indicates that the Microsoft account you are trying to use does not exist in the Azure AD tenant that your application is registered in, and the application is not accessible to this user. Based on the Ory documentation, when configuring Microsoft as a social sign-in provider, you must carefully set the Tenant field in your Ory provider configuration to control which users can sign in: - If you want to allow both work/school and personal Microsoft accounts, you should set the tenant to
common
. - If you want to allow only users from your organization, set the tenant to your Directory (tenant) ID or your organization's domain. From the error, it appears your app registration may not be set up to allow personal accounts, or the tenant value in your Ory configuration is not set to
common
. Double-check your Ory provider configuration and ensure you have:
Copy code
yaml microsoft_tenant: common
and that your Azure app registration is set to allow "Accounts in any organizational directory (Any Microsoft Entra ID tenant - Multitenant) and personal Microsoft accounts (e.g. Skype, Xbox)" as described in the Ory documentation [Selecting a Tenant](https://www.ory.sh/docs/kratos/social-signin/microsoft#selecting-a-tenant). If you have already set
microsoft_tenant: common
and are still seeing this error, ensure that your Azure app registration is correctly configured to allow both organizational and personal accounts, and that the redirect URI matches what is configured in Ory [Microsoft Social Sign-In](https://www.ory.sh/docs/kratos/social-signin/microsoft). Summary: - Set
microsoft_tenant: common
in your Ory provider config. - Ensure your Azure app registration allows both organizational and personal accounts. - Make sure the redirect URI is correct. If these are set correctly and the issue persists, the knowledge sources do not provide further troubleshooting steps for this specific Microsoft error.
q
@chilly-king-10285 in the configuration it asks gives me the subject source option between Userinfo and Me, What options should I choose?
c
A message from kapa.ai
When configuring Microsoft as a social sign-in provider in Ory, the subject source determines which unique identifier is used for the user. You have two main options: - userinfo: Uses the
sub
field from the OIDC
id_token
or the standard OIDC
/userinfo
endpoint. This is the default and standard way to identify users in most OIDC flows. - me: Uses the
id
field returned by the
<https://graph.microsoft.com/v1.0/me>
endpoint, or the
oid
(Object ID) field. This is useful if your legacy system or integration expects the Microsoft Graph
id
or
oid
as the user identifier. Which should you choose? - Use userinfo if you want to stick with the standard OIDC subject identifier, which is compatible with most OIDC providers and applications. - Use me if you are migrating from a system that uses the Microsoft Graph
id
or
oid
as the user’s unique identifier, or if you specifically need to match users based on those fields. If you choose
me
, you must also add the
<https://graph.microsoft.com/User.Read>
scope to your configuration, as this is required to access the Microsoft Graph
/me
endpoint [Subject identifier source](https://www.ory.sh/docs/kratos/social-signin/microsoft#subject-identifier-source). Example configuration:
Copy code
yaml providers:   - id: microsoft     ...     subject_source: me # or userinfo     scope:       - <https://graph.microsoft.com/User.Read> # Required when using 'me'     ...
Summary: - Choose userinfo for standard OIDC compatibility. - Choose me if you need to use the Microsoft Graph
id
or
oid
as the subject identifier, and add the required scope. For more details, see the [Ory documentation on subject identifier source for Microsoft](https://www.ory.sh/docs/kratos/social-signin/microsoft#subject-identifier-source).
q
@chilly-king-10285 can I enforce Single sign on for only certain accounts?
c
A message from kapa.ai
Based on the knowledge sources, Ory does not currently support enforcing Single Sign-On (SSO) for only certain accounts or users. SSO enforcement, like MFA enforcement, is configured at the flow or project level (for example, via
required_aal
or SSO connection settings), and not on a per-user basis. The documentation and configuration examples show that SSO is enabled or required for all users matching certain criteria (such as email domain or organization), but there is no documented way to selectively enforce SSO for individual accounts while allowing others to use different authentication methods [See it live](https://www.ory.sh/docs/kratos/organizations#see-it-live) [Step-up authentication](https://www.ory.sh/docs/kratos/mfa/step-up-authentication/). Summary: You cannot enforce SSO for only certain accounts; SSO enforcement is managed at the project or organizational level, not per user. If you need more granular control, such as enforcing SSO for specific users only, this is not currently supported according to the available documentation.
q
@chilly-king-10285 My app has customers that are grouped into organisations. One of the organisations would like to enforce SSO to all members of that organisation (i.e a subset of all my users). How can I achieve that?
c
A message from kapa.ai
You can achieve SSO enforcement for all members of a specific organization using Ory’s Organizations feature. Here’s how it works: - In Ory, organizations allow you to group users within a project. Each organization can have its own SSO (OIDC or SAML) connections. - When an organization is configured with SSO and associated with one or more email domains, any user registering or logging in with an email address from that domain will be required to use the organization’s SSO connection. The login screen will show a Sign in with SSO button instead of the password field for those users. Users outside the organization (with other email domains) will not see this option and can use other authentication methods as configured for your project. Example flow: - You configure an organization in Ory and associate it with the customer’s domain (e.g.,
@customer.com
). - You set up the SSO connection (OIDC or SAML) for that organization. - When a user with an email like
<mailto:[email protected]|[email protected]>
tries to register or log in, they are required to use SSO. - Other users (not in that organization) are not affected and can use other login methods. This approach enforces SSO only for members of the specified organization, as requested [See it live](https://www.ory.sh/docs/kratos/organizations#see-it-live) [B2B Single Sign-On & SAML](https://www.ory.sh/docs/kratos/organizations). If you need to support multiple organizations with different SSO requirements, you can configure each organization separately with its own SSO connections and associated domains.
q
@chilly-king-10285 My app has customers that are grouped into organisations. One of the organisations would like to enforce social sign in to all members of that organisation (i.e a subset of all my users). How can I achieve that?
c
A message from kapa.ai
You can enforce social sign-in (such as Google, Microsoft, etc.) for all members of a specific organization in Ory by using the Organizations feature. Here’s how it works according to the knowledge sources: - Organizations in Ory allow you to group users and associate each organization with one or more domains (e.g.,
@customer.com
). - You can configure each organization to have its own SSO or social sign-in connections. - When a user attempts to register or log in with an email address that matches an organization's domain, Ory will require them to use one of the organization's configured SSO or social sign-in providers. The login screen will show a Sign in with SSO (or the relevant social provider) button instead of the password field for those users. Users outside the organization (with other email domains) will not see this option and can use other authentication methods as configured for your project. Example flow: - Configure an organization in Ory and associate it with the customer’s domain (e.g.,
@customer.com
). - Set up the desired social sign-in provider(s) for that organization. - When a user with an email like
<mailto:[email protected]|[email protected]>
tries to register or log in, they are required to use the configured social sign-in. - Other users (not in that organization) are not affected and can use other login methods. This setup enforces social sign-in only for members of the specified organization, as requested [See it live](https://www.ory.sh/docs/kratos/organizations#see-it-live) [B2B Single Sign-On & SAML](https://www.ory.sh/docs/kratos/organizations). If you need to support multiple organizations with different social sign-in requirements, you can configure each organization separately with its own providers and associated domains.
q
@chilly-king-10285 is there another way to achieve this without organisations? These are avaialble only in the Growth tier which is much more expensive
c
A message from kapa.ai
I'm sorry, but the knowledge sources do not describe an alternative way to enforce social sign-in (or SSO) for only a subset of users (such as members of a specific customer group) without using the Organizations feature. The documented approach for enforcing SSO or social sign-in for specific groups is through Organizations, which, as you noted, is only available in the Growth tier [B2B Single Sign-On & SAML](https://www.ory.sh/docs/kratos/organizations). There is no mention in the knowledge sources of a built-in method to enforce authentication method restrictions for subsets of users based on traits like email domain or group membership outside of the Organizations feature. If you need this functionality and cannot use Organizations, the knowledge sources do not provide a supported workaround.
q
@chilly-king-10285 when I set my password in my development ory instance I get the error: 1. Requested return_to URL "http://localhost:5173" is not allowed.
c
A message from kapa.ai
The error message: > Requested return_to URL "http://localhost:5173" is not allowed. means that the URL you are trying to redirect to after setting your password is not in your project's allow list of return URLs. Ory enforces this allow list to prevent open redirect attacks. How to fix: You need to add
<http://localhost:5173>
to your project's allowed return URLs. To do this: 1. Go to the [Ory Console](https://console.ory.sh/). 2. Select your project. 3. Navigate to Browser Redirects or the section for Allowed Return URLs. 4. Add
<http://localhost:5173>
to the list. 5. Save your changes. After this, Ory will allow redirection to that URL. This requirement is described in the documentation: > Because Ory Identities prevents Open Redirect Attacks, you need to whitelist the domain in your Ory Identities config when using the
?return_to=
query parameter. For other return urls specified inside the configuration it isn't necessary to add the domain to the whitelist [Redirection configuration](https://www.ory.sh/docs/kratos/concepts/browser-redirect-flow-completion#redirection). If you do not add the URL to the allow list, Ory will block the redirect and show the error you encountered.
q
@chilly-king-10285 can I disable a user\s password programmatically?
c
A message from kapa.ai
Yes, you can disable a user's password programmatically in Ory by deleting the password credential for that identity using the Admin API. According to the knowledge sources, you can use the following endpoint:
Copy code
DELETE /admin/identities/{id}/credentials/password
This API call deletes the password credential for the specified identity, effectively disabling password-based login for that user. Note that you cannot delete passkeys or code auth credentials through this API [Delete a credential for a specific identity](https://raw.githubusercontent.com/ory/kratos/master/spec/api.json). Summary: You can programmatically disable a user's password by deleting their password credential via the Admin API.
q
can I programmatically check if someone has Single Sign on setup?
@chilly-king-10285 can I programmatically check if someone has Single Sign on setup?
c
A message from kapa.ai
The knowledge sources do not explicitly describe a direct API or method to programmatically check if a user has Single Sign-On (SSO) set up. While you can retrieve an identity’s credentials and see which types (e.g.,
oidc
for social sign-in) are present, there is no documented, built-in endpoint or flag that specifically indicates "this user has SSO enabled" in a single call. You can, however, fetch an identity’s details and inspect the
credentials
field to see if there are
oidc
(OpenID Connect) or other SSO-related credentials associated with the user. For example, using the Admin API:
Copy code
bash curl --request GET \   --header "Authorization: Bearer {ORY_API_KEY}" \   'https://$<http://PROJECT_SLUG.projects.oryapis.com/admin/identities/<identity_id|PROJECT_SLUG.projects.oryapis.com/admin/identities/<identity_id>>'
In the response, if you see an
oidc
credential under the
credentials
object, it indicates that the user has set up SSO via an OIDC provider [Credentials](https://www.ory.sh/docs/kratos/concepts/credentials). Summary: - There is no single API call that returns "SSO enabled: true/false" for a user. - You can check the presence of
oidc
credentials in the user’s identity object to infer if SSO is set up. - The knowledge sources do not provide a more direct or specialized method for this check.
q
@chilly-king-10285 I'm getting 429 on the POST [/self-service/login page
c
A message from kapa.ai
A
429
error on the
POST /self-service/login
page means you are hitting a rate limit for login attempts. Ory enforces both project-level and endpoint-based rate limits to protect against abuse and brute-force attacks. This can happen if there are too many login attempts in a short period, either from the same IP, user, or globally for your project. What you can do: - Wait and retry: The rate limit is temporary. Wait for a minute or two and try again. - Check your plan’s rate limits: Development and staging environments have much lower rate limits than production. For example, in the Developer plan, the default is 5 requests per second and 150 per minute for most endpoints, including login [Project rate limits](https://www.ory.sh/docs/guides/rate-limits#rate-limit-tables-by-subscription-plan). - Reduce automated or repeated login attempts: Automated tests or scripts can quickly exhaust the rate limit. - Upgrade your plan: Higher plans (Production, Growth, Enterprise) have higher rate limits. If you need more capacity, consider upgrading [Project rate limits](https://www.ory.sh/docs/guides/rate-limits#rate-limit-tables-by-subscription-plan). - For Enterprise/Growth: You can request IP whitelisting for trusted sources to get higher limits for those IPs [IP whitelisting](https://www.ory.sh/docs/guides/rate-limits#configuration-and-management). Summary: You are receiving a 429 because you have exceeded the allowed number of login requests. This is expected behavior to prevent abuse. Wait and try again, reduce the frequency of login attempts, or consider upgrading your plan if you need higher limits. For more details, see the [Ory rate limiting documentation](https://www.ory.sh/docs/guides/rate-limits).
q
@chilly-king-10285 I'm getting 1. "Too many API requests from your IP have been registered." for the login endpoint. Can I undo this from the console?
c
A message from kapa.ai
No, you cannot manually undo or reset rate limiting for your IP from the Ory Console. According to the knowledge sources, rate limits are enforced automatically by Ory to protect against abuse and brute-force attacks. When you hit the rate limit (such as "Too many API requests from your IP have been registered."), you must wait for the rate limit window to expire before making more requests. There is no option in the Ory Console to clear or reset these limits [Understand Ory Network rate limiting](https://www.ory.sh/docs/guides/rate-limits). If you are on the Enterprise or Growth plan, you can request IP whitelisting for trusted sources to get higher limits, but this also requires contacting Ory support and cannot be done instantly from the Console [Configuration and management](https://www.ory.sh/docs/guides/rate-limits#configuration-and-management). Summary: You must wait for the rate limit to reset; there is no way to manually undo it from the Ory Console.
q
@chilly-king-10285 when I inviting someone in my app, the invite link sent to the user can only be clicked once. can I change this rule? I have users who click the link but leave before setting their password and then get confused when clicking the link again
c
A message from kapa.ai
I'm sorry, but the knowledge sources do not describe a way to change the rule that invite (recovery) links can only be used once. The documentation states that recovery/invite links are single-use and become invalid after they are used, even if the user did not complete the password setup. If a user clicks the link, leaves before finishing, and tries to use the same link again, they will encounter an error. There is no configuration option mentioned to allow multiple uses of the same invite link or to keep it valid until the password is set [Invite users](https://www.ory.sh/docs/kratos/manage-identities/invite-users/) [Magic links](https://www.ory.sh/docs/kratos/manage-identities/account-recovery/#magic-links). The recommended workaround is to generate and send a new invite link if the user needs another chance to complete the process.
q
@chilly-king-10285 The email template for recovery code generates the recovery code link with {{ .RecoveryCode }}. This recovery code uses the Ory url rater than our custom auth url that we defined in the custom domains section. can this be changed?
c
A message from kapa.ai
Yes, the recovery code link in your email template should use your custom auth URL if your custom domain is configured correctly in Ory. However, there have been cases where, after updating or changing the custom domain, Ory still generates links using the old domain or the default Ory domain. This can happen if the internal configuration for the base URL used in magic/recovery links is not updated automatically. To ensure the recovery links use your custom domain, you may need to reset the
/selfservice/methods/link/config/base_url
configuration key. You can do this with the Ory CLI:
Copy code
bash ory patch identity-config <your-project-id> \   --remove '/selfservice/methods/link/config/base_url'
Running this command will force Ory Network to update the base URL to your current custom domain, so future recovery links will use the correct URL in your emails [Magic links use old custom domain name](https://www.ory.sh/docs/troubleshooting/magic-link-verification-url) [Github Issue: Updating custom domain does not update magic link url correctly](https://github.com/ory/network/issues/223). If you have already set your custom domain and are still seeing the Ory URL, try the above workaround. If the issue persists, double-check your custom domain configuration in the Ory Console. Summary: You can change the domain used in recovery links by ensuring your custom domain is set and, if needed, resetting the base URL for magic links as described above.
q
@chilly-king-10285 in branding -> UI URLs what's the difference between Custom domain and Custom UI Base URL
c
A message from kapa.ai
In Branding → UI URLs in the Ory Console: - Custom domain refers to the domain name you have added to your Ory project (e.g.,
<http://auth.example.com|auth.example.com>
). This is the domain through which your users interact with Ory’s services and where cookies are scoped. You can add multiple custom domains if you have the Multibrand feature enabled. - Custom UI Base URL is the base URL where your custom user interface (UI) is hosted for a given custom domain. This tells Ory where to redirect users for self-service flows (like login, registration, etc.) for that specific domain. For example, you might set the Custom UI Base URL to
<https://accounts.example.com/auth>
so that Ory knows to send users to your UI at that path for all flows. This setup allows you to serve different UIs (with different themes or branding) for different domains or brands, by specifying a different Custom UI Base URL for each custom domain [Configure Ory to use your UI: Multiple custom domains](https://www.ory.sh/docs/kratos/bring-your-own-ui/configure-ory-to-use-your-ui#multiple-custom-domains). Summary: - Custom domain: The domain your users access (e.g.,
<http://auth.example.com|auth.example.com>
). - Custom UI Base URL: The root URL where your custom UI is served for that domain (e.g., `https://accounts.example.com/auth`).
q
@chilly-king-10285 I've setup a custom domain in Branding -> Custom domains. In the email templates recovery email template the recovering link is under the variable: {{ .RecoveryCode }}. This translates into a url that uses the ory domain rather than the custom one, What am I missing?
c
A message from kapa.ai
If your recovery email template’s
{{ .RecoveryCode }}
link is still using the Ory domain instead of your custom domain, even after setting up a custom domain in Branding → Custom domains, it’s likely that the internal configuration for the base URL used in magic/recovery links has not been updated. This is a known issue: after updating or changing the custom domain, Ory may continue to generate links using the old or default Ory domain. To fix this, you need to reset the
/selfservice/methods/link/config/base_url
configuration key. You can do this using the Ory CLI:
Copy code
bash ory patch identity-config <your-project-id> \   --remove '/selfservice/methods/link/config/base_url'
Running this command will force Ory Network to update the base URL to your current custom domain, so future recovery links will use the correct domain in your emails [Magic links use old custom domain name](https://www.ory.sh/docs/troubleshooting/magic-link-verification-url) [Updating custom domain does not update magic link url correctly](https://github.com/ory/network/issues/223). Summary: You need to reset the magic link base URL as described above to ensure recovery links use your custom domain. If you’ve already set your custom domain and are still seeing the Ory domain, this is the recommended solution.
q
@chilly-king-10285 I've ran that command and still getting the issue, what else could it be?
c
A message from kapa.ai
If you have already run the command to remove
/selfservice/methods/link/config/base_url
and your recovery links in emails are still using the Ory domain instead of your custom domain, the knowledge sources do not provide additional troubleshooting steps or alternative solutions for this specific issue. The documented approach is to reset the base URL as you have done, which should force Ory to use the current custom domain for magic/recovery links [Magic links use old custom domain name](https://www.ory.sh/docs/troubleshooting/magic-link-verification-url) [Updating custom domain does not update magic link url correctly](https://github.com/ory/network/issues/223). If this does not resolve the problem, the knowledge sources do not contain further guidance. Summary: The knowledge sources do not contain enough information to explain why the issue persists after running the recommended command. If the problem continues, you may need to contact Ory support for further assistance.
q
@chilly-king-10285 In our platform, users can only gain access by being invited by another user. We're using invite links. When the user clicks the link they are redirected to the Ory experience screen to set their password. Is it possible to implement a custom screen only for that flow? And are there other recommended registration options that work similar to the invite one I explained?
c
A message from kapa.ai
Yes, it is possible to implement a custom screen specifically for the invite (recovery) flow in Ory, but this requires using a custom UI rather than the default Ory Account Experience UI. How to implement a custom screen for the invite flow: - Ory allows you to bring your own UI for any self-service flow, including recovery (which is used for invite links). You can configure Ory to redirect users to your custom UI for the recovery flow by setting the appropriate UI URL in the Ory Console under Branding → UI URLs. This way, when a user clicks an invite link, they will be directed to your custom screen instead of the default Ory Experience screen [Configure Ory to use your UI](https://www.ory.com/docs/kratos/bring-your-own-ui/configure-ory-to-use-your-ui). - You can use Ory Elements (a React component library) to build this custom UI, or you can build your own from scratch using the Ory SDKs and APIs [Ory Elements Introduction](https://www.ory.com/docs/elements) [Custom UI Overview](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-overview). Recommended registration options similar to invite: - The invite flow you described (admin creates a user, sends a recovery link, user sets their password) is the recommended approach for invite-only platforms in Ory [Invite users](https://www.ory.com/docs/kratos/manage-identities/invite-users/). - There are no alternative invite-style registration flows documented in the knowledge sources. The standard self-service registration flow is intended for open sign-ups, not invite-only scenarios. Summary: You can implement a custom screen for the invite flow by configuring a custom UI for the recovery flow in Ory. The invite-based registration you are using is the recommended approach for invite-only access; no alternative invite-style flows are documented in the knowledge sources.
q
@chilly-king-10285 does the ory elements library have to be in a nextjs app or can it live in a normal react app?
c
A message from kapa.ai
No, the Ory Elements library does not have to be used in a Next.js app—it can also be used in a normal React app. The documentation explicitly states that Ory Elements is a component library for React, and while there are quickstarts and examples for Next.js, you can integrate Ory Elements into any React application [Ory Elements Introduction](https://www.ory.com/docs/elements) [Integrate authentication into React + API](https://www.ory.com/docs/getting-started/integrate-auth/react). You can follow the [React integration guide](https://www.ory.com/docs/getting-started/integrate-auth/react) to use Ory Elements in a standard React (e.g., Vite, Create React App) project. Next.js is not required.
q
@chilly-king-10285 I want to implement my own recovery page. how do I submit the token from my custom page
c
A message from kapa.ai
To implement your own custom recovery page and submit the token (for example, the code the user receives via email), you need to: 1. Render the recovery flow form using the information from the recovery flow object, which you fetch from Ory. 2. Collect the code/token and other required fields (like CSRF token) from the user. 3. Submit the form data to the Ory recovery endpoint. Here’s how you do it: ### 1. Fetch the Recovery Flow When the user lands on your recovery page (typically via a link with a
flow
query parameter), fetch the recovery flow:
Copy code
js const flow = await ory.getRecoveryFlow({ id: flowId })
This will give you the form fields (nodes), including the CSRF token and the code input. ### 2. Render the Form Render the form fields as described in the flow’s
ui.nodes
. For the code step, you’ll have an input for the code, a hidden CSRF token, and a submit button. ### 3. Submit the Recovery Flow When the user submits the form with the code/token, send a POST request to the recovery endpoint with the required data:
Copy code
js await ory.updateRecoveryFlow({   flow: flow.id,   updateRecoveryFlowBody: {     code: userInputCode,         // The code/token the user received     method: "code",              // The method, as required by the flow     csrf_token: csrfTokenValue   // The CSRF token from the flow nodes   } })
Note: - The endpoint expects the
code
,
method
, and
csrf_token
fields in the body. - The
flow
parameter is the ID of the current recovery flow. This process is described in the knowledge sources for custom UI integration and recovery flows [Submit recovery flow](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#submit-recovery-flow). Summary: - Fetch the recovery flow using the flow ID. - Render the form fields, including the code and CSRF token. - Submit the code, method, and CSRF token to the recovery endpoint using the Ory SDK or a direct API call. If you need a code example for a specific framework, let me know!
q
@chilly-king-10285 is it possible for the invite link to point to me custom page?
c
A message from kapa.ai
Yes, it is possible for the invite link (recovery link) to point to your custom page. You can achieve this by configuring the Recovery UI URL in your Ory project settings. When you set a custom Recovery UI URL, Ory will generate invite/recovery links that direct users to your specified page instead of the default Ory Experience UI. To do this: 1. Go to the Ory Console. 2. Navigate to Branding → UI URLs. 3. Set the Recovery UI URL to your custom page (e.g., `https://your-app.com/recovery`). After this configuration, invite links sent to users will point to your custom recovery page, where you can implement your own UI and handle the recovery flow as needed [Configure Ory to use your UI](https://www.ory.com/docs/kratos/bring-your-own-ui/configure-ory-to-use-your-ui). If you are using custom domains, ensure your custom page is accessible under the correct domain and that your Ory project is configured to use it for the recovery flow.
q
@chilly-king-10285 I need to add a link to our privacy policy in the login page. Can this be added in the ory login experience?
c
A message from kapa.ai
The knowledge sources do not explicitly mention a built-in option to add a custom link (such as a privacy policy) directly to the Ory Account Experience login page through the Ory Console or configuration. While the Account Experience UI supports theming and some customization, there is no documented feature for injecting additional links or custom HTML into the login page via the standard settings [Account Experience Overview](https://www.ory.com/docs/account-experience). If you require this functionality, you would need to implement a custom UI for the login flow, where you have full control over the page content and can add a privacy policy link as needed [Custom UI Overview](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-overview). Summary: There is no documented way to add a privacy policy link to the default Ory login experience UI. For this level of customization, you should use a custom UI.
q
@chilly-king-10285 is it possible to do a custom UI that just reuses the Ory accoutn experience so I can slowly migrate to the custom UI?
c
A message from kapa.ai
The knowledge sources do not describe a built-in way to "wrap" or directly reuse the Ory Account Experience UI within a custom UI, nor do they mention a hybrid approach where you can selectively override only certain screens while keeping others from the Account Experience. The documented options are: - Use the Ory Account Experience UI as-is, with theming and branding options available in the Ory Console. - Switch to a fully custom UI by configuring custom UI URLs for each flow in the Ory Console. Once you set a custom UI base URL, the Account Experience is no longer used for those flows [Configure Ory to use your UI](https://www.ory.com/docs/kratos/bring-your-own-ui/configure-ory-to-use-your-ui). There is a [feature request](https://github.com/ory/network/issues/350) for more granular control (such as toggling individual pages between custom and Account Experience), but as of now, this is not supported. Summary: It is not currently possible to gradually migrate by mixing the Ory Account Experience UI and your custom UI for different screens. Once you set a custom UI for a flow, the Account Experience is disabled for that flow. If you need gradual migration, you would need to implement the custom UI for each flow as you go, but cannot "fall back" to the Account Experience for the others.
q
@chilly-king-10285 I'm getting this error when patching an identity: { "error": { "code": 403, "status": "Forbidden", "request": "2bca80f9-8a22-9cbd-a737-14ee58e69828", "message": "Access credentials are not sufficient to access this resource" } }
c
A message from kapa.ai
The error message:
Copy code
{
  "error": {
    "code": 403,
    "status": "Forbidden",
    "request": "2bca80f9-8a22-9cbd-a737-14ee58e69828",
    "message": "Access credentials are not sufficient to access this resource"
  }
}
means that the API key or credentials you are using do not have sufficient permissions to perform the patch operation on the identity. According to the knowledge sources, to patch an identity using the Ory Admin API, you must authenticate with an API key that has the correct privileges (an Ory Project API Key for the relevant project) and use it in the
Authorization
header as a Bearer token [Ory Network API Keys](https://www.ory.com/docs/concepts/personal-access-token). If you are using a Project API Key and still see this error, ensure: - The API key is for the correct project. - You are not using a Workspace API Key or a key with insufficient scope. - The key is included in the request as:
Copy code
Authorization: Bearer <API_KEY>
If you are using the Search API or other preview/enterprise features, note that some endpoints are only available on certain plans, and you may get a 403 if your plan does not include access [Github Issue: Webhook after I created an identity over the API](https://github.com/ory/network/issues/436). Summary: A 403 Forbidden error when patching an identity usually means your API key does not have the required permissions or is not being used correctly. Double-check your API key type, project association, and request headers.
q
@chilly-king-10285 I want to create a custom login screen for my react app. can you give me a super basic login screen that implementation?
c
A message from kapa.ai
Here’s a minimal React login page that uses Ory’s Frontend API and follows the basic “create → render → submit login flow” pattern. It renders only the
default
+
password
nodes and posts the form back to Ory, as shown in the docs examples.[^1]
Copy code
tsx // src/Login.tsx import React, { useEffect, useState } from "react" import {   Configuration,   FrontendApi,   LoginFlow,   UiNode,   UiNodeInputAttributes, } from "@ory/client" import {   filterNodesByGroups,   isUiNodeInputAttributes, } from "@ory/integrations/ui" import { useSearchParams } from "react-router-dom"  const ory = new FrontendApi(   new Configuration({     // In dev, use your Ory Tunnel URL, e.g. <http://localhost:4000>     basePath: "<http://localhost:4000>",     baseOptions: {       withCredentials: true, // important for cookies     },   }), )  export const Login: React.FC = () => {   const [flow, setFlow] = useState<LoginFlow>()   const [searchParams] = useSearchParams()    // Either get existing flow (?flow=) or create a new browser flow   useEffect(() => {     const flowId = searchParams.get("flow")     if (flowId) {       ory         .getLoginFlow({ id: flowId })         .then(({ data }) => setFlow(data))         .catch(() => {           // handle error (e.g. redirect or create new flow)         })       return     }      ory       .createBrowserLoginFlow()       .then(({ data }) => setFlow(data))       .catch(() => {         // handle error       })   }, [])    // Map a single UI node to an input or button   const mapNode = (node: UiNode, idx: number) => {     if (!isUiNodeInputAttributes(node.attributes)) return null      const attrs = node.attributes as UiNodeInputAttributes     if (attrs.type === "submit" || attrs.type === "button") {       return (         <button           key={idx}           type={attrs.type as "submit" | "button"}           name={attrs.name}           value={attrs.value}           disabled={attrs.disabled}         >           {node.meta?.label?.text ?? attrs.name}         </button>       )     }      return (       <div key={idx}>         {node.meta?.label?.text && (           <label>             {node.meta.label.text}             <input               name={attrs.name}               type={attrs.type}               autoComplete={                 attrs.autocomplete || attrs.name === "identifier"                   ? "username"                   : ""               }               defaultValue={attrs.value}               required={attrs.required}               disabled={attrs.disabled}             />           </label>         )}         {!node.meta?.label?.text && (           <input             name={attrs.name}             type={attrs.type}             defaultValue={attrs.value}             required={attrs.required}             disabled={attrs.disabled}           />         )}       </div>     )   }    if (!flow) return <div>Loading login…</div>    // Super basic: render default + password nodes and submit via browser POST   return (     <form action={flow.ui.action} method={flow.ui.method}>       {filterNodesByGroups({         nodes: flow.ui.nodes,         groups: ["default", "password"],       }).map((node, idx) => mapNode(node, idx))}     </form>   ) }
This is directly adapted from the official React examples for: - creating a login flow; - fetching a login flow; - rendering
default
+
password
nodes.[[Create login flow](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#create-login-flow); [Get login flow](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#get-login-flow)] If you want the version that also handles submit via XHR (and updates the flow on validation errors) instead of letting the browser post the form, you can use the
updateLoginFlow
example from the docs as a next step.[[Submit login flow](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#submit-login-flow)] [^1]: The structure (
createBrowserLoginFlow
,
getLoginFlow
,
filterNodesByGroups
, and mapping
UiNodeInputAttributes
) is taken from the React examples in the “Integration basics → Login flow” docs.
q
I get the following flow { "id": "735bc986-a823-4391-b658-470cb2d48e33", "organization_id": null, "type": "browser", "expires_at": "2025-12-02T235301.452384886Z", "issued_at": "2025-12-02T225301.452384886Z", "request_url": "http://localhost:4000/self-service/login/browser", "ui": { "action": "http://localhost:4000/self-service/login?flow=735bc986-a823-4391-b658-470cb2d48e33", "method": "POST", "nodes": [ { "type": "input", "group": "identifier_first", "attributes": { "name": "identifier", "type": "text", "value": "", "required": true, "autocomplete": "username webauthn", "disabled": false, "node_type": "input" }, "messages": [], "meta": { "label": { "id": 1070002, "text": "E-Mail", "type": "info", "context": { "name": "traits.email", "title": "E-Mail" } } } }, { "type": "input", "group": "identifier_first", "attributes": { "name": "method", "type": "submit", "value": "identifier_first", "disabled": false, "node_type": "input" }, "messages": [], "meta": { "label": { "id": 1070009, "text": "Continue", "type": "info" } } }, { "type": "input", "group": "default", "attributes": { "name": "csrf_token", "type": "hidden", "value": "wJaBRIKUKjjgT8fceB94LhqI4oRmclRoorY9hV9zOsdEfP3XzBl387aP70fpnNUlDl0b2yxhYAyw2MT3WrJ3nw==", "required": true, "disabled": false, "node_type": "input" }, "messages": [], "meta": {} } ] }, "created_at": "2025-12-02T225301Z", "updated_at": "2025-12-02T225301Z", "refresh": false, "requested_aal": "aal1", "state": "choose_method" } which result in your example showing a blank page
@chilly-king-10285 I get the following flow { "id": "735bc986-a823-4391-b658-470cb2d48e33", "organization_id": null, "type": "browser", "expires_at": "2025-12-02T235301.452384886Z", "issued_at": "2025-12-02T225301.452384886Z", "request_url": "http://localhost:4000/self-service/login/browser", "ui": { "action": "http://localhost:4000/self-service/login?flow=735bc986-a823-4391-b658-470cb2d48e33", "method": "POST", "nodes": [ { "type": "input", "group": "identifier_first", "attributes": { "name": "identifier", "type": "text", "value": "", "required": true, "autocomplete": "username webauthn", "disabled": false, "node_type": "input" }, "messages": [], "meta": { "label": { "id": 1070002, "text": "E-Mail", "type": "info", "context": { "name": "traits.email", "title": "E-Mail" } } } }, { "type": "input", "group": "identifier_first", "attributes": { "name": "method", "type": "submit", "value": "identifier_first", "disabled": false, "node_type": "input" }, "messages": [], "meta": { "label": { "id": 1070009, "text": "Continue", "type": "info" } } }, { "type": "input", "group": "default", "attributes": { "name": "csrf_token", "type": "hidden", "value": "wJaBRIKUKjjgT8fceB94LhqI4oRmclRoorY9hV9zOsdEfP3XzBl387aP70fpnNUlDl0b2yxhYAyw2MT3WrJ3nw==", "required": true, "disabled": false, "node_type": "input" }, "messages": [], "meta": {} } ] }, "created_at": "2025-12-02T225301Z", "updated_at": "2025-12-02T225301Z", "refresh": false, "requested_aal": "aal1", "state": "choose_method" } which result in your example showing a blank page
c
A message from kapa.ai
Your flow is using identifier‑first login, so you only get
identifier_first
+
default
nodes in the first step – there is no
password
group yet, which is why the example that filters
["default", "password"]
renders nothing. From your JSON:
Copy code
json {   "group": "identifier_first",   "attributes": { "name": "identifier", ... } }, {   "group": "identifier_first",   "attributes": { "name": "method", "value": "identifier_first", ... } }, {   "group": "default",   "attributes": { "name": "csrf_token", ... } }
To render this first step you need to map the
identifier_first
group instead of
password
. For example, adapt the
filterNodesByGroups
call like this:
Copy code
tsx <form action={flow.ui.action} method={flow.ui.method}>   {filterNodesByGroups({     nodes: flow.ui.nodes,     // first step of identifier-first login     groups: ["default", "identifier_first"],   }).map((node, idx) => mapUINode(node, idx))} </form>
This is the same pattern used in the docs examples (they just show
["default", "password"]
for a unified/password flow).[[Get login flow](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#get-login-flow); [UI node groups](https://www.ory.com/docs/kratos/concepts/ui-user-interface#ui-node-groups)] After the user submits the identifier‑first step, Ory will return a new login flow whose
ui.nodes
then include the
password
(and/or other) groups; on that step you can filter
["default", "password"]
again as in the original example.
q
@chilly-king-10285 how can I configure it to have email and password infirst screen?
c
A message from kapa.ai
Your project is currently using identifier‑first authentication, which is why the first screen only shows the email field. To have email + password on the first screen, you need to use the unified/password style instead. There are two important constraints from the docs and issues: 1. Valid values for
selfservice.flows.login.style
are only: -
unified
-
identifier_first
[[Login style issue](https://github.com/ory/kratos/issues/4504)] 2. On Ory Network with the built‑in Account Experience,
identifier_first
is the only supported style right now –
unified
is not supported there yet. [[Network issue comment](https://github.com/ory/network/issues/441); [Identifier first doc](https://www.ory.com/docs/identities/sign-in/identifier-first-authentication)] So: - Self‑hosted Kratos (your own UI) Set in your `kratos.yaml`:
Copy code
yaml   selfservice:     flows:       login:         style: unified
This will make the login flow return
password
group nodes with
identifier
+
password
in one step, as shown in the password login payload docs. [[Login payloads](https://www.ory.com/docs/kratos/self-service/flows/user-login#login-flow-payloads); [Username/email+password config](https://www.ory.com/docs/kratos/concepts/credentials/username-email-password)] - Ory Network using Account Experience UI You cannot currently switch that UI to “email + password in first screen”; identifier‑first is enforced there. The Ory team explicitly confirmed that
unified
style is not supported with Account Experience yet. [[Network issue comment](https://github.com/ory/network/issues/441)] If you are on Ory Network and want email + password on the first screen, the only documented way is to: 1. Turn off the legacy / Account Experience login screen (Branding → Theming), and 2. Implement a custom login UI that: - creates a browser login flow, and - renders the
password
group nodes (
identifier
+
password
) from the flow. [[Create login flow React example](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#create-login-flow)]
q
@chilly-king-10285 I'm implementing a custom UI, how do I create - creates a browser login flow with
password
group nodes (
identifier
+
password
) from the flow.?
c
A message from kapa.ai
To get email + password fields in your custom UI, you don’t need a special config flag in the UI itself. You simply: 1. Create a browser login flow. 2. Render the
default
and
password
groups from
flow.ui.nodes
. When the login style is unified/password, the flow already contains: -
identifier
(email/username) in group
password
-
password
in group
password
-
csrf_token
in group
default
as shown in the docs examples.[[Login payloads](https://www.ory.com/docs/kratos/self-service/flows/user-login#login-flow-payloads); [Create login flow](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#create-login-flow)] Below is a minimal React example that: - creates a browser login flow, - then renders only
default
+
password
nodes (identifier + password + submit):
Copy code
tsx import {   Configuration,   FrontendApi,   LoginFlow,   UiNode,   UiNodeInputAttributes, } from "@ory/client" import {   filterNodesByGroups,   isUiNodeInputAttributes, } from "@ory/integrations/ui" import { useEffect, useState } from "react"  const frontend = new FrontendApi(   new Configuration({     basePath: "<http://localhost:4000>", // your Ory Tunnel URL     baseOptions: {       withCredentials: true, // include cookies     },   }), )  export const Login = () => {   const [flow, setFlow] = useState<LoginFlow>()    useEffect(() => {     frontend       .createBrowserLoginFlow({         // optional: returnTo, aal, refresh...       })       .then(({ data }) => setFlow(data))       .catch((err) => {         // handle error       })   }, [])    const mapNode = (node: UiNode, key: number) => {     if (!isUiNodeInputAttributes(node.attributes)) return null      const attrs = node.attributes as UiNodeInputAttributes     const type = attrs.type      if (type === "submit" || type === "button") {       return (         <button           key={key}           type={type as "submit" | "button"}           name={attrs.name}           value={attrs.value}           disabled={attrs.disabled}         >           {node.meta?.label?.text ?? attrs.name}         </button>       )     }      return (       <div key={key}>         {node.meta?.label?.text && <label>{node.meta.label.text}</label>}         <input           name={attrs.name}           type={attrs.type}           autoComplete={             attrs.autocomplete || attrs.name === "identifier"               ? "username"               : ""           }           defaultValue={attrs.value}           required={attrs.required}           disabled={attrs.disabled}         />       </div>     )   }    if (!flow) return <div>Loading…</div>    return (     <form action={flow.ui.action} method={flow.ui.method}>       {filterNodesByGroups({         nodes: flow.ui.nodes,         // this gives you CSRF + identifier + password + submit         groups: ["default", "password"],       }).map((node, idx) => mapNode(node, idx))}     </form>   ) }
This is directly adapted from the official “Create login flow” and “Get login flow” React examples, which also use
filterNodesByGroups({ groups: ["default", "password"] })
to render the password method form.[[Create login flow React](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#create-login-flow); [Get login flow React](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#get-login-flow)] Important notes: - If your project is configured with identifier‑first, the first flow you get will have group
identifier_first
instead of
password
, so you’d need to render
["default", "identifier_first"]
for step 1, then
["default", "password"]
after the first submit.[[UI node groups](https://www.ory.com/docs/kratos/concepts/ui-user-interface#ui-node-groups)] - To actually have email + password on the first screen, the backend login style must be unified/password (not identifier‑first); that’s controlled in the identity config, not in the React code.
q
@chilly-king-10285 but the nodes in the flow only have identifier_first
c
A message from kapa.ai
In your current project the login flow is configured as identifier‑first, so the backend only returns
identifier_first
nodes in the first step. That’s why your custom UI only ever sees
identifier_first
and never
password
. To get email + password on the first screen from a login flow, the login style must be
unified
(password style), not
identifier_first
. This is controlled in the identity configuration, not in your React code:
Copy code
yaml selfservice:   flows:     login:       style: unified    # instead of identifier_first
With
style: unified
, a browser login flow will return
password
group nodes that include both: -
identifier
(email/username) -
password
on the first step, as shown in the login payload examples where
group: "password"
has
identifier
,
password
, and a submit node.[[Login payloads](https://www.ory.com/docs/kratos/self-service/flows/user-login#login-flow-payloads); [Create login flow](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#create-login-flow)] However, there is an important limitation: - On Ory Network with the built‑in Account Experience UI, only
identifier_first
is currently supported;
unified
is not supported there yet.[[Network issue 441](https://github.com/ory/network/issues/441); [Kratos issue 4504](https://github.com/ory/kratos/issues/4504)] So: - If you’re self‑hosting Kratos: set
selfservice.flows.login.style: unified
in your config, then your custom UI will see
password
nodes with
identifier
+
password
on the first flow. - If you’re on Ory Network using Account Experience: you cannot switch the backend login style away from
identifier_first
right now. In that case, even with a custom UI, the first login step will only have
identifier_first
nodes, and you’d need to: - render
["default", "identifier_first"]
for step 1, then - after submit, render
["default", "password"]
when the second‑step flow returns password nodes. There’s no documented way in the provided sources to have the Network Account Experience backend emit unified/password nodes on the first step today.
q
@chilly-king-10285 how. do I change config:
Copy code
selfservice:
  flows:
    login:
      style: unified    # instead of identifier_first
c
A message from kapa.ai
You can’t change that setting to
unified
on Ory Network with the built‑in Account Experience right now. From the linked issue, the Ory team clarified: - Valid values for
selfservice.flows.login.style
are
unified
and
identifier_first
. - But on Ory Network’s Account Experience,
unified
is not supported yet, so attempts to set it are rejected / ignored and the style stays
identifier_first
.[\[Network issue 441\]](https://github.com/ory/network/issues/441)[\[Identifier first doc\]](https://www.ory.com/docs/identities/sign-in/identifier-first-authentication) So: - Self‑hosted Kratos: you can set
Copy code
yaml   selfservice:     flows:       login:         style: unified
in your
kratos.yaml
and restart Kratos. This is supported for self‑hosted. [[Identifier first doc](https://www.ory.com/docs/identities/sign-in/identifier-first-authentication)] - Ory Network (your case): the same config key exists in the schema, but
unified
is not honored for the Account Experience. There is no supported way in the provided docs to switch the Network login flow style away from
identifier_first
today. Any attempt to change it via
ory update identity-config
/
ory patch identity-config
will either fail validation or effectively be ignored, as described in the GitHub issue you saw.[\[Kratos issue 4504\]](https://github.com/ory/kratos/issues/4504) If you need email + password on the first screen on Ory Network today, the documented path is to: 1. Keep the backend in identifier‑first. 2. Implement a custom UI against the login flow and handle the two‑step flow (
identifier_first
then
password
) yourself. [[Custom UI basic integration](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration)]
q
@chilly-king-10285 the custom login screen doesn't work. When I try to login I end up not authenticated and for some reason the flow response multiplies the buttons. Here's my implementation and the last response I got from the flow
Copy code
import React, { useEffect, useState } from 'react';
import { Configuration, FrontendApi, LoginFlow, UiNode, UiNodeInputAttributes } from '@ory/client';
import { useSearchParams } from 'react-router-dom';
import { filterNodesByGroups, isUiNodeInputAttributes } from '@ory/integrations/ui';
import { Box, Center, Paper } from '@mantine/core';
import { LogoWithIcon } from 'src/UI/app-container/header/Header';
import Button from 'src/components/design-system/buttons/Button';

const ory = new FrontendApi(
  new Configuration({
    // In dev, use your Ory Tunnel URL, e.g. <http://localhost:4000>
    basePath: '<http://localhost:4000>',
    baseOptions: {
      withCredentials: true, // important for cookies
    },
  }),
);
export const LoginPage = () => {
  const [flow, setFlow] = useState<LoginFlow>();
  const [searchParams] = useSearchParams();

  // Either get existing flow (?flow=) or create a new browser flow
  useEffect(() => {
    const flowId = searchParams.get('flow');
    if (flowId) {
      ory
        .getLoginFlow({ id: flowId })
        .then(({ data }) => setFlow(data))
        .catch(() => {
          // TODO
        });
      return;
    }

    ory
      .createBrowserLoginFlow()
      .then(({ data }) => setFlow(data))
      .catch(() => {
        // TODO
      });
  }, []);

  // Map a single UI node to an input or button
  const mapNode = (node: UiNode, idx: number) => {
    if (!isUiNodeInputAttributes(node.attributes)) return null;

    const attrs = node.attributes as UiNodeInputAttributes;
    if (attrs.type === 'submit' || attrs.type === 'button') {
      return (
        <Button
          key={idx}
          type={attrs.type as 'submit' | 'button'}
          name={attrs.name}
          disabled={attrs.disabled}
        >
          {node.meta?.label?.text ?? attrs.name}
        </Button>
      );
    }

    return (
      <div key={idx}>
        {node.meta?.label?.text && (
          <label>
            {node.meta.label.text}
            <input
              name={attrs.name}
              type={attrs.type}
              autoComplete={attrs.autocomplete || attrs.name === 'identifier' ? 'username' : ''}
              defaultValue={attrs.value}
              required={attrs.required}
              disabled={attrs.disabled}
            />
          </label>
        )}
        {!node.meta?.label?.text && (
          <input
            name={attrs.name}
            type={attrs.type}
            defaultValue={attrs.value}
            required={attrs.required}
            disabled={attrs.disabled}
          />
        )}
      </div>
    );
  };
  if (!flow) return <div>Loading login…</div>;

  return (
    <Box bg="gray.10" h="100vh">
      <Center h="100%">
        {!flow && <div>Loading login…</div>}
        {flow && (
          <Paper bg={'gray.0'} radius={'xl'} py={'48px'} px={'84px'}>
            <LogoWithIcon textColour={'gray.10'} />
            <form action={flow.ui.action} method={flow.ui.method}>
              {filterNodesByGroups({
                nodes: flow.ui.nodes,
                groups: ['default', 'password', 'identifier_first'],
              }).map((node, idx) => mapNode(node, idx))}
            </form>
          </Paper>
        )}
      </Center>
    </Box>
  );
};
Copy code
{id: "3299ceb5-a75d-49eb-96f5-17d718dff955", organization_id: null, type: "browser",…}
active
: 
"identifier_first"
created_at
: 
"2025-12-08T12:21:54Z"
expires_at
: 
"2025-12-08T13:21:54.39348Z"
id
: 
"3299ceb5-a75d-49eb-96f5-17d718dff955"
issued_at
: 
"2025-12-08T12:21:54.39348Z"
organization_id
: 
null
refresh
: 
false
request_url
: 
"<http://localhost:4000/self-service/login/browser?refresh=true>"
requested_aal
: 
"aal1"
state
: 
"choose_method"
type
: 
"browser"
ui
: 
{action: "<http://localhost:4000/self-service/login?flow=3299ceb5-a75d-49eb-96f5-17d718dff955>",…}
action
: 
"<http://localhost:4000/self-service/login?flow=3299ceb5-a75d-49eb-96f5-17d718dff955>"
method
: 
"POST"
nodes
: 
[{type: "input", group: "identifier_first",…},…]
0
: 
{type: "input", group: "identifier_first",…}
attributes
: 
{name: "identifier", type: "hidden", value: "<mailto:[email protected]|[email protected]>", required: true,…}
group
: 
"identifier_first"
messages
: 
[]
meta
: 
{,…}
type
: 
"input"
1
: 
{type: "input", group: "default", attributes: {name: "csrf_token", type: "hidden",…}, messages: [],…}
attributes
: 
{name: "csrf_token", type: "hidden",…}
group
: 
"default"
messages
: 
[]
meta
: 
{}
type
: 
"input"
2
: 
{type: "input", group: "password",…}
attributes
: 
{name: "password", type: "password", required: true, autocomplete: "current-password", disabled: false,…}
group
: 
"password"
messages
: 
[]
meta
: 
{label: {id: 1070001, text: "Password", type: "info"}}
type
: 
"input"
3
: 
{type: "input", group: "password",…}
attributes
: 
{name: "method", type: "submit", value: "password", disabled: false, node_type: "input"}
group
: 
"password"
messages
: 
[]
meta
: 
{label: {id: 1010022, text: "Sign in with password", type: "info"}}
type
: 
"input"
4
: 
{type: "input", group: "password",…}
5
: 
{type: "input", group: "password",…}
attributes
: 
{name: "method", type: "submit", value: "password", disabled: false, node_type: "input"}
group
: 
"password"
messages
: 
[]
meta
: 
{label: {id: 1010022, text: "Sign in with password", type: "info"}}
type
: 
"input"
updated_at
: 
"2025-12-08T12:21:54Z"
c
A message from kapa.ai
Two separate things are going on here: 1. You’re not actually handling the login submit in React, so you don’t end up authenticated. 2. The extra submit buttons are expected with multiple password‐method nodes. --- ## 1. Why you’re not logged in Your form:
Copy code
tsx <form action={flow.ui.action} method={flow.ui.method}>   {filterNodesByGroups({ … }).map(mapNode)} </form>
This submits like a normal HTML form to Ory. That does complete the login flow server‑side, but your React app never: - inspects the response, - refreshes the flow, - or redirects after success. The docs’ “submit login flow” example uses
updateLoginFlow
and
onSubmit
so you can react to success / validation errors.[^login-submit] Minimal pattern:
Copy code
tsx const submit = (event: React.FormEvent<HTMLFormElement>) => {   event.preventDefault()    const form = event.currentTarget   const formData = new FormData(form)    let body = Object.fromEntries(formData) as unknown as UpdateLoginFlowBody    // Make sure the correct method is set when multiple submit buttons exist   if ("submitter" in event.nativeEvent) {     const method = (event.nativeEvent as unknown as { submitter: HTMLInputElement }).submitter     body = { ...body, [method.name]: method.value }   }    ory     .updateLoginFlow({ flow: flow!.id, updateLoginFlowBody: body })     .then(() => {       // e.g. redirect to your app’s home       window.location.href = "/"     })     .catch((err: AxiosError) => {       if (err.response?.status === 400) {         // validation error: server returns an updated flow         setFlow(err.response.data as LoginFlow)       }     }) }
And wire it:
Copy code
tsx <form action={flow.ui.action} method={flow.ui.method} onSubmit={submit}>   … </form>
This is exactly how the docs’ React example does it.[^login-submit] As long as: - you use
withCredentials: true
(you do), and - your app and Ory (via the tunnel) are on the same origin for cookies, the session cookie will be set and
/sessions/whoami
will show you as logged in.[^debug-csrf] --- ## 2. “Duplicated” password buttons Your flow shows:
Copy code
json 3: {   "group": "password",   "attributes": { "name": "method", "type": "submit", "value": "password" },   "meta": { "label": { "text": "Sign in with password" } } }, 4: { "type": "input", "group": "password", ... }, 5: {   "group": "password",   "attributes": { "name": "method", "type": "submit", "value": "password" },   "meta": { "label": { "text": "Sign in with password" } } }
Ory can legitimately return multiple submit nodes with the same
name
(e.g. coming from different groups or layout). Docs explicitly call this out and say you must make sure only the clicked button’s value is submitted as the
method
, which is exactly what the
submitter
logic above does.[^submit-node] So: - Seeing more than one
"name": "method", "value": "password"
node is not a bug in your UI. - Your
mapNode
correctly renders a
<Button>
for every submit node – that’s why you see “duplicates”. - Functionally it’s fine as long as your submit code uses the clicked button’s `name`/`value`. If you only want one visible button, you can add your own filtering, e.g. take only the first submit node of each group before mapping, but that’s cosmetic. --- ## 3. One more small fix in your code Your custom
<Button>
doesn’t pass the
value
attribute:
Copy code
tsx <Button   key={idx}   type={attrs.type as 'submit' | 'button'}   name={attrs.name}   disabled={attrs.disabled} >   {node.meta?.label?.text ?? attrs.name} </Button>
For Ory, the important part is: -
name="method"
-
value="password"
Without
value
, the browser won’t send
method=password
from that button. Either: - pass it through:
Copy code
tsx   <Button     key={idx}     type={attrs.type as "submit" | "button"}     name={attrs.name}     value={attrs.value}     disabled={attrs.disabled}   >     {node.meta?.label?.text ?? attrs.name}   </Button>
- or rely on the
submitter
logic in the
submit
handler to inject
method: "password"
into the body, as shown above.[^login-submit][^submit-node] --- ### TL;DR - Use
updateLoginFlow
in an
onSubmit
handler and redirect on success (don’t rely on plain HTML POST only). - Include the clicked submit button’s
name
and
value
in the payload (
method=password
). - Multiple
method=password
submit nodes in the flow are normal; your code is just rendering all of them. --- [^login-submit]: See the “Submit login flow” React example, which uses
updateLoginFlow
and
submitter
to build the body.[Submit login flow](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#submit-login-flow) [^debug-csrf]: For cookies/CSRF and
withCredentials
, see the debug section in the custom UI basic integration docs.[Debug](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#debug) [^submit-node]: The UI node concepts explain that multiple submit nodes with the same
name
exist and you must ensure the correct one’s value is sent.[Submit input node](https://www.ory.com/docs/kratos/concepts/ui-user-interface#submit-input-node)
q
@chilly-king-10285 can you give me the full file fixed version please?
c
A message from kapa.ai
Here’s your component updated to: - include the submit handler using
updateLoginFlow
, - pass the submit button’s
value
, - and handle multiple
method
buttons correctly, as in the docs’ submit example.[\[submit login flow\]](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#submit-login-flow)[\[submit input node\]](https://www.ory.com/docs/kratos/concepts/ui-user-interface#submit-input-node)
Copy code
tsx import React, { useEffect, useState } from "react" import {   Configuration,   FrontendApi,   LoginFlow,   UiNode,   UiNodeInputAttributes,   UpdateLoginFlowBody, } from "@ory/client" import { useSearchParams } from "react-router-dom" import {   filterNodesByGroups,   isUiNodeInputAttributes, } from "@ory/integrations/ui" import { Box, Center, Paper } from "@mantine/core" import { LogoWithIcon } from "src/UI/app-container/header/Header" import Button from "src/components/design-system/buttons/Button" import { AxiosError } from "axios"  const ory = new FrontendApi(   new Configuration({     basePath: "<http://localhost:4000>", // your Ory Tunnel URL     baseOptions: {       withCredentials: true, // include cookies     },   }), )  export const LoginPage = () => {   const [flow, setFlow] = useState<LoginFlow>()   const [searchParams] = useSearchParams()    useEffect(() => {     const flowId = searchParams.get("flow")     if (flowId) {       ory         .getLoginFlow({ id: flowId })         .then(({ data }) => setFlow(data))         .catch(() => {           // handle error / optionally create a new flow         })       return     }      ory       .createBrowserLoginFlow()       .then(({ data }) => setFlow(data))       .catch(() => {         // handle error       })   }, [searchParams])    const submit = (event: React.FormEvent<HTMLFormElement>) => {     event.preventDefault()     if (!flow) return      const form = event.currentTarget     const formData = new FormData(form)      // Map the form data to JSON for the request body     let body = Object.fromEntries(formData) as unknown as UpdateLoginFlowBody      // Ensure we send the clicked submit button's name/value (method)     if ("submitter" in event.nativeEvent) {       const submitter = (event.nativeEvent as unknown as {         submitter: HTMLInputElement       }).submitter        if (submitter.name && submitter.value) {         body = {           ...body,           [submitter.name]: submitter.value,         }       }     }      ory       .updateLoginFlow({         flow: flow.id,         updateLoginFlowBody: body,       })       .then(() => {         // login successful – redirect into your app         window.location.href = "/"       })       .catch((err: AxiosError) => {         if (err.response?.status === 400) {           // validation/user error: server returns updated flow with messages           setFlow(err.response.data as LoginFlow)           return         }         // handle other errors       })   }    const mapNode = (node: UiNode, idx: number) => {     if (!isUiNodeInputAttributes(node.attributes)) return null      const attrs = node.attributes as UiNodeInputAttributes      if (attrs.type === "submit" || attrs.type === "button") {       return (         <Button           key={idx}           type={attrs.type as "submit" | "button"}           name={attrs.name}           value={attrs.value}           disabled={attrs.disabled}         >           {node.meta?.label?.text ?? attrs.name}         </Button>       )     }      return (       <div key={idx}>         {node.meta?.label?.text && (           <label>             {node.meta.label.text}             <input               name={attrs.name}               type={attrs.type}               autoComplete={                 attrs.autocomplete || attrs.name === "identifier"                   ? "username"                   : ""               }               defaultValue={attrs.value}               required={attrs.required}               disabled={attrs.disabled}             />           </label>         )}         {!node.meta?.label?.text && (           <input             name={attrs.name}             type={attrs.type}             defaultValue={attrs.value}             required={attrs.required}             disabled={attrs.disabled}           />         )}       </div>     )   }    if (!flow) {     return <div>Loading login…</div>   }    return (     <Box bg="gray.10" h="100vh">       <Center h="100%">         <Paper bg="gray.0" radius="xl" py="48px" px="84px">           <LogoWithIcon textColour="gray.10" />           <form             action={flow.ui.action}             method={flow.ui.method}             onSubmit={submit}           >             {filterNodesByGroups({               nodes: flow.ui.nodes,               // identifier-first step + password step + default (csrf)               groups: ["default", "password", "identifier_first"],             }).map((node, idx) => mapNode(node, idx))}           </form>         </Paper>       </Center>     </Box>   ) }
q
@chilly-king-10285 when I try to login I get a 303 See Other response from the POST self-service/login endpoint
c
A message from kapa.ai
A
303 See Other
from
POST /self-service/login
is the expected behavior for a browser login flow. Per the API docs: - For browser flows (your case, because you’re using the Experience UI / browser and not
Accept: application/json
): - On success, Ory responds with HTTP 303 and a
Location
header pointing to the post/after login URL or
return_to
URL. - On validation error, Ory also responds with HTTP 303 back to the login UI with the (updated) flow containing error messages.[
updateLoginFlow
browser behavior](https://www.ory.sh/docs/reference/api#operation/updateLoginFlow) So if you see a 303 when logging in: - The browser should follow the redirect automatically and land on either: - your configured post‑login redirect URL; or - the login UI again with errors attached to the flow. If you’re handling the request via `fetch`/XHR: - The 303 is just an HTTP redirect; you need to inspect the
Location
header
and follow it (or use the SDK as in
updateLoginFlow
, which already implements the expected behavior).[
updateLoginFlow
browser behavior](https://www.ory.sh/docs/kratos/reference/api#updateLoginFlow) If instead you want a JSON response without redirects: - Call
POST /self-service/login
with `Accept: application/json`: - On success: HTTP 200 + JSON body with the session / identity and
Set-Cookie
. - On validation error: HTTP 400 with the flow JSON. - On expired flow: HTTP 303 to a fresh login flow.[
updateLoginFlow
JSON mode](https://www.ory.sh/docs/reference/api#operation/updateLoginFlow) So the 303 isn’t an error by itself; what matters is where it redirects and whether the browser (or your code) follows that redirect.
q
@chilly-king-10285 given my code, how do I make it call with Accept: application/json?
Copy code
import React, { useEffect, useState } from 'react';
import { Configuration, FrontendApi, LoginFlow, UiNode, UiNodeInputAttributes } from '@ory/client';
import { useSearchParams } from 'react-router-dom';
import { filterNodesByGroups, isUiNodeInputAttributes } from '@ory/integrations/ui';
import { Box, Center, Paper } from '@mantine/core';
import { LogoWithIcon } from 'src/UI/app-container/header/Header';
import Button from 'src/components/design-system/buttons/Button';

const ory = new FrontendApi(
  new Configuration({
    // In dev, use your Ory Tunnel URL, e.g. <http://localhost:4000>
    basePath: '<http://localhost:4000>',
    baseOptions: {
      withCredentials: true, // important for cookies
    },
  }),
);
export const LoginPage = () => {
  const [flow, setFlow] = useState<LoginFlow>();
  const [searchParams] = useSearchParams();

  // Either get existing flow (?flow=) or create a new browser flow
  useEffect(() => {
    const flowId = searchParams.get('flow');
    if (flowId) {
      ory
        .getLoginFlow({ id: flowId })
        .then(({ data }) => setFlow(data))
        .catch(() => {
          // TODO
        });
      return;
    }

    ory
      .createBrowserLoginFlow()
      .then(({ data }) => setFlow(data))
      .catch(() => {
        // TODO
      });
  }, []);

  // Map a single UI node to an input or button
  const mapNode = (node: UiNode, idx: number) => {
    if (!isUiNodeInputAttributes(node.attributes)) return null;

    const attrs = node.attributes as UiNodeInputAttributes;
    if (attrs.type === 'submit' || attrs.type === 'button') {
      return (
        <Button
          key={idx}
          type={attrs.type as 'submit' | 'button'}
          name={attrs.name}
          disabled={attrs.disabled}
        >
          {node.meta?.label?.text ?? attrs.name}
        </Button>
      );
    }

    return (
      <div key={idx}>
        {node.meta?.label?.text && (
          <label>
            {node.meta.label.text}
            <input
              name={attrs.name}
              type={attrs.type}
              autoComplete={attrs.autocomplete || attrs.name === 'identifier' ? 'username' : ''}
              defaultValue={attrs.value}
              required={attrs.required}
              disabled={attrs.disabled}
            />
          </label>
        )}
        {!node.meta?.label?.text && (
          <input
            name={attrs.name}
            type={attrs.type}
            defaultValue={attrs.value}
            required={attrs.required}
            disabled={attrs.disabled}
          />
        )}
      </div>
    );
  };
  if (!flow) return <div>Loading login…</div>;

  return (
    <Box bg="gray.10" h="100vh">
      <Center h="100%">
        {!flow && <div>Loading login…</div>}
        {flow && (
          <Paper bg={'gray.0'} radius={'xl'} py={'48px'} px={'84px'}>
            <LogoWithIcon textColour={'gray.10'} />
            <form action={flow.ui.action} method={flow.ui.method}>
              {filterNodesByGroups({
                nodes: flow.ui.nodes,
                groups: ['default', 'password', 'identifier_first'],
              }).map((node, idx) => mapNode(node, idx))}
            </form>
          </Paper>
        )}
      </Center>
    </Box>
  );
};
c
A message from kapa.ai
Set the
Accept
header to
application/json
in the SDK configuration so all SDK calls (including
updateLoginFlow
) use it. You can do that by adding
headers
inside `baseOptions`:
Copy code
ts const ory = new FrontendApi(   new Configuration({     basePath: "<http://localhost:4000>",     baseOptions: {       withCredentials: true,       headers: {         Accept: "application/json",       },     },   }), )
The login
updateLoginFlow
endpoint switches to JSON (no redirects, 200/400 responses) when called with
Accept: application/json
.[
updateLoginFlow
JSON mode](https://www.ory.sh/docs/reference/api#operation/updateLoginFlow)
q
@chilly-king-10285 when I define a custom login flow, my host will be different from the usualy ory tunnel. Do I need to configure this accepted new URL (currently my local app is on localhost:5173 and ory tunnel on localhost:4000)? If so, where do I configure?
c
A message from kapa.ai
Yes, you need to be careful with which host talks to Ory, but you do not add the new URL to Ory as an “allowed origin” for login flows. For local development with a custom login UI: - Your React app runs on
<http://localhost:5173>
. - Ory Tunnel runs on
<http://localhost:4000>
. - All calls from your custom UI to Ory (creating/fetching/submitting flows) must go to
<http://localhost:4000>
(the tunnel), not directly to the Ory Network URL.[\[local dev\]](https://www.ory.com/docs/getting-started/local-development#local-development) This is exactly what you already do in your
FrontendApi
config:
Copy code
ts const ory = new FrontendApi(   new Configuration({     basePath: "<http://localhost:4000>",     baseOptions: {       withCredentials: true,     },   }), )
That’s all that’s needed for the login flow itself: - The browser loads your UI from
<http://localhost:5173>
. - Your UI calls the tunnel at
<http://localhost:4000>
for
/self-service/login/*
. - Tunnel makes sure cookies and CORS work by putting Ory on the same top‑level domain as your app.[\[tunnel\]](https://www.ory.com/docs/guides/cli/proxy-and-tunnel#ory-tunnel) You only need to configure URLs in Ory when you change: 1. UI URLs for flows (login/registration/recovery/settings): Set your custom login page under Branding → UI URLs → Login UI URL in the Console so Ory redirects the browser to your React login page for browser flows.[\[custom UI URLs\]](https://www.ory.com/docs/kratos/bring-your-own-ui/configure-ory-to-use-your-ui) 2. Allowed return_to URLs: If you use
?return_to=
or set default redirects to
<http://localhost:5173/>...
, add those to Allowed return URLs (Browser Redirects) in the Console so Ory doesn’t reject them.[\[allowed return_to\]](https://www.ory.com/docs/kratos/concepts/browser-redirect-flow-completion#redirection) You do not need to separately “register”
<http://localhost:5173>
just because your custom UI lives there; it’s enough that: - SDK basePath points to the tunnel (`http://localhost:4000`), and - any
return_to
targets (like `http://localhost:5173/...`) are in the allowed return URLs list.
q
@chilly-king-10285 my login flow completes, then I call teh whoamI endpoint which has a cookie with it but I still get 401, why is that?
c
A message from kapa.ai
The most common reasons for getting
401
from
GET /sessions/whoami
even though you think you’re sending a cookie are: 1. The cookie isn’t actually being sent (CORS / credentials issue) For browser / SPA calls, you must: - Have Ory and your app on the same top‑level domain (use Ory Tunnel in dev).[[Local dev](https://www.ory.com/docs/getting-started/local-development#local-development); [Cookie model](https://www.ory.com/docs/security-model#http-cookies)] - Call
whoami
with credentials enabled, e.g. in the SDK: ```ts const ory = new FrontendApi( new Configuration({ basePath: "http://localhost:4000", // tunnel baseOptions: { withCredentials: true }, }), ) await ory.toSession() // sends cookie automatically ```[[Session cookie React](https://www.ory.com/docs/kratos/session-management/overview#using-ory-session-cookie)] or with `fetch`: ```ts fetch("http://localhost:4000/sessions/whoami", { credentials: "include", }) ```[[Cookie-based model](https://www.ory.com/docs/security-model#cross-origin-http-cookies)] If
credentials/include
is missing or CORS not set up, the browser won’t send the cookie and
whoami
returns
401
.[[CSRF troubleshooting](https://www.ory.com/docs/troubleshooting/csrf#accessing-apis-from-client-side-javascript--ajax)] 2. Domain mismatch (localhost vs 127.0.0.1 or different subdomains) Cookies are scoped by domain. If the session cookie is set for
localhost
but you call
whoami
on
127.0.0.1
(or a different subdomain), the browser won’t send it.[[Cookie model](https://www.ory.com/docs/security-model#http-cookie-domains); [Local dev limitations](https://www.ory.com/docs/getting-started/local-development#limitations)] 3. You’re not forwarding the cookie in a server‑side call From a backend / API server, you must forward the
Cookie
header you got from the browser: ```ts // Express middleware example frontend.toSession({ cookie: req.header("cookie") }) ```[[Session checks Express](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#session-checks); [toSession ref](https://www.ory.sh/docs/reference/api#tag/frontend/operation/toSession)] If you call
toSession()
without passing the cookie in this server‑side scenario, you get
401
. 4. Session is not active / expired / wrong AAL - If there is no active session for that cookie,
whoami
returns
401
with error id
session_inactive
.[[toSession ref](https://www.ory.com/docs/kratos/reference/api#tag/frontend/operation/toSession)] - If your config requires a higher AAL than the session has, you can get
403
, but a completely missing/invalid session is
401
.[[AAL / whoami](https://www.ory.com/docs/identities/sign-in/check-session-token-cookie-api)] 5. Calling the wrong base URL (project slug vs tunnel) In local dev, you must call
<http://localhost:4000/sessions/whoami>
(the tunnel), not
https://$<http://PROJECT_SLUG.projects.oryapis.com|PROJECT_SLUG.projects.oryapis.com>
, otherwise the cookie set on
localhost
is not sent.[[Local development](https://www.ory.com/docs/getting-started/local-development#local-development)] --- Concrete things to check: - In browser DevTools → Network: - On the
whoami
request, is the
Cookie
header present and does it contain
ory_kratos_session
(or your custom name)?[[CSRF troubleshooting](https://www.ory.com/docs/troubleshooting/csrf)] - Is the request URL exactly the same origin the cookie was set on (scheme + host, ignoring port)?[[Cookie model](https://www.ory.com/docs/security-model#http-cookie-domains)] - If calling from your backend, are you passing
cookie: req.header("cookie")
to
toSession()
?[[Session checks](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#session-checks)] If all of the above are correct and you still get
401
, it usually means the session has expired or was never issued (e.g. login did not complete successfully).
q
@chilly-king-10285 does it have to be the same ory object used for login and calling whoamI
Copy code
const ory = new FrontendApi(
  new Configuration({
    // In dev, use your Ory Tunnel URL, e.g. <http://localhost:4000>
    basePath: '<http://localhost:4000>',
    baseOptions: {
      headers: {
        Accept: 'application/json',
      },
      withCredentials: true, // important for cookies
    },
  }),
);
c
A message from kapa.ai
No, it does not have to be the same
FrontendApi
instance, but it must be configured to talk to the same Ory project / base URL and use cookies in the same browser context. What matters for `toSession`/`whoami` is: - The request goes to the same origin that issued the session cookie (in dev: your tunnel, e.g. `http://localhost:4000`). [[Local development](https://www.ory.com/docs/getting-started/local-development#local-development)] - The request includes cookies, i.e.
withCredentials: true
/
credentials: "include"
. [[Cookie-based model](https://www.ory.com/docs/security-model#cross-origin-http-cookies)] - From server-side code, you forward the
Cookie
header into
toSession
. [[Session checks](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#session-checks)] So you can create a second
FrontendApi
instance for
toSession
, as long as it’s configured like your snippet (same
basePath
,
withCredentials: true
). The session is browser‑cookie based; it’s not bound to a particular SDK object.
q
@chilly-king-10285 I get 303 also when I put the wrong password.
c
A message from kapa.ai
For browser login flows, getting a
303 See Other
is expected both on success and on validation errors, including wrong passwords. Per the
updateLoginFlow
API docs for browser flows: - With normal browser accept headers (
text/html
): - On success: →
303
redirect to the post/after-login URL or
return_to
. - On validation error (e.g. wrong password): →
303
redirect back to the login UI URL with the same
flow
ID, which now contains error messages in
flow.ui.messages
/
flow.ui.nodes[*].messages
.[
updateLoginFlow
browser behavior](https://www.ory.sh/docs/reference/api#operation/updateLoginFlow)[Login form validation](https://www.ory.com/docs/kratos/self-service/flows/user-login#login-form-validation) If you instead call the endpoint with
Accept: application/json
, the behavior changes: - API / AJAX mode (
Accept: application/json
):
- On success:
200
+ JSON (identity +
Set-Cookie
). - On validation error (wrong password):
400
+ JSON body containing the flow with error messages.[
updateLoginFlow
JSON mode](https://www.ory.sh/docs/reference/api#operation/updateLoginFlow) So: - Seeing
303
even for wrong credentials is normal if you’re in browser mode. - To handle errors in your React app without following redirects, keep using
Accept: application/json
and inspect the
400
response body (updated flow) to show error messages in your UI.
q
@chilly-king-10285 my authentication works with cookies. I'm soon going to build a mobile app for my users. Can I still use the cookie mechanism or will I need to recofigure my ory instance
c
A message from kapa.ai
You can keep your current cookie-based setup for the web app and add a token-based setup for the mobile app without reconfiguring everything from scratch. Ory already supports both: - Browser apps: use Ory Session Cookies, managed by Ory’s browser self‑service endpoints (
/self-service/{flow}/browser
).[[Browser vs native](https://www.ory.com/docs/identities/native-browser); [Cookie model](https://www.ory.com/docs/security-model)] - Native / mobile apps: use Ory Session Tokens instead of cookies, via the API endpoints (
/self-service/{flow}/api
).[[Browser vs native](https://www.ory.com/docs/identities/native-browser); [Session overview](https://www.ory.com/docs/kratos/session-management/overview)] For your mobile app you should: 1. Use API flows (not browser flows): Initialize login/registration with
/self-service/login/api
,
/self-service/registration/api
, etc. These do not issue CSRF cookies and will return a session token on successful login.[[API flows native](https://www.ory.com/docs/kratos/self-service#api-flows-native-mobile--desktop-apps-command-line-interfaces-smart-tvs-and-more); [createNativeLoginFlow](https://www.ory.com/docs/kratos/reference/api#createNativeLoginFlow)] 2. Store the session token in the app (secure storage / keychain). 3. Call
whoami
with the token
to check session status from the app, e.g.:
Copy code
bash    curl -H "Authorization: Bearer {your-session-token}" \      "https://$<http://PROJECT_SLUG.projects.oryapis.com/sessions/whoami|PROJECT_SLUG.projects.oryapis.com/sessions/whoami>"
[[Check session status](https://www.ory.com/docs/identities/sign-in/check-session-token-cookie-api); [Using Ory Session Token](https://www.ory.com/docs/kratos/session-management/overview#using-ory-session-token)] 4. Keep using cookies and the browser endpoints for your existing web app—no need to disable or change that. So you don’t need to abandon cookies for the web; you simply use the native/API flow + session tokens for the mobile app in parallel.
q
@chilly-king-10285 I'm creating a custom UI. when I start a flow I get a response like this one:
Copy code
{
  "id": "e40d625a-2193-483f-bccc-ccb4b0b9f8c1",
  "organization_id": null,
  "type": "browser",
  "expires_at": "2025-12-16T17:26:26.759137Z",
  "issued_at": "2025-12-16T16:26:26.759137Z",
  "request_url": "<http://localhost:4000/self-service/login/browser?refresh=true>",
  "ui": {
    "action": "<http://localhost:4000/self-service/login?flow=e40d625a-2193-483f-bccc-ccb4b0b9f8c1>",
    "method": "POST",
    "nodes": [
      {
        "type": "input",
        "group": "identifier_first",
        "attributes": {
          "name": "identifier",
          "type": "text",
          "value": "",
          "required": true,
          "autocomplete": "username webauthn",
          "disabled": false,
          "node_type": "input"
        },
        "messages": [],
        "meta": {
          "label": {
            "id": 1070002,
            "text": "E-Mail",
            "type": "info",
            "context": {
              "name": "traits.email",
              "title": "E-Mail"
            }
          }
        }
      },
      {
        "type": "input",
        "group": "identifier_first",
        "attributes": {
          "name": "method",
          "type": "submit",
          "value": "identifier_first",
          "disabled": false,
          "node_type": "input"
        },
        "messages": [],
        "meta": {
          "label": {
            "id": 1070009,
            "text": "Continue",
            "type": "info"
          }
        }
      },
      {
        "type": "input",
        "group": "oidc",
        "attributes": {
          "name": "provider",
          "type": "submit",
          "value": "google-Rvv4I_Gn",
          "disabled": false,
          "node_type": "input"
        },
        "messages": [],
        "meta": {
          "label": {
            "id": 1010002,
            "text": "Sign in with Google",
            "type": "info",
            "context": {
              "provider": "Google",
              "provider_id": "google-Rvv4I_Gn"
            }
          }
        }
      },
      {
        "type": "input",
        "group": "oidc",
        "attributes": {
          "name": "provider",
          "type": "submit",
          "value": "microsoft-3csX8ovp",
          "disabled": false,
          "node_type": "input"
        },
        "messages": [],
        "meta": {
          "label": {
            "id": 1010002,
            "text": "Sign in with Microsoft",
            "type": "info",
            "context": {
              "provider": "Microsoft",
              "provider_id": "microsoft-3csX8ovp"
            }
          }
        }
      },
      {
        "type": "input",
        "group": "default",
        "attributes": {
          "name": "csrf_token",
          "type": "hidden",
          "value": "aXwlhqRwvexNwJr06py318UkBFHfkSaerirUvz3yymc8E5RCBeedpr6qLyYE05hdj5+Hjuwe/7oWHzKPvE4C9w==",
          "required": true,
          "disabled": false,
          "node_type": "input"
        },
        "messages": [],
        "meta": {}
      }
    ]
  },
  "created_at": "2025-12-16T16:26:27Z",
  "updated_at": "2025-12-16T16:26:27Z",
  "refresh": false,
  "requested_aal": "aal1",
  "state": "choose_method"
}
Does it have to work with dynamic nodes? All I want to to know if the user has a password and/or social sign in
c
A message from kapa.ai
You can (and should) treat the nodes dynamically, but you can also infer “has password” / “has social sign‑in available” from what you get back in the flow. From the login‑flow docs: - Each login method is exposed as UI nodes grouped by `group`: -
password
→ email/identifier + password + submit. -
oidc
→ one submit node per configured OIDC provider. -
identifier_first
→ the first‑step identifier field when identifier‑first is enabled.[\[Login methods\]](https://www.ory.com/docs/kratos/self-service/flows/user-login#login)[\[UI node groups\]](https://www.ory.com/docs/kratos/concepts/ui-user-interface#ui-node-groups) - In your example, the flow has: - group
identifier_first
(email field + “Continue”), - group
oidc
(Google + Microsoft buttons), - group
default
(CSRF token). There is no
password
group
in this first step, which is expected when using identifier‑first style: password nodes only appear in the second step, after the user has entered an identifier.[\[Login flow payloads – password\]](https://www.ory.com/docs/kratos/self-service/flows/user-login#login-flow-payloads) So: - To know which methods are available in this project/flow, inspect the groups present in `flow.ui.nodes`: - If any node has
group === "oidc"
, social sign‑in is available for this flow. - If any node has
group === "password"
, password login is available on this step. - In identifier‑first, you won’t see
password
until after the identifier‑first submit; that doesn’t mean the user “has no password”, only that the current state is step 1. There is no field in the flow that tells you per‑identity “this particular user has a password credential / OIDC link already”; the flow only tells you which methods are offered at this step. The identity’s actual credentials are not exposed in the login flow payload.[\[Login flow payloads\]](https://www.ory.com/docs/kratos/self-service/flows/user-login#login-flow-payloads) In practice for a custom UI: - Render UI based on groups: - step 1: `["default", "identifier_first", "oidc"]`; - after identifier submit:
["default", "password", "oidc"]
if password is enabled. - If you need to know whether a specific identity already has password vs OIDC credentials, that requires looking at the identity via the Admin API, which the login flow itself does not provide; the attached docs don’t describe a ready‑made flag for that in the flow.
q
@chilly-king-10285 what documentation should I read to understand what to expect from the dynamic nodes response and how to handle them?
c
A message from kapa.ai
To understand what to expect from the dynamic nodes and how to handle them in a custom UI, these are the key docs to read: 1. UI concepts (nodes, groups, messages) Explains what
ui.nodes
are, how
group
works (
password
,
oidc
,
identifier_first
,
default
, etc.), and how JSON maps 1:1 to HTML inputs, labels, and scripts.[\[UI nodes concept\]](https://www.ory.com/docs/kratos/concepts/ui-user-interface) 2. Self-service login flow (payload examples) Shows full example login flow payloads and how the nodes look for password login (identifier + password + submit) and error handling.[\[Login flow payloads\]](https://www.ory.com/docs/kratos/self-service/flows/user-login#login-flow-payloads) 3. Bring-your-own UI – basic integration (Login flow) Has concrete React examples for: - creating a browser login flow, - getting an existing flow, - submitting the flow, - mapping nodes dynamically with
filterNodesByGroups
and
isUiNodeInputAttributes
.[\[Create login flow\]](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#create-login-flow)[\[Get/submit login flow\]](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#get-login-flow) 4. Self-service flows overview (form rendering) Shows a generic flow JSON and the corresponding HTML form, reinforcing how to render nodes directly.[\[Form rendering\]](https://www.ory.com/docs/kratos/self-service#form-rendering) If you’re doing more advanced things (like passwordless/WebAuthn), the advanced integration section for login shows how to filter specific groups and attributes (e.g.
webauthn
) and handle script nodes.[\[Passwordless authentication (advanced)\]](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-advanced-integration#passwordless-authentication)
q
@chilly-king-10285 let's use the good social sign in as an example:
Copy code
{
  "type": "input",
  "group": "oidc",
  "attributes": {
    "name": "provider",
    "type": "submit",
    "value": "google-Rvv4I_Gn",
    "disabled": false,
    "node_type": "input"
  },
  "messages": [],
  "meta": {
    "label": {
      "id": 1010002,
      "text": "Sign in with Google",
      "type": "info",
      "context": {
        "provider": "Google",
        "provider_id": "google-Rvv4I_Gn"
      }
    }
  }
}
c
A message from kapa.ai
For that social sign-in node, you should treat it as: - Group:
"oidc"
→ this is an OIDC/social sign‑in button. - What to render: a submit button with the label from
meta.label.text
. - What gets sent on click: -
name = "provider"
-
value = "google-Rvv4I_Gn"
→ tells Ory which configured OIDC provider to start. [[Login with OIDC](https://www.ory.com/docs/kratos/self-service/flows/user-login#login-with-google-facebook-github--openid-connect--oauth-20)] A minimal React render for that node (integrated into your existing
mapNode
) is exactly the same as for other submit nodes:
Copy code
tsx if (isUiNodeInputAttributes(node.attributes)) {   const attrs = node.attributes as UiNodeInputAttributes    if (attrs.type === "submit") {     return (       <button         key={idx}         type="submit"         name={attrs.name}          // "provider"         value={attrs.value}        // "google-Rvv4I_Gn"         disabled={attrs.disabled}       >         {node.meta?.label?.text}   // "Sign in with Google"       </button>     )   } }
Behind the scenes, when that button is clicked, the browser submits the login form to
flow.ui.action
and Ory sees
provider=google-Rvv4I_Gn
and starts the Google OIDC flow for that provider. [[Advanced social sign‑in form example](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-advanced-integration#social-sign-in)]
q
@chilly-king-10285 let's use the good social sign in as an example:
Copy code
{
  "type": "input",
  "group": "oidc",
  "attributes": {
    "name": "provider",
    "type": "submit",
    "value": "google-Rvv4I_Gn",
    "disabled": false,
    "node_type": "input"
  },
  "messages": [],
  "meta": {
    "label": {
      "id": 1010002,
      "text": "Sign in with Google",
      "type": "info",
      "context": {
        "provider": "Google",
        "provider_id": "google-Rvv4I_Gn"
      }
    }
  }
}
To me it's not clear from the docs that is fixed or not. I want to show a button with a google icon here but there's no image provided so I need to check for a field to have google as a string and hope Ory doesn't change the contract? for example can meta.label.context.provider change?
c
A message from kapa.ai
You’re right that this isn’t clearly spelled out in one place, but there are a couple of stable things you can rely on from the docs and examples. ### What is stable? For OIDC login/settings nodes, Ory consistently uses: -
group: "oidc"
for social sign-in / account linking nodes [[Login with OIDC](https://www.ory.com/docs/kratos/self-service/flows/user-login#login-with-google-facebook-github--openid-connect--oauth-20); [Settings OIDC](https://www.ory.com/docs/kratos/self-service/flows/user-settings#link-and-unlink-from-google-facebook-github--openid-connect--oauth-20)] - `attributes`: -
name: "provider"
(or
"link"
/
"unlink"
for settings) -
value: "<provider-id>"
(e.g.
"google-Rvv4I_Gn"
) [[Login with OIDC](https://www.ory.com/docs/kratos/self-service/flows/user-login#login-with-google-facebook-github--openid-connect--oauth-20)] -
meta.label
with the human readable text and a
context
that includes: - `provider`: a human-friendly name, like
"Google"
or
"github"
[[Sign in ui-messages](https://www.ory.com/docs/kratos/concepts/ui-messages#sign-in-with-provider-1010002); [Link provider](https://www.ory.com/docs/kratos/concepts/ui-messages#link-provider-1050002)] - `provider_id`: the configured provider ID, same as
attributes.value
[[Sign in ui-messages](https://www.ory.com/docs/kratos/concepts/ui-messages#sign-in-with-provider-1010002)] The message definition for “Sign in with {provider}” explicitly documents this shape:
Copy code
json {   "id": 1010002,   "text": "Sign in with {provider}",   "type": "info",   "context": {     "provider": "{provider}",     "provider_id": "{providerID}"   } }
[[Sign in ui-messages](https://www.ory.com/docs/kratos/concepts/ui-messages#sign-in-with-provider-1010002)] That same
{provider}
context is used for “Sign up with {provider}” and “Link {provider}”, so
meta.label.context.provider
is not an accident – it’s part of the UI message contract.[[Sign up with provider](https://www.ory.com/docs/kratos/concepts/ui-user-interface#sign-up-with-provider-1040002); [Link provider](https://www.ory.com/docs/kratos/concepts/ui-user-interface#link-provider-1050002)] Ory Elements also relies on this convention: its
SsoButton
/
DefaultButtonSocial
components decide what logo to show based on the node and its attributes, i.e. they look at the OIDC node and provider identity to choose a Google / Microsoft icon.[[DefaultButtonSocial](https://www.ory.com/docs/elements/reference/@ory/elements-react/theme/functions/DefaultButtonSocial); [SsoButton docs](https://www.ory.com/docs/elements/reference/@ory/elements-react/type-aliases/OryFlowComponents#node)] ### How to safely add a Google icon Given the above, the safest pattern for your own UI is: - Treat all OIDC nodes generically (
group === "oidc"
). - For each node: - Use
attributes.value
/
meta.label.context.provider_id
as the stable provider ID. - Use
meta.label.context.provider
as the human-readable label. - Map provider IDs (or normalized
provider
) to logos on your side. Example (pseudo-code):
Copy code
ts function providerKey(node: UiNode) {   const ctx = node.meta?.label?.context as { provider?: string; provider_id?: string } | undefined   // Prefer provider_id (matches config id), fall back to lowercased provider name   return ctx?.provider_id ?? ctx?.provider?.toLowerCase() ?? "" }  const logos: Record<string, React.ComponentType> = {   "google-Rvv4I_Gn": GoogleLogo,   google: GoogleLogo,   "microsoft-3csX8ovp": MicrosoftLogo,   microsoft: MicrosoftLogo, }  const key = providerKey(node) const Logo = logos[key] ?? DefaultSsoIcon
This is exactly the pattern Elements uses conceptually: it receives a
UiNode
and a
logos
map and chooses the correct icon per provider.[[DefaultButtonSocial](https://www.ory.com/docs/elements/reference/@ory/elements-react/theme/functions/DefaultButtonSocial)] ### Could
meta.label.context.provider
change? The docs do not explicitly guarantee that
context.provider
will never change, but: - It’s part of the documented UI message contract for multiple messages (login, sign up, link).[[ui-messages](https://www.ory.com/docs/kratos/concepts/ui-messages)] - The official Elements components are built on top of these nodes/messages. So, while there is no explicit “stability promise” sentence, everything in the current docs and libraries assumes: -
group: "oidc"
-
attributes.name: "provider"
-
meta.label.context.provider
and
provider_id
as shown above. If you want to be maximally robust, key your logic primarily off
provider_id
(which directly reflects your OIDC provider config id) and treat
provider
as display text only.
q
@chilly-king-10285 for this node
Copy code
{
                "type": "input",
                "group": "identifier_first",
                "attributes": {
                    "name": "identifier",
                    "type": "text",
                    "value": "",
                    "required": true,
                    "autocomplete": "username webauthn",
                    "disabled": false,
                    "node_type": "input"
                },
                "messages": [],
                "meta": {
                    "label": {
                        "id": 1070002,
                        "text": "E-Mail",
                        "type": "info",
                        "context": {
                            "name": "traits.email",
                            "title": "E-Mail"
                        }
                    }
                }
            },
Is it safe to look for meta.label.context.name == traits.email ?
c
A message from kapa.ai
Short answer: yes, that pattern is explicitly documented and safe to rely on for this use case. For identifier‑/trait‑based fields, Ory documents that: - The label text and context come from the identity schema’s
title
and field path. - In the UI node,
meta.label.context
contains: - `name`: the trait path, e.g. `"traits.email"`; - `title`: the display title, e.g.
"E-Mail"
. This is shown in the identity schema / UI examples, where a trait:
Copy code
json "email": {   "type": "string",   "format": "email",   "title": "Email",   "<http://ory.sh/kratos|ory.sh/kratos>": { ... } }
produces a node like:
Copy code
json "meta": {   "label": {     "id": 1070002,     "text": "E-Mail",     "type": "info",     "context": {       "title": "E-Mail"     }   } }
and the more detailed examples show
context.name: "traits.email"
for the email field.[[Node order and labels](https://www.ory.com/docs/kratos/concepts/ui-user-interface#node-order-and-labels); [Custom user interface labels](https://www.ory.com/docs/kratos/manage-identities/customize-identity-schema#custom-user-interface-labels)] So for the identifier‑first node you posted, checking:
Copy code
ts node.meta?.label?.context?.name === "traits.email"
is a valid and stable way to detect “this is the email identifier field” in your custom UI.
q
@chilly-king-10285 when I enter the wrong password I want an error to show up. how do I identify it was an error?
c
A message from kapa.ai
For a custom UI you detect “wrong password” (or any validation error) by: 1. Catching the
updateLoginFlow
error
. 2. Checking the HTTP status. 3. Reading error messages from the returned flow. ### 1. Browser vs JSON mode For browser flows: - With normal browser headers,
updateLoginFlow
responds with: -
303
redirect back to the login UI (same
flow
id) when validation fails. [[updateLoginFlow](https://www.ory.sh/docs/kratos/reference/api#updateLoginFlow); [Login validation](https://www.ory.com/docs/kratos/self-service/flows/user-login#login-form-validation)] - Your UI should then call
getLoginFlow
again and render the messages in: -
flow.ui.messages
(form-level errors like “The provided credentials are invalid”), and/or -
flow.ui.nodes[*].messages
(field-level errors). [[Form submission & payload validation – browser](https://www.ory.com/docs/kratos/self-service#form-submission-and-payload-validation)] For JSON/AJAX mode (
Accept: application/json
): -
updateLoginFlow
responds with: -
400
and a JSON body containing the updated flow with error messages attached. [[updateLoginFlow](https://www.ory.com/docs/reference/api#operation/updateLoginFlow); [Form submission & payload validation – SPA](https://www.ory.com/docs/kratos/self-service#form-submission-and-payload-validation-1)] In a React app using JSON mode this is typically:
Copy code
ts ory   .updateLoginFlow({ flow: flow.id, updateLoginFlowBody: body })   .then(() => {     // success – redirect to your app   })   .catch((err: AxiosError) => {     if (err.response?.status === 400) {       // validation error (e.g. wrong password)       const updatedFlow = err.response.data as LoginFlow       setFlow(updatedFlow) // re-render; messages now included     }   })
### 2. Where the error message is For wrong credentials, Ory sets a method-level error like:
Copy code
json "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"     }   ],   ... }
[[Login validation – wrong credentials](https://www.ory.com/docs/kratos/self-service/flows/user-login#login-form-validation); [API flows example](https://www.ory.com/docs/kratos/self-service#form-submission-and-payload-validation-2)] Field-level errors (e.g. missing password) appear in each node’s `messages`:
Copy code
json {   "type": "input",   "group": "password",   "attributes": { "name": "password", ... },   "messages": [     {       "id": 4000001,       "text": "length must be >= 1, but got 0",       "type": "error"     }   ] }
[[Browser flows – validation example](https://www.ory.com/docs/kratos/self-service#form-submission-and-payload-validation)] So to show errors: - Render
flow.ui.messages
somewhere near the top of the form. - For each node you render, also render
node.messages
next to the corresponding input. This is the pattern shown in the “Error handling” example: iterate over
ui.messages
and
ui.nodes[*].messages
after a 400 response. [[Error handling](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#error-handling)]
q
@chilly-king-10285 when I enter try to enter the password I get
Copy code
{
  "id": "3faa0ea8-9a83-4b08-8e06-a732a90d3923",
  "organization_id": null,
  "type": "browser",
  "expires_at": "2025-12-16T20:07:49.850702Z",
  "issued_at": "2025-12-16T19:07:49.850702Z",
  "request_url": "<http://localhost:4000/self-service/login/browser?refresh=true>",
  "ui": {
    "action": "<http://localhost:4000/self-service/login?flow=3faa0ea8-9a83-4b08-8e06-a732a90d3923>",
    "method": "POST",
    "nodes": [
      {
        "type": "input",
        "group": "identifier_first",
        "attributes": {
          "name": "identifier",
          "type": "text",
          "value": "",
          "required": true,
          "autocomplete": "username webauthn",
          "disabled": false,
          "node_type": "input"
        },
        "messages": [
          {
            "id": 4000002,
            "text": "Property identifier is missing.",
            "type": "error",
            "context": {
              "property": "identifier"
            }
          }
        ],
        "meta": {
          "label": {
            "id": 1070002,
            "text": "E-Mail",
            "type": "info",
            "context": {
              "name": "traits.email",
              "title": "E-Mail"
            }
          }
        }
      },
      {
        "type": "input",
        "group": "identifier_first",
        "attributes": {
          "name": "method",
          "type": "submit",
          "value": "identifier_first",
          "disabled": false,
          "node_type": "input"
        },
        "messages": [],
        "meta": {
          "label": {
            "id": 1070009,
            "text": "Continue",
            "type": "info"
          }
        }
      },
      {
        "type": "input",
        "group": "oidc",
        "attributes": {
          "name": "provider",
          "type": "submit",
          "value": "google-Rvv4I_Gn",
          "disabled": false,
          "node_type": "input"
        },
        "messages": [],
        "meta": {
          "label": {
            "id": 1010002,
            "text": "Sign in with Google",
            "type": "info",
            "context": {
              "provider": "Google",
              "provider_id": "google-Rvv4I_Gn"
            }
          }
        }
      },
      {
        "type": "input",
        "group": "oidc",
        "attributes": {
          "name": "provider",
          "type": "submit",
          "value": "microsoft-3csX8ovp",
          "disabled": false,
          "node_type": "input"
        },
        "messages": [],
        "meta": {
          "label": {
            "id": 1010002,
            "text": "Sign in with Microsoft",
            "type": "info",
            "context": {
              "provider": "Microsoft",
              "provider_id": "microsoft-3csX8ovp"
            }
          }
        }
      },
      {
        "type": "input",
        "group": "default",
        "attributes": {
          "name": "csrf_token",
          "type": "hidden",
          "value": "nNOFAHB1cma64yoU+xpPGwWfhJPP5qlHDqNQWVGFvuZfeyHu1LtPkynt62BzycvE28zLRmmTVpzekAtJp4DEKw==",
          "required": true,
          "disabled": false,
          "node_type": "input"
        },
        "messages": [],
        "meta": {}
      },
      {
        "type": "input",
        "group": "default",
        "attributes": {
          "name": "password",
          "type": "text",
          "disabled": false,
          "node_type": "input"
        },
        "messages": [],
        "meta": {}
      }
    ]
  },
  "created_at": "2025-12-16T19:07:50Z",
  "updated_at": "2025-12-16T19:07:50Z",
  "refresh": false,
  "requested_aal": "aal1",
  "state": "choose_method"
}
c
A message from kapa.ai
You’re seeing that error state because you’re still in the first identifier‑first step, and your submit body doesn’t contain the required
identifier
field. Ory is responding with a new flow where the
identifier
node has a validation error:
Copy code
json "messages": [   {     "id": 4000002,     "text": "Property identifier is missing.",     "type": "error",     "context": { "property": "identifier" }   } ]
and the flow state is still:
Copy code
json "state": "choose_method"
Key points from this response: - The only login method that has actually been triggered is
identifier_first
(because
active
is not
password
and there is no
password
group yet). - Ory complains
identifier
is missing → your React submit handler did not send the identifier value when you tried to type a password. In identifier‑first login, the correct sequence is: 1. Step 1 – identifier screen Flow has groups:
["default", "identifier_first", "oidc"]
. You must: - render and POST
identifier
+
method=identifier_first
+
csrf_token
to
flow.ui.action
. [[Identifier‑first auth](https://www.ory.com/docs/identities/sign-in/identifier-first-authentication); [Login flow payloads](https://www.ory.com/docs/kratos/self-service/flows/user-login#login-flow-payloads)] 2. Step 2 – password screen for that identifier After a successful identifier‑first submit, you get a new flow whose
ui.nodes
include the
password
group (identifier is now hidden/fixed, password + “Sign in with password” buttons). [[Login flow payloads](https://www.ory.com/docs/kratos/self-service/flows/user-login#login-flow-payloads)] In your last response payload:
Copy code
json {   "group": "default",   "attributes": {     "name": "password",     "type": "text",     "disabled": false,     "node_type": "input"   } }
that
password
node is in the
default
group
, which is not how the normal password method is exposed (it should be
group: "password"
with
identifier
,
password
, and
method=password
).[[Login payload example](https://www.ory.com/docs/kratos/self-service/flows/user-login#login-with-usernameemail-and-password)] This suggests your custom UI is: - adding its own
password
field into the form (outside of the nodes), or - mutating the form payload so that Ory never receives the expected
identifier_first
data, and instead just sees a request with no
identifier
. To fix this: 1. Step 1 UI (email only) - When the flow contains
identifier_first
nodes, render only: -
group: "default"
(CSRF), -
group: "identifier_first"
(identifier field + Continue button), - and optionally
group: "oidc"
for social buttons. - Your submit handler must send at least:
Copy code
json      {        "identifier": "<user email>",        "method": "identifier_first",        "csrf_token": "<value from default node>"      }
This is exactly the pattern in the identifier‑first docs. [[Identifier‑first auth](https://www.ory.com/docs/identities/sign-in/identifier-first-authentication)] 2. After identifier submit - Call
updateLoginFlow
and, on success, load the new flow ID from the redirect / error and fetch it with
getLoginFlow
. [[Submit login flow](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#submit-login-flow)] - That new flow should now have
group: "password"
nodes. Only then render the password input and
method=password
submit button. 3. Do not inject your own password field into the first step If you want “email + password on the first screen”, that would require
style: unified
on the server side, which is currently not supported on Ory Network’s Account Experience; with identifier‑first you must respect the two‑step flow. In other words: the error you’re seeing (
Property identifier is missing
) is correct—Ory never saw an
identifier
for the identifier‑first method. Adjust your custom UI so that: - Step 1: send
identifier
+
method=identifier_first
(no password field yet). - Step 2: after getting a flow that contains
group: "password"
, render password and sign‑in button and submit
password
+
method=password
+
csrf_token
.
q
@chilly-king-10285 I'm having CSRF errors. I'm assuming it's becaus eI don't use this? { "type": "input", "group": "default", "attributes": { "name": "csrf_token", "type": "hidden", "value": "9wOGAxhK9roVy2rtijFTcQCwUqSqMA5tYOhP7APUYkSibDfHud3W8Oah3z9kfnz7SgvRe5m/10nY3ancgmiq1A==", "required": true, "disabled": false, "node_type": "input" }, "messages": [], "meta": {} }, Where should i use it?
c
A message from kapa.ai
Yes, CSRF errors are very likely because that node is not being sent back. In a browser flow, you must: 1. Render that node as a hidden input in your form. 2. Send its value back in the request body as
csrf_token
, together with the other fields (identifier, password, method, …). 3. Make sure the CSRF cookie is also sent (browser does this if you use
withCredentials: true
/
credentials: "include"
). [[CSRF defenses](https://www.ory.com/docs/kratos/concepts/security#defenses-against-cross-site-request-forgery); [CSRF troubleshooting](https://www.ory.com/docs/troubleshooting/csrf); [BYO UI debug](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#debug)] From the docs (for login/registration/settings): > For browser applications you must send all cookies and the CSRF token in the request body. The CSRF token value is a hidden input field called
csrf_token
. [[Submit login flow](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#submit-login-flow); [Submit registration flow](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#submit-registration-flow)] ### How to use that node in your React form When you render the nodes, do not filter out the
default
group and keep the
csrf_token
input as-is:
Copy code
tsx <form action={flow.ui.action} method={flow.ui.method} onSubmit={submit}>   {filterNodesByGroups({     nodes: flow.ui.nodes,     groups: ["default", "identifier_first", "password", "oidc"], // includes csrf_token   }).map(mapNode)} </form>
With your existing
mapNode
, that node will become:
Copy code
html <input   name="csrf_token"   type="hidden"   value="9wOGAxhK9roVy2rtijFTcQCwUqSqMA5tYOhP7APUYkSibDfHud3W8Oah3z9kfnz7SgvRe5m/10nY3ancgmiq1A==" />
When the form is submitted (either via `fetch`/`updateLoginFlow` or native POST), the body will include
csrf_token=…
, which Kratos compares against the CSRF cookie. If they match, the request is accepted; if missing or mismatched, you get
security_csrf_violation
. [[CSRF defenses](https://www.ory.com/docs/kratos/concepts/security#defenses-against-cross-site-request-forgery); [BYO UI debug](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#debug)] So: - Yes, you must use that node. - Render it as a hidden field and make sure it’s included in the submitted form data.
q
@chilly-king-10285 do you know what the 4000006 id is for messages when returning an error from login flow?
c
A message from kapa.ai
4000006
is the message ID for: > “The provided credentials are invalid, check for spelling mistakes in your password or username, email address, or phone number.” [[Login validation](https://www.ory.com/docs/kratos/self-service/flows/user-login#login-form-validation); [Message 4000006](https://www.ory.com/docs/kratos/concepts/ui-messages#the-provided-credentials-are-invalid-check-for-spelling-mistakes-in-your-password-or-username-email-address-or-phone-number-4000006)] In JSON it looks like:
Copy code
json {   "id": 4000006,   "text": "The provided credentials are invalid, check for spelling mistakes in your password or username, email address, or phone number.",   "type": "error" }
[[UI message 4000006](https://www.ory.com/docs/kratos/concepts/ui-user-interface#the-provided-credentials-are-invalid-check-for-spelling-mistakes-in-your-password-or-username-email-address-or-phone-number-4000006)]
q
@chilly-king-10285 how do I initiate the forgot password flows?
c
A message from kapa.ai
To start a “forgot password” (account recovery) flow you use the recovery flow endpoints. The exact endpoint depends on whether you’re in a browser app or a native/mobile/API client. ### 1. Browser apps (usual case with Experience UI or custom web UI) Initiate the flow by calling:
Copy code
http GET /self-service/recovery/browser
- This creates a browser recovery flow and then redirects the browser to your configured recovery UI (
selfservice.flows.recovery.ui_url
) with
?flow=<flow-id>
in the query string.[\[createBrowserRecoveryFlow\]](https://www.ory.sh/docs/reference/api#operation/createBrowserRecoveryFlow) - If you’re building a custom UI in React, you typically call the SDK and render based on the returned flow:
Copy code
ts import { Configuration, FrontendApi } from "@ory/client"  const frontend = new FrontendApi(   new Configuration({     basePath: "<http://localhost:4000>", // Ory Tunnel in dev     baseOptions: { withCredentials: true },   }), )  await frontend.createBrowserRecoveryFlow({ returnTo: "/" })
[[Create recovery flow – React](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#create-recovery-flow)] Then, on your
/recovery
page, you load the flow by ID (
flow
query param) and render
flow.ui.nodes
.[\[Get recovery flow – React](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#get-recovery-flow)] ### 2. Native / mobile / API clients Use the API recovery flow:
Copy code
http GET /self-service/recovery/api
Example from the docs:
Copy code
bash curl -H 'Content-Type: application/json' \      -H 'Accept: application/json' \      "https://$<http://PROJECT_SLUG.projects.oryapis.com/self-service/recovery/api|PROJECT_SLUG.projects.oryapis.com/self-service/recovery/api>"
This returns a JSON
RecoveryFlow
object (no redirects) with
ui.action
,
ui.method
, and
ui.nodes
, which you then use to POST the user’s email or code.[\[Create recovery flow – native](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#create-recovery-flow)] ### 3. Submitting the flow (user enters email / code) Once the flow is created and rendered, you submit it via:
Copy code
http POST /self-service/recovery?flow=<flow-id>
with a body including: -
method
(
"code"
for code-based recovery), -
email
(first step), -
code
(second step, when the user types the received code), -
csrf_token
(for browser flows). Example (browser):
Copy code
bash curl -X POST -H 'Content-Type: application/json' \   -H 'Accept: application/json' \   -d '{"method":"code","email":"[email protected]","csrf_token":"your-csrf-token"}' \   -b cookies.txt \   "https://$<http://PROJECT_SLUG.projects.oryapis.com/self-service/recovery?flow=<flow-id|PROJECT_SLUG.projects.oryapis.com/self-service/recovery?flow=<flow-id>>"
[[Submit recovery flow – browser](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#submit-recovery-flow)] --- So to “initiate forgot password”: - Browser:
GET /self-service/recovery/browser
→ redirect to your recovery UI with
?flow=…
. - Native/API:
GET /self-service/recovery/api
→ use returned flow JSON.
q
@chilly-king-10285 how do I initiate the forgot password with the JS ory client?
c
A message from kapa.ai
To start a “forgot password” (recovery) flow with the JS Ory client you use the FrontendApi recovery flow methods. Use browser flow for web apps, native flow for mobile / API clients. ### 1. Browser “forgot password” flow (React / SPA)
Copy code
ts import { Configuration, FrontendApi } from "@ory/client"  const frontend = new FrontendApi(   new Configuration({     basePath: "<http://localhost:4000>", // Ory Tunnel in dev     baseOptions: {       withCredentials: true, // include cookies     },   }), )  // Create the browser recovery flow export async function createRecovery() {   return await frontend.createBrowserRecoveryFlow({     returnTo: "/", // optional redirect after completed recovery   }) }
This matches the docs’ React example (just with
createBrowserRecoveryFlow
instead of
createNativeRecoveryFlow
).[[Create recovery flow](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#create-recovery-flow)] Then on your
/recovery
page, load and render the flow:
Copy code
ts import { RecoveryFlow } from "@ory/client" import { useEffect, useState } from "react" import { useSearchParams } from "react-router-dom"  const frontend = new FrontendApi(   new Configuration({     basePath: "<http://localhost:4000>",     baseOptions: { withCredentials: true },   }), )  export const Recovery = () => {   const [flow, setFlow] = useState<RecoveryFlow>()   const [searchParams] = useSearchParams()    useEffect(() => {     const id = searchParams.get("flow")      frontend       .getRecoveryFlow({ id: id! })       .then(({ data }) => setFlow(data))       .catch(() => {         // handle error       })   }, [])    // render flow.ui.nodes and submit with updateRecoveryFlow }
[[Get recovery flow – React](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#get-recovery-flow)] To submit (user enters email / code), call:
Copy code
ts import { UpdateRecoveryFlowBody } from "@ory/client"  await frontend.updateRecoveryFlow({   flow: flow.id,   updateRecoveryFlowBody: body as UpdateRecoveryFlowBody, })
[[Submit recovery flow – React](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#submit-recovery-flow)] ### 2. Native / mobile (API) “forgot password” flow For native/API clients you initialize the native flow:
Copy code
ts import { Configuration, FrontendApi } from "@ory/client"  const frontend = new FrontendApi(   new Configuration({     basePath: `https://${process.env.ORY_PROJECT_SLUG}.<http://projects.oryapis.com|projects.oryapis.com>`,   }), )  export async function createRecovery() {   return await frontend.createNativeRecoveryFlow() }
[[Create recovery flow – native](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#create-recovery-flow)]
q
@chilly-king-10285 with custom UI how do I implemetn the social sign in buttons for google and microsoft?
c
A message from kapa.ai
You can implement Google and Microsoft social sign-in in a custom UI by rendering the
oidc
nodes from the login (or registration) flow as native HTML form submits that post directly to Ory. From your flow example, each provider is exposed as an
oidc
node like:
Copy code
json {   "type": "input",   "group": "oidc",   "attributes": {     "name": "provider",     "type": "submit",     "value": "google-Rvv4I_Gn",     "disabled": false,     "node_type": "input"   },   "meta": {     "label": {       "text": "Sign in with Google",       "context": {         "provider": "Google",         "provider_id": "google-Rvv4I_Gn"       }     }   } }
Ory expects a native form POST for social sign-in: when the user clicks the button, the browser must submit
provider=<provider-id>
to
flow.ui.action
. [[Advanced social sign-in](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-advanced-integration#social-sign-in)] ### 1. Basic HTML structure In your React (or any) UI, you can render a dedicated form for OIDC:
Copy code
tsx <form action={flow.ui.action} method={flow.ui.method}>   {/* CSRF token from group "default" */}   <input     type="hidden"     name="csrf_token"     value={csrfNode.attributes.value}   />    {/* Google */}   <button type="submit" name="provider" value="google-Rvv4I_Gn">     Sign in with Google   </button>    {/* Microsoft */}   <button type="submit" name="provider" value="microsoft-3csX8ovp">     Sign in with Microsoft   </button> </form>
This matches the docs’ SPA example, where they show a separate form that does a native POST for social sign-in. [[Advanced social sign-in](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-advanced-integration#social-sign-in)] ### 2. Rendering dynamically from nodes (recommended) Instead of hardcoding provider IDs, render all
oidc
nodes from `flow.ui.nodes`:
Copy code
tsx import { filterNodesByGroups, isUiNodeInputAttributes } from "@ory/integrations/ui" import { UiNode, UiNodeInputAttributes } from "@ory/client"  const oidcNodes = filterNodesByGroups({   nodes: flow.ui.nodes,   groups: ["oidc"], })  const csrfNode = flow.ui.nodes.find(   (n) =>     isUiNodeInputAttributes(n.attributes) &&     (n.attributes as UiNodeInputAttributes).name === "csrf_token", ) as UiNode | undefined  <form action={flow.ui.action} method={flow.ui.method}>   {csrfNode && isUiNodeInputAttributes(csrfNode.attributes) && (     <input       type="hidden"       name="csrf_token"       value={(csrfNode.attributes as UiNodeInputAttributes).value}     />   )}    {oidcNodes.map((node, idx) => {     if (!isUiNodeInputAttributes(node.attributes)) return null     const attrs = node.attributes as UiNodeInputAttributes     const label = node.meta?.label?.text ?? attrs.value      return (       <button         key={idx}         type="submit"         name={attrs.name}   // "provider"         value={attrs.value} // e.g. "google-..."         disabled={attrs.disabled}       >         {label}       </button>     )   })} </form>
This follows the same pattern as the docs’ custom-UI examples (using
filterNodesByGroups
and
UiNodeInputAttributes
). [[Create login flow](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#create-login-flow); [UI node groups](https://www.ory.com/docs/kratos/concepts/ui-user-interface#ui-node-groups)] ### 3. Adding Google / Microsoft icons To show provider-specific icons, you can inspect: -
attrs.value
(e.g.
"google-Rvv4I_Gn"
,
"microsoft-3csX8ovp"
), or -
node.meta.label.context.provider
(e.g.
"Google"
,
"Microsoft"
). [[Sign in with provider message](https://www.ory.com/docs/kratos/concepts/ui-messages#sign-in-with-provider-1010002)] Example:
Copy code
tsx const providerCtx = node.meta?.label?.context as   | { provider?: string; provider_id?: string }   | undefined  const providerKey =   providerCtx?.provider_id ?? providerCtx?.provider?.toLowerCase() ?? ""  let Icon = DefaultIcon if (providerKey.startsWith("google") || providerKey === "google") Icon = GoogleIcon if (providerKey.startsWith("microsoft") || providerKey === "microsoft") Icon = MicrosoftIcon  return (   <button ...>     <Icon />     {label}   </button> )
This is the same information Ory Elements uses to render its
SsoButton
with provider logos. [[OryFlowComponents Node.SsoButton](https://www.ory.com/docs/elements/reference/@ory/elements-react/type-aliases/OryFlowComponents#node)] ### 4. Keep OIDC in a separate form (SPA note) If you also have password login on the same page, the docs recommend two forms: - One form for OIDC that does a native POST (as above). - One form for password (and other methods) that you can handle via XHR/`updateLoginFlow`. [[Advanced social sign-in](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-advanced-integration#social-sign-in)] This avoids conflicts between SPA-style submission and the redirect-based OIDC flow. --- So, in summary: - Use
group: "oidc"
nodes to render one button per provider. - Each button is a native submit with
name="provider"
and
value
from the node. - Include the
csrf_token
hidden input from the
default
group. - Optionally map
meta.label.context.provider
/
provider_id
to show Google/Microsoft icons.
q
@chilly-king-10285 the button oes curl 'http://localhost:4000/self-service/login?flow=f4dec5a9-8393-4fe8-bc3e-4f1a836c0b03' \ -H 'Accept: application/json, text/plain, */*' \ -H 'Accept-Language: en-GB,en-US;q=0.9,en;q=0.8' \ -H 'Connection: keep-alive' \ -H 'Content-Type: application/json' \ -b '__cflb=04dTofjtHwhfCny3TibYMM4LURzprwaH96f2gaNAbm; ory_kratos_continuity=MTc2NjUyMTU1NnxEWDhFQVFMX2dBQUJFQUVRQUFCZl80QUFBUVp6ZEhKcGJtY01Jd0FoYjNKNVgydHlZWFJ2YzE5dmFXUmpYMkYxZEdoZlkyOWtaVjl6WlhOemFXOXVCbk4wY21sdVp3d21BQ1E1WkdabE5qQmhZaTB4WWpObUxUUTVPV1V0WWpsbVlTMHdaakV3WVdJd1pHWTJOMlk9fM1N12NhAwgxKqoTD6kEtIu_w7OIELRgcfD3oK9mOYXq; csrf_token_c6bb307207b77f13527f367f9b6ea5c6fc893d2f2c9c06d269c47baf0bb062d9=4hQH5TfUzmCx5TNTZ80SBbcNqwYCXp5ivVZyezXLppk=; __cf_bm=2izRi9Ylc.GRIbG.BSwN07wnx6yXmNpFN95dT0BYSxw-1766521679.2823906-1.0.1.1-VlTJzS.3qKl.JRki6Lt9.po6j_k9TP533Hqq1aYXzDOVy1ZEL4tav.0OHiaLLSVgTm471jK_LEv78wWJqqlNslxlEx3yJ8kHBgmx.eBFiIijNuipwEslbdKKX9zo.7YK' \ -H 'Origin: http://localhost:5173' \ -H 'Referer: http://localhost:5173/' \ -H 'Sec-Fetch-Dest: empty' \ -H 'Sec-Fetch-Mode: cors' \ -H 'Sec-Fetch-Site: same-site' \ -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36' \ -H 'sec-ch-ua: "Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24"' \ -H 'sec-ch-ua-mobile: ?0' \ -H 'sec-ch-ua-platform: "macOS"' \ --data-raw '{"method":"oidc","provider":"google","csrf_token":"8z53RKVkHyRYjbdrgpoHBLmPzhpHdfH2PdXH8lJCSGwRKnChkrDRROlohDjlVxUBDoJlHEUrb5SAg7WJZ4nu9Q=="}' and I get the response
Copy code
{
  "id": "ab311c76-2033-49f1-97c0-99247022628a",
  "organization_id": null,
  "type": "browser",
  "expires_at": "2025-12-23T20:49:55.963277Z",
  "issued_at": "2025-12-23T19:49:55.963277Z",
  "request_url": "<http://localhost:4000/self-service/login/browser?refresh=true>",
  "ui": {
    "action": "<http://localhost:4000/self-service/login?flow=ab311c76-2033-49f1-97c0-99247022628a>",
    "method": "POST",
    "nodes": [
      {
        "type": "input",
        "group": "identifier_first",
        "attributes": {
          "name": "identifier",
          "type": "text",
          "value": "",
          "required": true,
          "autocomplete": "username webauthn",
          "disabled": false,
          "node_type": "input"
        },
        "messages": [],
        "meta": {
          "label": {
            "id": 1070002,
            "text": "E-Mail",
            "type": "info",
            "context": {
              "name": "traits.email",
              "title": "E-Mail"
            }
          }
        }
      },
      {
        "type": "input",
        "group": "identifier_first",
        "attributes": {
          "name": "method",
          "type": "submit",
          "value": "identifier_first",
          "disabled": false,
          "node_type": "input"
        },
        "messages": [],
        "meta": {
          "label": {
            "id": 1070009,
            "text": "Continue",
            "type": "info"
          }
        }
      },
      {
        "type": "input",
        "group": "oidc",
        "attributes": {
          "name": "provider",
          "type": "submit",
          "value": "google-Rvv4I_Gn",
          "disabled": false,
          "node_type": "input"
        },
        "messages": [],
        "meta": {
          "label": {
            "id": 1010002,
            "text": "Sign in with Google",
            "type": "info",
            "context": {
              "provider": "Google",
              "provider_id": "google-Rvv4I_Gn"
            }
          }
        }
      },
      {
        "type": "input",
        "group": "oidc",
        "attributes": {
          "name": "provider",
          "type": "submit",
          "value": "microsoft-3csX8ovp",
          "disabled": false,
          "node_type": "input"
        },
        "messages": [],
        "meta": {
          "label": {
            "id": 1010002,
            "text": "Sign in with Microsoft",
            "type": "info",
            "context": {
              "provider": "Microsoft",
              "provider_id": "microsoft-3csX8ovp"
            }
          }
        }
      },
      {
        "type": "input",
        "group": "default",
        "attributes": {
          "name": "csrf_token",
          "type": "hidden",
          "value": "WUf5n7xNPrasraLw2/H9hlQWAL3SKOhFSEwUP96u3NENiTu3nMv4RHiZwjGeNdcjxGelL8h0frYBLjHVMRLbSA==",
          "required": true,
          "disabled": false,
          "node_type": "input"
        },
        "messages": [],
        "meta": {}
      }
    ]
  },
  "created_at": "2025-12-23T19:49:56Z",
  "updated_at": "2025-12-23T19:49:56Z",
  "refresh": false,
  "requested_aal": "aal1",
  "state": "choose_method"
}
c
A message from kapa.ai
You’re calling the login endpoint with
Accept: application/json
, so Ory is behaving in API/SPA mode and returning the updated login flow as JSON, not doing the browser redirect that actually starts the Google flow. [[updateLoginFlow](https://www.ory.sh/docs/kratos/reference/api#updateLoginFlow)] For social sign‑in, Ory expects a native browser form POST without
Accept: application/json
, so the browser can follow the redirect to Google. In SPAs, the docs recommend using a separate form for OIDC that does a normal POST, while you keep JSON/AJAX for password etc. [[Advanced social sign-in](https://www.ory.sh/docs/kratos/bring-your-own-ui/custom-ui-advanced-integration#social-sign-in)] Right now your request:
Copy code
bash -H 'Accept: application/json, text/plain, */*' -d '{"method":"oidc","provider":"google", ... }'
tells Ory: “treat this as an API client and give me JSON”, so it just returns a fresh flow (what you pasted) instead of redirecting to Google. To fix it: 1. Render an OIDC-only form that does a native POST to `flow.ui.action`:
Copy code
html <form action="http://localhost:4000/self-service/login?flow=..." method="POST">   <input type="hidden" name="csrf_token" value="..."/>    <!-- Google -->   <button type="submit" name="provider" value="google-Rvv4I_Gn">     Sign in with Google   </button>    <!-- Microsoft -->   <button type="submit" name="provider" value="microsoft-3csX8ovp">     Sign in with Microsoft   </button> </form>
2. Do not set
Accept: application/json
on that form submit.
Let the browser handle the redirect to Google and back. This is exactly what the docs show for SPAs: one native form for OIDC, another (XHR/JSON) for password. [[Advanced social sign-in](https://www.ory.sh/docs/kratos/bring-your-own-ui/custom-ui-advanced-integration#social-sign-in)] If you keep
Accept: application/json
(or submit via `fetch`/Axios), you’ll only ever see JSON flows like the one you posted and the Google redirect will never happen.
q
@chilly-king-10285 when I send this request curl 'http://localhost:4000/self-service/login?flow=3a0f73ba-ef9d-4ab3-b572-549815a57262' \ -H 'Accept: application/json, text/plain, */*' \ -H 'Accept-Language: en-GB,en-US;q=0.9,en;q=0.8' \ -H 'Connection: keep-alive' \ -H 'Content-Type: application/json' \ -b 'ory_kratos_continuity=MTc2NjU4Mzc5NnxEWDhFQVFMX2dBQUJFQUVRQUFCZl80QUFBUVp6ZEhKcGJtY01Jd0FoYjNKNVgydHlZWFJ2YzE5dmFXUmpYMkYxZEdoZlkyOWtaVjl6WlhOemFXOXVCbk4wY21sdVp3d21BQ1E1Wm1RM016VTJaaTB4TVRRekxUUmlNbVF0WVRrMk15MWlOMk5oTnpka01qZzBOelU9fBXCnqY2g0if7h1zLdL_iqeEGm9-u71P3YJYyk_4C3o4; __cflb=04dTofjtHwhfCny3TibYMM4LURzprwaSuSuSTJRJ7D; csrf_token_c6bb307207b77f13527f367f9b6ea5c6fc893d2f2c9c06d269c47baf0bb062d9=U9EzPrFNW9XNo9GeRJkz39w3R2URh0x6/lpD7GAXtWs=; __cf_bm=nvon.p4I4wfyOW10N3wtBAaVqG6pAmAs4nVDJhvyewA-1766600646.171507-1.0.1.1-gh2AwBpgZho4nXq75GCClN.uqj7hrJ.2wV57uH2O3yMzoaF3yb4Z8tQdwKS.h3SzWXwfZ29TPlOFg7OAYOz8h2_19EImfhwbZ33mWjElKkcbPuhO6WfNLCNlMRq3AGLU' \ -H 'Origin: http://localhost:5173' \ -H 'Referer: http://localhost:5173/' \ -H 'Sec-Fetch-Dest: empty' \ -H 'Sec-Fetch-Mode: cors' \ -H 'Sec-Fetch-Site: same-site' \ -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36' \ -H 'sec-ch-ua: "Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24"' \ -H 'sec-ch-ua-mobile: ?0' \ -H 'sec-ch-ua-platform: "macOS"' \ --data-raw '{"method":"password","identifier":"[email protected]","csrf_token":"bLteduu8G9RkJcY5o3rFPPg1/lkCi/+7VPpi9XdaA58/am1IWvFAAamGF6fn4/bjJAK5PBMMs8GqoCEZF0229A=="}' Is it possiblet o find out what authentication that user has setup? for example if the user never set a password I'd like to know there's no password and not show the password field in that case
c
A message from kapa.ai
The knowledge sources don’t describe a way to learn, during a login flow, which concrete credential types a specific identity actually has (e.g. “this user has no password credential, only OIDC”). The login flow payload only tells you which methods are offered for this flow (via
ui.nodes
groups like
password
,
oidc
,
identifier_first
), not what’s configured on the identity itself.[\[Login flow payloads](https://www.ory.com/docs/kratos/self-service/flows/user-login#login-flow-payloads)\] So for your specific goal: > “If the user never set a password I’d like to know there’s no password and not show the password field” there is no documented way to derive that from the login flow alone. What you can do (admin-side, not in the public login flow): - Use the Admin Identity API to inspect credentials for a known identity ID and check if the
password
credential exists:
Copy code
http   GET /admin/identities/{id}?include_credential=password
The response’s
credentials
object will contain a
password
entry if that identity has a password credential.[\[getIdentity](https://www.ory.sh/docs/reference/api#tag/identity/operation/getIdentity); [Credentials concept](https://www.ory.com/docs/kratos/concepts/credentials)\] But that requires you to already know which identity you’re looking at (i.e. after login or via some back‑office logic), and it’s not something you can safely use in the public login UI to decide whether to render the password field. In short: - From the login flow: you can only see which methods are available for the project/flow (password, OIDC, etc.), not whether this particular user has ever set a password. - From the Admin API: you can check an identity’s credentials (
password
,
oidc
, …) if you already know their identity ID and are on the backend.
q
@chilly-king-10285 my web app does cookie based authentication. I'm going ot implement a mobile app. What should I do on Ory's side to be able to authenticate via my mobile app?
c
A message from kapa.ai
You don’t need to change your existing cookie-based web setup. For a mobile app you add native/API flows and use session tokens instead of cookies. From Ory’s perspective you should: --- ### 1. Use API (native) self-service flows in the mobile app Mobile apps must use the
/api
variants of the flows, not the browser ones: - Login:
GET /self-service/login/api
→ create native login flow - Registration:
GET /self-service/registration/api
- Settings, recovery, etc. have similar
/api
endpoints.[\[API flows – native apps](https://www.ory.com/docs/kratos/self-service#api-flows-native-mobile--desktop-apps-command-line-interfaces-smart-tvs-and-more); [createNativeLoginFlow](https://www.ory.sh/docs/kratos/reference/api#createNativeLoginFlow)] These endpoints: - return JSON only (no redirects, no CSRF cookies), - are designed for mobile / desktop / CLI clients. Your mobile app: 1. Calls
createNativeLoginFlow
. 2. Renders the form from
flow.ui.nodes
using native UI components. 3. Submits credentials as JSON to the
ui.action
URL (or via
updateLoginFlow
in the SDK). --- ### 2. Use session tokens instead of cookies For native clients, Ory issues an Ory Session Token instead of a browser cookie.[\[Browser vs native apps](https://www.ory.com/docs/identities/native-browser); [Using Ory Session Token](https://www.ory.com/docs/kratos/session-management/overview#using-ory-session-token)] - After a successful native login, you get a session token (or you can request a session token exchange code and then exchange it). - Store this token securely on the device (Keychain / Keystore). - To check the session, call:
Copy code
http   GET /sessions/whoami   Authorization: Bearer <session-token>
or with the SDK using
xSessionToken
.[\[Check session via token](https://www.ory.com/docs/identities/sign-in/check-session-token-cookie-api); [Session token example](https://www.ory.com/docs/kratos/session-management/overview#using-ory-session-token)] The response is the same session object you’d get from the cookie-based
whoami
on the web. --- ### 3. Keep your existing cookie-based web auth as-is Your current web app continues to: - use
/self-service/{flow}/browser
endpoints, - rely on Ory session cookies in the browser.[\[Browser vs native apps](https://www.ory.com/docs/identities/native-browser)] You don’t need to disable or reconfigure cookies to add mobile auth; you just: - use browser flows + cookies for the web app, - use API flows + session tokens for the mobile app, against the same Ory project. --- ### Summary On Ory’s side you only need to: - Call the native
/api
self-service endpoints
from the mobile app. - Use and store the session token returned by those flows. - Use
whoami
with
Authorization: Bearer
/
xSessionToken
to validate sessions. No breaking changes to your existing cookie-based web authentication are required.
q
@chilly-king-10285 can you give me a simple JS implementation for the sessions token approach?
c
A message from kapa.ai
Here’s a minimal JS example using the Ory JS client with session tokens (for mobile / native style auth). ### 1. Create the client
Copy code
js import { Configuration, FrontendApi } from "@ory/client"  const ory = new FrontendApi(   new Configuration({     basePath: `https://${process.env.ORY_PROJECT_SLUG}.<http://projects.oryapis.com|projects.oryapis.com>`,   }), )
### 2. Check a session with a session token Given a session token you stored on the device:
Copy code
js async function checkSession(sessionToken) {   // Uses the X-Session-Token header under the hood   const { data: session } = await ory.toSession({     xSessionToken: sessionToken,   })    return session }
This is the pattern shown in the docs for using session tokens instead of cookies.[[Session token TS example](https://www.ory.com/docs/kratos/session-management/overview#using-ory-session-token)] ### 3. Example usage
Copy code
js (async () => {   const token = "<stored-session-token-from-login>"    try {     const session = await checkSession(token)     console.log("User is logged in as:", session.identity.id)   } catch (err) {     // 401 / 403 → no or invalid session     console.error("Not logged in or session invalid", err.response?.status)   } })()
The docs only show the “check session” side for session tokens (not a full native login example), so this snippet focuses on that part.[[Check session via token](https://www.ory.com/docs/identities/sign-in/check-session-token-cookie-api); [Session token TS example](https://www.ory.com/docs/kratos/session-management/overview#using-ory-session-token)]
q
@chilly-king-10285 and how do I do the login with the setup you showed me above?
c
A message from kapa.ai
The knowledge sources only show how to use a session token (check an existing session), not a full native login flow that returns the token. They do not contain a complete JS example for “log in and get a session token” for mobile/native apps, so I can’t give you a fully accurate end‑to‑end snippet. What is documented: - For native/mobile apps you must use the API flows (
/self-service/login/api
) and then use the session token with
toSession({ xSessionToken })
instead of cookies.[Browser vs native apps](https://www.ory.com/docs/identities/native-browser)[API flows – native apps](https://www.ory.com/docs/kratos/self-service#api-flows-native-mobile--desktop-apps-command-line-interfaces-smart-tvs-and-more) - Once you have a session token, you check it like this (JS/TS example from the docs):
Copy code
ts   import { Configuration, FrontendApi } from "@ory/client"    const frontend = new FrontendApi(     new Configuration({       basePath: `https://${process.env.ORY_PROJECT_SLUG}.<http://projects.oryapis.com|projects.oryapis.com>`,     }),   )    export async function checkSession(sessionToken: string) {     return await frontend.toSession({       xSessionToken: sessionToken,     })   }
[Using Ory Session Token](https://www.ory.com/docs/kratos/session-management/overview#using-ory-session-token) What’s not shown in the provided sources is: - A JS example that: 1. Calls
createNativeLoginFlow
/
/self-service/login/api
, 2. Submits credentials, 3. Receives a session token in the response. Because that part is missing from the knowledge you provided, I can’t reliably show the exact request/response shape or the JS code to extract the token without guessing. So, conceptually: 1. Your mobile app should perform login using the native login flow (
/self-service/login/api
/
createNativeLoginFlow
). 2. On successful login, Ory issues a session token (or a token exchange code, depending on how you configure the flow). 3. You store that token on the device and then use
toSession({ xSessionToken })
as shown above to authenticate subsequent calls.[Browser vs native apps](https://www.ory.com/docs/identities/native-browser)[Using Ory Session Token](https://www.ory.com/docs/kratos/session-management/overview#using-ory-session-token) But the exact JS login call that returns the token is not present in the knowledge sources, so I can’t provide a concrete implementation snippet for that step.
q
@chilly-king-10285 how do I login with social sign in with react native
c
A message from kapa.ai
You can do social sign-in from React Native in two main ways, depending on whether you want to: 1. Use the standard OIDC browser flow (opens a browser tab and comes back), or 2. Use the native provider SDK (e.g. Google SDK) and then exchange the
id_token
at Ory. Below are both approaches as documented. --- ## 1. Native app social sign-in via OIDC (browser-based) This is the “generic” way and works for Google, Microsoft, etc. The flow is: 1. Create a login flow of type
api
with session token exchange code enabled
:
Copy code
ts    // React Native / TypeScript    import { Configuration, FrontendApi } from "@ory/client"     const ory = new FrontendApi(      new Configuration({        basePath: "https://<your-project>.<http://projects.oryapis.com|projects.oryapis.com>",      }),    )     const { data: flow } = await ory.createNativeLoginFlow({      return_session_token_exchange_code: true,      return_to: "<http://localhost:19006/Callback>", // your app callback URL    })
This is step 1–5 in the native social sign-in docs.[[Native social sign-in impl](https://www.ory.com/docs/kratos/social-signin/native-apps#implementation)] 2. Submit the flow with the chosen social provider (e.g. Google) using the
oidc
method. If Ory decides the user must do social sign-in, it will respond with
422
and a
redirect_browser_to
URL:
Copy code
ts    // Pseudocode – submit the form for provider "google-..."    const res = await ory.updateLoginFlow({      flow: flow.id,      updateLoginFlowBody: {        method: "oidc",        provider: "google-<id>",      },    })     // If status 422, read res.data.redirect_browser_to
The docs show this pattern and then use
WebBrowser.openAuthSessionAsync
to open the provider URL.[[Native social sign-in impl](https://www.ory.com/docs/kratos/social-signin/native-apps#implementation)] 3. Open the provider URL in a browser tab and wait for the redirect back to your app:
Copy code
ts    import * as WebBrowser from "expo-web-browser"     const result = await WebBrowser.openAuthSessionAsync(      res.data.redirect_browser_to,      "<http://localhost:19006/Callback>", // same as return_to    )
[[Native social sign-in impl](https://www.ory.com/docs/kratos/social-signin/native-apps#implementation)] 4. Handle the callback in your app (e.g. a
/Callback
screen). The callback URL contains the second part of the session token exchange code (
code
query param). Combine it with the first part from step 1 and exchange it for a session token, then store that token securely (e.g. SecureStore).[[Native social sign-in impl](https://www.ory.com/docs/kratos/social-signin/native-apps#implementation)] This is the flow described in “Social sign-in for native and mobile apps”.[[Native social sign-in overview](https://www.ory.com/docs/kratos/social-signin/native-apps)] --- ## 2. Using the Google SDK directly (no browser) For Google specifically, Ory supports using the Google SDK on native apps: 1. Configure a Google social sign-in provider in Ory with the same
client_id
as your native app.[[Google SDK native](https://www.ory.com/docs/kratos/social-signin/google#using-the-google-sdk-on-native-apps)] 2. In React Native (example is Flutter in docs, but the pattern is the same), use the Google SDK to get an `id_token`:
Copy code
ts    // Pseudocode in TS, analogous to the Flutter example    const account = await googleSignIn.signIn()    const auth = await account.authentication    const idToken = auth.idToken
3. Submit that
id_token
to Ory as part of the login flow using the
oidc
method:
Copy code
ts    import {      FrontendApi,      Configuration,      UpdateLoginFlowWithOidcMethod,      UpdateLoginFlowBody,    } from "@ory/client"     const ory = new FrontendApi(      new Configuration({        basePath: "https://<your-project>.<http://projects.oryapis.com|projects.oryapis.com>",      }),    )     const body: UpdateLoginFlowWithOidcMethod = {      id_token: idToken,      method: "oidc",      provider: "google",    }     await ory.updateLoginFlow({      flow: flowId,      updateLoginFlowBody: { oneOf: body } as unknown as UpdateLoginFlowBody,    })
This mirrors the Flutter example in the docs, where they call
updateRegistrationFlow
with `UpdateRegistrationFlowWithOidcMethod`; for login you use
updateLoginFlow
instead.[[Google SDK native example](https://www.ory.com/docs/kratos/social-signin/google#using-the-google-sdk-on-native-apps)] Ory validates the
id_token
against Google’s keys and creates a session (and identity) accordingly.[[Google SDK native](https://www.ory.com/docs/kratos/social-signin/google#using-the-google-sdk-on-native-apps)] --- ### Which should you use? - Use approach 1 (OIDC via browser) if you want a single pattern that works for multiple providers (Google, Microsoft, etc.).[[Native social sign-in overview](https://www.ory.com/docs/kratos/social-signin/native-apps)] - Use approach 2 (Google SDK) if you want a more “native” Google UX and are okay with a provider-specific integration.[[Google SDK native](https://www.ory.com/docs/kratos/social-signin/google#using-the-google-sdk-on-native-apps)] Both approaches end with you having an Ory session token for the user, which you then use in the app (e.g. via
toSession({ xSessionToken })
).[[Using Ory Session Token](https://www.ory.com/docs/kratos/session-management/overview#using-ory-session-token)]
q
@chilly-king-10285 is there a native approach with microsoft?
c
A message from kapa.ai
The knowledge sources you provided only describe a native‑SDK flow for Google (using the Google SDK and sending an
id_token
to Ory).[[Google native](https://www.ory.com/docs/kratos/social-signin/google#using-the-google-sdk-on-native-apps)] I’m not seeing any equivalent documentation for a Microsoft-specific native SDK flow (like the Google one) in these sources. What is documented for Microsoft is the standard OIDC/social sign‑in setup (tenant selection, scopes, data mapping, subject source), which assumes the usual browser-based OIDC redirect flow, not a native SDK exchange.[[Microsoft social sign-in](https://www.ory.com/docs/kratos/social-signin/microsoft); [Subject source](https://www.ory.com/docs/kratos/social-signin/microsoft#subject-identifier-source)] So, based on the available docs: - A native “SDK + id_token exchange” pattern is documented only for Google. - For Microsoft, the documented path is the regular OIDC browser flow (which you can still use from React Native via an in-app browser / auth session), not a Microsoft-native SDK flow. If you need a Microsoft-native approach analogous to the Google SDK flow, it’s not described in the knowledge sources you shared.
q
@chilly-king-10285 I get a session toke with
Copy code
const { data: flow } = await ory.createNativeLoginFlow();

console.log('logging in')
const { data: result } = await ory.updateLoginFlow({
    flow: flow.id,
    updateLoginFlowBody: {
        method: 'password',
        identifier: email,
        password,
    },
});


const sessionToken = (result as any)?.session_token;
How do I check the session validity with a rest call?I'm currently calling with /sessions/whoami and the authorization header but it I get 401
c
A message from kapa.ai
You’re on the right track: for a native login flow you get a
session_token
, and you validate it with
GET /sessions/whoami
using that token. The 401 means the token is either not being sent in a supported way, or is invalid/expired. According to the docs,
/sessions/whoami
accepts the session token in one of three places: [[toSession ref](https://www.ory.sh/docs/reference/api#tag/frontend/operation/toSession); [Check session token vs cookie](https://www.ory.com/docs/identities/sign-in/check-session-token-cookie-api)] -
Authorization: Bearer <session-token>
-
X-Session-Token: <session-token>
-
Cookie: ory_kratos_session=...
(for browser cookies) For a session token from a native flow, use either
Authorization
or
X-Session-Token
. ### Correct REST call Using `Authorization`:
Copy code
bash curl 'https://$<http://PROJECT_SLUG.projects.oryapis.com/sessions/whoami|PROJECT_SLUG.projects.oryapis.com/sessions/whoami>' \   -H 'Accept: application/json' \   -H 'Authorization: Bearer YOUR_SESSION_TOKEN_HERE'
This is exactly how the docs show checking a session token: [[Using Ory Session Token](https://www.ory.com/docs/kratos/session-management/overview#using-ory-session-token); [Check session status](https://www.ory.com/docs/identities/sign-in/check-session-token-cookie-api)] Or using `X-Session-Token`:
Copy code
bash curl 'https://$<http://PROJECT_SLUG.projects.oryapis.com/sessions/whoami|PROJECT_SLUG.projects.oryapis.com/sessions/whoami>' \   -H 'Accept: application/json' \   -H 'X-Session-Token: YOUR_SESSION_TOKEN_HERE'
If you still get 401: - Double‑check you’re calling the project URL, not the tunnel, when using the token (tokens are for the project API, cookies for the tunnel/browser). [[Session token TS example](https://www.ory.com/docs/kratos/session-management/overview#using-ory-session-token)] - Make sure you’re using the exact
session_token
from the successful
updateLoginFlow
response and not a different field. - Confirm the token hasn’t expired (session lifespan / revocation). If you want to do this via the JS client instead of raw REST:
Copy code
ts import { Configuration, FrontendApi } from "@ory/client"  const ory = new FrontendApi(   new Configuration({     basePath: `https://${process.env.ORY_PROJECT_SLUG}.<http://projects.oryapis.com|projects.oryapis.com>`,   }), )  const { data: session } = await ory.toSession({   xSessionToken: sessionToken, })
[[Using Ory Session Token](https://www.ory.com/docs/kratos/session-management/overview#using-ory-session-token)]
q
@chilly-king-10285 the social sign in with google and microsoft are not very seemless on mobile app. What is a good practice to prevent this while keeping the account safe. The customer wants login with social sign in enforced
c
A message from kapa.ai
The knowledge sources don’t describe UX “best practices” for enforcing social sign‑in on mobile beyond the two supported technical patterns: 1. Standard OIDC browser flow from native apps This is the documented, provider‑agnostic way for Google, Microsoft, etc.: - Your app initializes a native login flow with
return_session_token_exchange_code=true
. [[Native social sign-in impl](https://www.ory.com/docs/kratos/social-signin/native-apps#implementation)] - When the user chooses Google/Microsoft, you submit the flow with
method: "oidc"
and the provider ID. Ory responds with
422
and
redirect_browser_to
. [[Native social sign-in impl](https://www.ory.com/docs/kratos/social-signin/native-apps#implementation)] - You open that URL in an in‑app browser/auth session (e.g.
WebBrowser.openAuthSessionAsync
in React Native). [[Native social sign-in impl](https://www.ory.com/docs/kratos/social-signin/native-apps#implementation)] - After the provider redirects back, you exchange the session token exchange code for an Ory session token, store it securely, and use it with
toSession({ xSessionToken })
. [[Native social sign-in impl](https://www.ory.com/docs/kratos/social-signin/native-apps#implementation); [Session token usage](https://www.ory.com/docs/kratos/session-management/overview#using-ory-session-token)] This keeps accounts safe (full OIDC flow, same security as web) and is the recommended generic approach for native apps, but it will always involve a browser step. 2. Provider‑specific native SDKs (only documented for Google) For Google, there is a more seamless native UX: - Use the Google SDK in the app to obtain an
id_token
. - Submit that
id_token
(and optional nonce) to Ory via
updateLoginFlow
/
updateRegistrationFlow
with
method: "oidc", provider: "google"
. - Ory validates the token against Google’s keys and creates the identity/session. [[Google native SDK](https://www.ory.com/docs/kratos/social-signin/google#using-the-google-sdk-on-native-apps)] This avoids the browser step, but the docs only describe it for Google, not Microsoft. Given the sources, if the customer wants social sign‑in enforced and you want to keep accounts safe on mobile: - Use the native OIDC flow with an in‑app browser/auth session for both Google and Microsoft, as described in the “Social sign-in for native and mobile apps” guide. This is the documented, secure pattern for enforcing social sign‑in on mobile. [[Native social sign-in overview](https://www.ory.com/docs/kratos/social-signin/native-apps)] - Optionally, for Google only, you can improve UX further by using the Google native SDK + id_token flow, while still relying on Ory for validation and session issuance. [[Google native SDK](https://www.ory.com/docs/kratos/social-signin/google#using-the-google-sdk-on-native-apps)] The knowledge sources do not describe an equivalent native‑SDK pattern for Microsoft, nor alternative “more seamless” but still enforced approaches beyond these two.
q
@chilly-king-10285 Is there a Face ID mechanism?
c
A message from kapa.ai
There isn’t a dedicated “Face ID API” in Ory, but you can use Face ID (and similar biometrics) via WebAuthn / Passkeys in browser-based flows: - WebAuthn supports on-device authenticators such as Touch ID, Face ID, Windows Hello, and Android Biometric Authentication.[\[WebAuthn MFA](https://www.ory.com/docs/kratos/mfa/webauthn-fido-yubikey)\] - Ory exposes this through: - Passkeys / passwordless login (recommended, modern UX).[\[Passkeys overview](https://www.ory.com/docs/kratos/passwordless/passkeys)\] - WebAuthn MFA (second factor).[\[WebAuthn MFA](https://www.ory.com/docs/kratos/mfa/webauthn-fido-yubikey)\] Important constraints: - WebAuthn is a **browser-only standard**; it does not work in native mobile apps directly.[\[WebAuthn limitations](https://www.ory.com/docs/kratos/mfa/webauthn-fido-yubikey#limitations)\] - For native apps (e.g. React Native, Flutter), you currently can’t use Ory’s WebAuthn/Passkeys directly; you’d need to go through a browser-based flow. So: - Yes: you can let users authenticate with Face ID (and other biometrics) when using Ory in a browser via Passkeys/WebAuthn. - No native-only Face ID mechanism is documented for mobile apps in the provided sources.
q
@chilly-king-10285 when I try to start a login flow with body
Copy code
{
  method: "oidc",
  provider: "microsoft",
}
I get:
Copy code
LOG  res {"_bodyBlob": {"_data": {"__collector": [Object], "blobId": "64AB9244-01F2-46F2-8DF4-A1D1E5E542D2", "name": "login.json", "offset": 0, "size": 1763, "type": "application/json"}}, "_bodyInit": {"_data": {"__collector": [Object], "blobId": "64AB9244-01F2-46F2-8DF4-A1D1E5E542D2", "name": "login.json", "offset": 0, "size": 1763, "type": "application/json"}}, "bodyUsed": true, "headers": {"map": {"alt-svc": "h3=\":443\"; ma=86400", "cache-control": "private, no-cache, no-store, must-revalidate", "cf-cache-status": "DYNAMIC", "cf-ray": "9b76f462fd95c82c-TPA", "content-length": "1763", "content-type": "application/json; charset=utf-8", "date": "Fri, 02 Jan 2026 02:51:26 GMT", "ory-network-ingress": "T", "ory-network-region": "euw", "permissions-policy": "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), usb=()", "referrer-policy": "strict-origin-when-cross-origin", "server": "cloudflare", "set-cookie": "__cf_bm=eHONEaX4QsuytY4b00hNwK3RyDdErfxs2d7aEqZMO_M-1767322286.558539-1.0.1.1-VDF9d5upBpkH_45T0sWf2_JinwrHObRihsX9n9IaTVct0nYpMlQrRBgH2DekF.Efv4sBiTerVsnFQhJJWz8u8rR9x8aWNmgN0BvJyhVvsivJVBBkZA0WVtiQ9pMCI.ZV; Path=/; Expires=Fri, 02 Jan 2026 03:21:26 GMT; HttpOnly; SameSite=Lax", "strict-transport-security": "max-age=0;", "vary": "Origin, Origin,Cookie", "via": "1.1 google", "x-content-type-options": "nosniff", "x-frame-options": "DENY"}}, "ok": false, "status": 400, "statusText": "", "type": "default", "url": "<http://localhost:4000/self-service/login?flow=47b61e16-211a-4541-b314-a16e679af4bc>"}
c
A message from kapa.ai
The knowledge sources you provided don’t include the error body, only the 400 status wrapper. To understand why Ory rejected the request, you need to read and inspect the JSON error payload in the response body. For native / mobile social sign‑in, the documented pattern is: 1. Create a native login flow (
type: api
):
Copy code
ts    const { data: flow } = await ory.createNativeLoginFlow({      return_session_token_exchange_code: true,      // optionally: return_to: "<your-app://callback>"    })
This is step 1–5 in the native social sign‑in docs.[[Native social sign-in impl](https://www.ory.com/docs/kratos/social-signin/native-apps#implementation)] 2. **Submit the flow with
method: "oidc"
and the *provider ID from the node***: In the flow you get back, the Microsoft node looks like:
Copy code
json    {      "group": "oidc",      "attributes": {        "name": "provider",        "type": "submit",        "value": "microsoft-3csX8ovp",        ...      },      "meta": {        "label": {          "context": {            "provider": "Microsoft",            "provider_id": "microsoft-3csX8ovp"          }        }      }    }
You must use that
value
/
provider_id
(e.g.
"microsoft-3csX8ovp"
), not just
"microsoft"
, when you call
updateLoginFlow
.[[Login with OIDC](https://www.ory.com/docs/kratos/self-service/flows/user-login#login-with-google-facebook-github--openid-connect--oauth-20)] So the body should be:
Copy code
ts    await ory.updateLoginFlow({      flow: flow.id,      updateLoginFlowBody: {        method: "oidc",        provider: "microsoft-3csX8ovp", // from node.attributes.value      },    })
3. Handle the 422 with
redirect_browser_to
: For native apps, when Ory needs to send the user to the IdP, it responds with
422
and a JSON body containing
redirect_browser_to
. You then open that URL in an in‑app browser/auth session (e.g.
WebBrowser.openAuthSessionAsync
in React Native), and later exchange the session token exchange code.[[Native social sign-in impl](https://www.ory.com/docs/kratos/social-signin/native-apps#implementation)] --- So for your specific 400: - The most likely issue (based on the docs) is that
provider: "microsoft"
does not match any configured provider ID; you need to use the exact ID from the
oidc
node (
attributes.value
/
meta.label.context.provider_id
). - To be sure, log
await res.json()
on that 400 response; the JSON will contain an
error
object with
error.id
and
error.reason
explaining the problem (e.g. unknown provider, missing field, wrong flow type). The knowledge sources don’t include that exact error text, so you need to inspect it at runtime.
q
@chilly-king-10285 I did POST http://localhost:4000/self-service/login?flow=41cc5edd-fb66-4c75-9280-db7e227bcb55 with body
Copy code
{
  "method": "oidc",
  "provider": "microsoft"
}
and I get
Copy code
{
  "id": "41cc5edd-fb66-4c75-9280-db7e227bcb55",
  "organization_id": null,
  "type": "api",
  "expires_at": "2026-01-02T03:56:12.437761Z",
  "issued_at": "2026-01-02T02:56:12.437761Z",
  "request_url": "<http://localhost:4000/self-service/login/api?return_to=http%3A%2F%2Flocalhost%3A19006%2Fms-callback&return_session_token_exchange_code=true>",
  "return_to": "<http://localhost:19006/ms-callback>",
  "ui": {
    "action": "<http://localhost:4000/self-service/login?flow=41cc5edd-fb66-4c75-9280-db7e227bcb55>",
    "method": "POST",
    "nodes": [
      {
        "type": "input",
        "group": "identifier_first",
        "attributes": {
          "name": "identifier",
          "type": "text",
          "value": "",
          "required": true,
          "autocomplete": "username webauthn",
          "disabled": false,
          "node_type": "input"
        },
        "messages": [
          {
            "id": 4000037,
            "text": "This account does not exist or has no login method configured.",
            "type": "error"
          }
        ],
        "meta": {
          "label": {
            "id": 1070002,
            "text": "E-Mail",
            "type": "info",
            "context": {
              "name": "traits.email",
              "title": "E-Mail"
            }
          }
        }
      },
      {
        "type": "input",
        "group": "identifier_first",
        "attributes": {
          "name": "method",
          "type": "submit",
          "value": "identifier_first",
          "disabled": false,
          "node_type": "input"
        },
        "messages": [],
        "meta": {
          "label": {
            "id": 1070009,
            "text": "Continue",
            "type": "info"
          }
        }
      },
      {
        "type": "input",
        "group": "oidc",
        "attributes": {
          "name": "provider",
          "type": "submit",
          "value": "google-9NrXo5Lo",
          "disabled": false,
          "node_type": "input"
        },
        "messages": [],
        "meta": {
          "label": {
            "id": 1010002,
            "text": "Sign in with Google",
            "type": "info",
            "context": {
              "provider": "Google",
              "provider_id": "google-9NrXo5Lo"
            }
          }
        }
      },
      {
        "type": "input",
        "group": "default",
        "attributes": {
          "name": "csrf_token",
          "type": "hidden",
          "value": "",
          "required": true,
          "disabled": false,
          "node_type": "input"
        },
        "messages": [],
        "meta": {}
      }
    ]
  },
  "created_at": "2026-01-02T02:56:13Z",
  "updated_at": "2026-01-02T02:56:13Z",
  "refresh": false,
  "requested_aal": "aal1",
  "state": "choose_method"
}
What am I doign wrong? I haven't chosen an account yet
c
A message from kapa.ai
You’re not doing anything “wrong” in the sense of calling the wrong endpoint – the response is telling you exactly what’s happening:
Copy code
json "messages": [   {     "id": 4000037,     "text": "This account does not exist or has no login method configured.",     "type": "error"   } ]
For API/native flows, Ory returns validation errors by updating the flow and putting error messages on the relevant nodes (here on the
identifier
node).[[Login form validation](https://www.ory.com/docs/kratos/self-service/flows/user-login#login-form-validation); [API flows validation](https://www.ory.com/docs/kratos/self-service#form-submission-and-payload-validation-2)] Key points from your payload: -
type: "api"
→ this is a native/API login flow, not a browser flow. -
state: "choose_method"
and
group: "identifier_first"
→ you’re in the identifier‑first step. - The error message is attached to the
identifier
node, not to OIDC. That specific message means: > “This account does not exist or has no login method configured.” In identifier‑first login, Ory first looks up the account by identifier (email) and then decides which login methods are available for that identity (password, OIDC, etc.). If there is no identity for that email, or the identity has no usable login method, you get exactly this error on the identifier field.[[Login form validation](https://www.ory.com/docs/kratos/self-service/flows/user-login#login-form-validation)] In your example you POSTed:
Copy code
json {"method":"oidc","provider":"microsoft"}
but the returned flow: - still has
group: "identifier_first"
with an error, - only shows a Google OIDC node (
value: "google-9NrXo5Lo"
), - has no Microsoft OIDC node at all. So two things are going on: 1. Microsoft is not present in this flow The
ui.nodes
only contain a Google OIDC provider. That means, for this project/flow, only Google is configured/available; there is no
provider: "microsoft-..."
node to submit. For OIDC you must always use the provider ID from the node’s
attributes.value
(e.g.
"google-9NrXo5Lo"
), not a bare `"google"`/`"microsoft"` string.[[Login with OIDC](https://www.ory.com/docs/kratos/self-service/flows/user-login#login-with-google-facebook-github--openid-connect--oauth-20)] 2. The identifier‑first step is failing before any OIDC redirect Because the identifier step is active and has an error, Ory is not starting any OIDC flow yet. You haven’t provided a valid identifier that maps to an identity with a configured login method, so you get
4000037
on the identifier node. Given the docs and the flow you posted, the correct sequence for an API/native identifier‑first login is: 1. Step 1 – identifier Submit:
Copy code
json    {      "method": "identifier_first",      "identifier": "<user email>"    }
to the flow’s
ui.action
. If the email belongs to an identity that has a login method (password or OIDC), Ory will move the flow forward; otherwise you get the
4000037
error on the identifier node.[[Identifier-first authentication](https://www.ory.com/docs/identities/sign-in/identifier-first-authentication)] 2. Step 2 – choose method (password / OIDC) Once the identifier is accepted and the identity has OIDC configured, the flow will include the relevant
oidc
node(s) for that identity, and you can then submit:
Copy code
json    {      "method": "oidc",      "provider": "<provider-id-from-node>"    }
where
<provider-id-from-node>
is the exact
attributes.value
from the
oidc
node (e.g.
"google-9NrXo5Lo"
).[[Login with OIDC](https://www.ory.com/docs/kratos/self-service/flows/user-login#login-with-google-facebook-github--openid-connect--oauth-20)] Right now, because: - there is no Microsoft provider node in the flow, and - the identifier you’re using does not resolve to an identity with a configured login method, Ory correctly returns the
4000037
error and keeps you in
state: "choose_method"
with the identifier field in error. To move forward you need to: - Ensure the identity you’re testing with actually exists and has a login method configured (password or OIDC), and - For OIDC, only submit providers that are present in the flow (
oidc
nodes’
attributes.value
), not a bare
"microsoft"
string.
q
@chilly-king-10285 one of my users has App Protection Policies which mean can only sign in via edge when doign mobile app login. The problme is that with iOS it's not possible. Is there an option to use microsoft authentcation library and brokered authentcation with ory?
c
I’m not seeing anything in the provided knowledge sources that mentions: • Using the Microsoft Authentication Library (MSAL) directly with Ory, or • Supporting brokered authentication (e.g. Edge / Authenticator app enforcement via Intune App Protection Policies) as a first‑class integration. The documented options for Microsoft with Ory are: • Standard OIDC social sign‑in using Microsoft Entra ID (Azure AD) configured as an OIDC provider in Ory, which assumes a normal browser-based OIDC flow.Microsoft social sign-in • For native/mobile apps, the generic pattern is: ◦ Use Ory’s native login flow (
/self-service/login/api
) and ◦ When the user chooses Microsoft, Ory returns a
redirect_browser_to
URL that you open in an in-app browser/auth session (Custom Tabs / ASWebAuthenticationSession), then you exchange the session token exchange code.Social sign-in for native apps There is no mention in these docs of: • A Microsoft‑specific native SDK flow (like the documented Google SDK +
id_token
pattern), or • A way to plug MSAL’s brokered authentication (Edge/Authenticator) directly into Ory as a supported, documented flow. So based on the available documentation: • Ory supports Microsoft social sign‑in via standard OIDC and the native OIDC flow for mobile (using an in‑app browser), • But there is no documented option to use MSAL with brokered authentication as a first‑class integration with Ory. 👍 0 button 👎 0 button ℹ️ Tip: Mention @kapa.ai in the thread for follow-ups or DM the app for 1-to-1 help.
q
@chilly-king-10285 is there no way to do social sign in, in app?
c
Yes, you can do social sign-in “in app” for mobile, but it’s done via Ory’s native social sign-in flow, which still uses a browser tab or auth session under the hood. Ory’s documented pattern for mobile/native apps is: • Use the native login flow (
/self-service/login/api
, via
createNativeLoginFlow
) with
return_session_token_exchange_code=true
. • When the user taps “Sign in with Google/Microsoft”, you submit the flow with
method: "oidc"
and the provider from the
oidc
node. • Ory responds with
422
and a
redirect_browser_to
URL. You open this URL in an in-app browser/auth session (ASWebAuthenticationSession on iOS, Custom Tabs on Android, or
WebBrowser.openAuthSessionAsync
in React Native). • After the provider redirects back, your app receives the callback, exchanges the session token exchange code for an Ory session token, and stores that token for API calls. [Native social sign-in overview; Native social sign-in implementation] So: • You do not have to send users to your web UI; the whole flow is integrated into your mobile app. • But the actual provider login still happens in a browser context (as required by OIDC), opened from your app. For Google specifically, there is also a more “native” option using the Google SDK to get an
id_token
and then submitting that to Ory, but that pattern is only documented for Google, not Microsoft. [Google native SDK] 👍 0 button 👎 0 button ℹ️ Tip: Mention @kapa.ai in the thread for follow-ups or DM the app for 1-to-1 help.
q
@chilly-king-10285 is there no mechanism to achieve social sign in login with the microsoft authenticator? The browser is not a good option because it has to be on edge which ios doesn't allow
c
The knowledge sources you provided do not describe any integration between Ory and the Microsoft Authentication Library (MSAL) or Microsoft Authenticator / brokered authentication (the Intune/Edge/Authenticator flow). There is no documented mechanism to have Ory accept tokens coming from MSAL / brokered auth the way it does for the Google native SDK flow. What is documented for Microsoft is: • Standard OIDC social sign‑in via Microsoft Entra ID, which assumes a browser-based OIDC redirect flow (works in mobile apps via an in‑app browser/auth session). [Microsoft social sign-in] • For native/mobile apps, the generic pattern is: ◦ Use the native login flow (
/self-service/login/api
). ◦ When the user chooses Microsoft, Ory returns a
redirect_browser_to
URL. ◦ You open that URL in an in‑app browser/auth session (ASWebAuthenticationSession / Custom Tabs / React Native WebBrowser). ◦ After redirect back, you exchange the session token exchange code for an Ory session token. [Social sign-in for native apps] There is a provider‑specific native SDK flow only for Google, where you obtain an
id_token
via the Google SDK and send it to Ory; there is no equivalent documented for Microsoft. [Google native SDK] So, based on the available docs: • Ory supports Microsoft social sign‑in via standard OIDC, including from mobile apps using an in‑app browser. • There is no documented way to use Microsoft Authenticator / MSAL brokered authentication as a native, in‑app social sign‑in mechanism with Ory. 👍 0 button 👎 0 button ℹ️ Tip: Mention @kapa.ai in the thread for follow-ups or DM the app for 1-to-1 help.
q
@chilly-king-10285 how do I configure invite link expiry time?
c
You configure the expiry time of an invite link per request using the
expires_in
field when calling the recovery link endpoint:
Copy code
curl --request POST -sL \
  --header "Authorization: Bearer {ORY_API_KEY}" \
  --header "Content-Type: application/json" \
  --data '{
    "expires_in": "12h",
    "identity_id": "$ACCOUNT_ID"
  }' https://{project-slug}.projects.oryapis.com/admin/recovery/link
• Set
expires_in
to any duration (e.g.
"12h"
,
"48h"
,
"7d"
). The pattern must match
^[0-9]+(ns|us|ms|s|m|h)$
. • If you omit
expires_in
, it defaults to the value configured in
selfservice.methods.code.config.lifespan
(default: 1 hour). [Invite users; createRecoveryLinkForIdentity] To change the global default expiry for all recovery/invite links, update your Identities config:
Copy code
selfservice:
  methods:
    code:
      enabled: true
      config:
        lifespan: 24h  # default is 1h
[Edit recovery configuration; Recovery flow config] 👍 0 button 👎 0 button ℹ️ Tip: Mention @kapa.ai in the thread for follow-ups or DM the app for 1-to-1 help.
q
I want to change a user's email trait. Can I do it with the api without their interventin?
@chilly-king-10285 I want to change a user's email trait. Can I do it with the api without their interventin?
c
Yes, you can update a user's email trait without their intervention using the Admin Identity API. There are two approaches: 1. Full update with PUT /admin/identities/{id} (updateIdentity) Replace the entire identity (including traits):
Copy code
curl --request PUT \
  --header "Authorization: Bearer $ORY_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "schema_id": "default",
    "traits": {
      "email": "[email protected]"
    },
    "state": "active"
  }' \
  "https://$ORY_SLUG.projects.oryapis.com/admin/identities/$IDENTITY_ID"
Note: this requires sending the full traits object (not just the changed field), otherwise you'll overwrite other traits with empty values. [updateIdentity] 2. Partial update with PATCH /admin/identities/{id} (patchIdentity) — recommended Use JSON Patch to update only the email trait without touching anything else:
Copy code
curl --request PATCH \
  --header "Authorization: Bearer $ORY_API_KEY" \
  --header "Content-Type: application/json" \
  --data '[
    {
      "op": "replace",
      "path": "/traits/email",
      "value": "[email protected]"
    }
  ]' \
  "https://$ORY_SLUG.projects.oryapis.com/admin/identities/$IDENTITY_ID"
This is the recommended approach as it only changes the specific field you want. [patchIdentity; Update identities to use a new schema] Both endpoints require an API key with admin access. [Identity metadata & traits] 👍 0 button 👎 0 button ℹ️ Tip: Mention @kapa.ai in the thread for follow-ups or DM the app for 1-to-1 help.
q
@chilly-king-10285 nodes are not being loaded for a customer in the login page. How can I debug this?
c
The knowledge sources don't describe a specific "nodes not loading" error, but based on the documented flow structure and common issues, here are the things to check: 1. Check if the flow is being created correctly The login flow must be initialized first before nodes are returned. Verify the flow creation response contains a
ui.nodes
array:
Copy code
curl -X GET \
    -H 'Content-Type: application/json' \
    -H 'Accept: application/json' \
    -c cookies.txt \
    https://$PROJECT_SLUG.projects.oryapis.com/self-service/login/browser
If the response has an empty
nodes: []
, the flow was created but no methods are configured. [Create login flow] 2. Check if the flow ID is being passed correctly When your UI loads, it should read the
?flow=
query parameter and call:
Copy code
GET /self-service/login/flows?id=<flow-id>
If the flow ID is missing or wrong, the flow fetch will fail and no nodes will be returned. [Get login flow] 3. Check for CSRF cookie issues For browser flows, calling the flow endpoint without the correct CSRF cookies will result in a failure and potentially empty/missing nodes:
Copy code
curl -X GET \
    --cookie-jar $cookieJar --cookie $cookieJar \
    -H "Accept: application/json" \
    "https://$PROJECT_SLUG.projects.oryapis.com/self-service/login/flows?id=$flowId"
Make sure cookies are being sent with the request. [Form rendering] 4. Check for CORS errors If the customer is on a different domain, CORS could be blocking the response. Ory has a "deny by default" CORS policy — only domains added to your project will have
Access-Control-Allow-Origin
set. [CORS debug] • For local dev: use Ory Tunnel. • For production: add the customer's domain to your Ory project under Developers → CORS. 5. Check if the correct methods are enabled Nodes are grouped by method (
password
,
oidc
,
webauthn
, etc.). If a method is disabled in your project config, its nodes won't appear. Check your project's Authentication → Methods settings in the Ory Console. 6. Check your node filtering code If you're using
filterNodesByGroups
, make sure you're including all the groups you need:
Copy code
filterNodesByGroups({
  nodes: flow.ui.nodes,
  groups: ["default", "password"], // make sure the right groups are listed
})
If
groups
is too restrictive, some nodes will be filtered out and not rendered. [Login flow rendering] To narrow it down, I'd recommend checking the network tab in the browser dev tools for that customer and looking at: • The response of
/self-service/login/flows?id=...
— does it contain
ui.nodes
? • Any
4xx
errors (401 = missing/invalid cookies, 403 = CORS/CSRF, 410 = expired flow, 400 = bad request). • Any CORS errors in the console. 👍 0 button 👎 0 button ℹ️ Tip: Mention @kapa.ai in the thread for follow-ups or DM the app for 1-to-1 help.
q
are there logs I can look at in ory? it works fine on other devices
@chilly-king-10285 are there logs I can look at in ory? it works fine on other devices
c
Yes, there are a few options for looking at logs depending on whether you're on Ory Network (cloud) or self-hosted: Self-hosted Logs are written to stdout/stderr and you can configure the log level and format: [Logs and audit trails]
Copy code
log:
  level: debug  # increase to debug or trace for more detail
  format: json
  leak_sensitive_values: false
Set
level: debug
or
level: trace
to get more detail. Each log entry includes HTTP request context (method, path, status, headers, remote IP, etc.) which would help identify what's different about that specific device/customer. [Log fields] Ory Network (cloud) The knowledge sources do not explicitly confirm a self-service log viewer in the Ory Console for all plans. However: • There is a GitHub issue requesting exactly this — visibility into logs/errors from the Console/API — which suggests it may not be fully available on all plans. [Improve error observability; Account activity observability] • If you have access to distributed tracing (Jaeger, Datadog, OpenTelemetry, etc.), you can correlate traces with logs to see exactly what happened for a specific request. [Distributed tracing] What to look for Given the issue is device-specific, the logs to focus on are: • HTTP request logs — check
remote
IP,
user-agent
, headers. • Flow fetch errors —
400
,
401
,
403
,
410
on
/self-service/login/flows?id=...
. • CSRF/cookie errors — these are often device/browser specific. If you're on Ory Network and don't have log access, I'd recommend reaching out to Ory support directly, as the knowledge sources suggest log visibility may be limited depending on your plan. 👍 0 button 👎 0 button ℹ️ Tip: Mention @kapa.ai in the thread for follow-ups or DM the app for 1-to-1 help.
q
@chilly-king-10285 can I integrate saml and oicd in mysetup?
c
Thinking completed Yes, you can integrate both SAML and OIDC in your setup [Example flow; SAML SSO Providers]. Here is how Ory supports using both together: 1. Through Ory Polis (Enterprise SSO) Ory Polis (acting as a SAML/OIDC Service Provider) supports configuring both SAML connections (such as Azure AD / Microsoft Entra, Okta, JumpCloud) and OpenID Connect (OIDC) connections side-by-side for different tenants or products [Example flow; SAML SSO Providers]. • Different Offerings / Tenants: For example, you can connect one offering or customer to a SAML Identity Provider and another to Google Workspace via OIDC [Example flow]. • SAML & OIDC Handlers: The application handles authorization requests using either protocol depending on the connection type configured [Single Sign-On Authentication]. • SAML & OIDC Federation: If needed, Ory Polis can also act as an IdP to federate multiple upstream Identity Providers via SAML or OIDC Federation Apps [SAML Federation]. 2. Through Ory Network (Organizations / B2B SSO) In Ory Network, enterprise SAML SSO is handled through the B2B Organizations feature [SAML]: • SAML support is provided via Ory Polis acting as the SAML Service Provider, as Ory Identities (Kratos) does not natively speak SAML [Generic SAML 2.0]. • You can import and manage identities that have both SAML and OIDC credentials associated with specific organizations [Organization-specific SAML and OIDC connections]. Note: SAML is available exclusively on select Enterprise plans [SAML]. 👍 0 button 👎 0 button ℹ️ Tip: Mention @kapa.ai in the thread for follow-ups or DM the app for 1-to-1 help.