quiet-painting-32952
02/02/2025, 4:21 PMdocker 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 loginchilly-king-10285
02/02/2025, 4:21 PMchilly-king-10285
02/02/2025, 4:21 PMory 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: 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.quiet-painting-32952
05/10/2025, 9:06 AMchilly-king-10285
05/10/2025, 9:07 AMchilly-king-10285
05/10/2025, 9:07 AMweb_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: 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!quiet-painting-32952
05/17/2025, 9:03 PMchilly-king-10285
05/17/2025, 9:03 PMchilly-king-10285
05/17/2025, 9:03 PMinactive, 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: 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).quiet-painting-32952
05/17/2025, 9:17 PMasync 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 questionchilly-king-10285
05/17/2025, 9:17 PMchilly-king-10285
05/17/2025, 9:17 PMpython 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.quiet-painting-32952
05/18/2025, 9:26 AMchilly-king-10285
05/18/2025, 9:26 AMchilly-king-10285
05/18/2025, 9:26 AMquiet-painting-32952
05/18/2025, 10:27 AMchilly-king-10285
05/18/2025, 10:27 AMchilly-king-10285
05/18/2025, 10:27 AMpassword 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: 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).quiet-painting-32952
05/18/2025, 10:31 AMexport const ory = new FrontendApi(
new Configuration({
basePath: Config.getAuthBasePath(),
baseOptions: {
withCredentials: true,
},
}),
);chilly-king-10285
05/18/2025, 10:32 AMchilly-king-10285
05/18/2025, 10:32 AMFrontendApi 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: js const flow = await ory.frontend.createBrowserSettingsFlow() .then(({ data }) => data) Example for a native app: 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: 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.quiet-painting-32952
05/18/2025, 10:59 AMconst flow = await ory.createBrowserSettingsFlow()
I get two nodes as a result of password group
{
"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?chilly-king-10285
05/18/2025, 10:59 AMchilly-king-10285
05/18/2025, 11:00 AMaction 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: 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): 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).quiet-painting-32952
05/18/2025, 11:05 AM{
"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"
}
}chilly-king-10285
05/18/2025, 11:05 AMchilly-king-10285
05/18/2025, 11:05 AMcsrf_token. - You must extract this value from the flow and include it in your payload when submitting the password change. 2. Example: 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.quiet-painting-32952
05/18/2025, 11:13 AMawait 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 403chilly-king-10285
05/18/2025, 11:13 AMchilly-king-10285
05/18/2025, 11:14 AMattributes.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: 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)quiet-painting-32952
05/18/2025, 11:20 AMchilly-king-10285
05/18/2025, 11:20 AMchilly-king-10285
05/18/2025, 11:20 AMyaml 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: 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: 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!quiet-painting-32952
05/18/2025, 11:23 AMquiet-painting-32952
05/18/2025, 11:23 AMchilly-king-10285
05/18/2025, 11:23 AMchilly-king-10285
05/18/2025, 11:23 AMbash 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: yaml selfservice: methods: totp: config: issuer: <http://ExampleIssuer.com|ExampleIssuer.com> enabled: true lookup_secret: enabled: true 3. Update your configuration: 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/).quiet-painting-32952
05/18/2025, 11:29 AMchilly-king-10285
05/18/2025, 11:29 AMchilly-king-10285
05/18/2025, 11:29 AMrequired_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: 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.quiet-painting-32952
05/18/2025, 11:31 AMrequired_aal to highest_available (not aal2), for example in the settings flow:chilly-king-10285
05/18/2025, 11:31 AMchilly-king-10285
05/18/2025, 11:32 AMrequired_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: 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: yaml selfservice: flows: settings: required_aal: highest_available 3. Upload the updated configuration: 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).quiet-painting-32952
05/18/2025, 11:38 AMory get identity-config
The requested action was forbidden
I have the right ORY_PROJECT_API_KEY and ORY_SDK_URL configuredchilly-king-10285
05/18/2025, 11:38 AMchilly-king-10285
05/18/2025, 11:38 AMThe 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: 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).quiet-painting-32952
05/18/2025, 11:46 AMchilly-king-10285
05/18/2025, 11:46 AMchilly-king-10285
05/18/2025, 11:46 AMrequired_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/).quiet-painting-32952
05/18/2025, 11:47 AMchilly-king-10285
05/18/2025, 11:47 AMchilly-king-10285
05/18/2025, 11:48 AMrequired_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.quiet-painting-32952
05/18/2025, 11:50 AMchilly-king-10285
05/18/2025, 11:50 AMchilly-king-10285
05/18/2025, 11:50 AMrequired_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.quiet-painting-32952
05/18/2025, 12:01 PMclients:
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
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.chilly-king-10285
05/18/2025, 12:01 PMchilly-king-10285
05/18/2025, 12:01 PMrequired_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.quiet-painting-32952
05/18/2025, 12:20 PMory.createBrowserSettingsFlow()
[
{
"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 userchilly-king-10285
05/18/2025, 12:20 PMchilly-king-10285
05/18/2025, 12:21 PMjson { "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.quiet-painting-32952
05/18/2025, 12:22 PMchilly-king-10285
05/18/2025, 12:22 PMchilly-king-10285
05/18/2025, 12:23 PMtotp.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.quiet-painting-32952
07/01/2025, 10:31 PMchilly-king-10285
07/01/2025, 10:31 PMchilly-king-10285
07/01/2025, 10:32 PM<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.quiet-painting-32952
08/06/2025, 7:39 AMchilly-king-10285
08/06/2025, 7:39 AMchilly-king-10285
08/06/2025, 7:39 AMexpires_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.quiet-painting-32952
08/06/2025, 10:22 AMchilly-king-10285
08/06/2025, 10:22 AMchilly-king-10285
08/06/2025, 10:22 AMchilly-king-10285
08/06/2025, 10:22 AMfrom_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).chilly-king-10285
08/06/2025, 10:22 AMfrom_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.quiet-painting-32952
08/06/2025, 10:24 AMchilly-king-10285
08/06/2025, 10:24 AMchilly-king-10285
08/06/2025, 10:24 AMconnection_uri in your Ory project's identity configuration under the courier.smtp section. You can do this using the Ory CLI: 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).quiet-painting-32952
08/07/2025, 12:04 PMchilly-king-10285
08/07/2025, 12:04 PMchilly-king-10285
08/07/2025, 12:04 PMmax-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: 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.quiet-painting-32952
08/07/2025, 12:11 PMchilly-king-10285
08/07/2025, 12:11 PMquiet-painting-32952
08/07/2025, 12:11 PMchilly-king-10285
08/07/2025, 12:11 PMchilly-king-10285
08/07/2025, 12:12 PMmax-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.quiet-painting-32952
08/08/2025, 12:31 PMchilly-king-10285
08/08/2025, 12:31 PMchilly-king-10285
08/08/2025, 12:31 PMrequired_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.quiet-painting-32952
09/17/2025, 8:39 AM{
"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?chilly-king-10285
09/17/2025, 8:39 AMchilly-king-10285
09/17/2025, 8:40 AMquiet-painting-32952
09/17/2025, 8:50 AMchilly-king-10285
09/17/2025, 8:50 AMchilly-king-10285
09/17/2025, 8:50 AMselfservice.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: 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.quiet-painting-32952
09/17/2025, 8:55 AMchilly-king-10285
09/17/2025, 8:55 AMchilly-king-10285
09/17/2025, 8:55 AMselfservice.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).quiet-painting-32952
09/30/2025, 2:56 PMchilly-king-10285
09/30/2025, 2:56 PMchilly-king-10285
09/30/2025, 2:56 PMquiet-painting-32952
09/30/2025, 3:36 PMchilly-king-10285
09/30/2025, 3:36 PMchilly-king-10285
09/30/2025, 3:36 PMquiet-painting-32952
09/30/2025, 6:48 PMchilly-king-10285
09/30/2025, 6:48 PMchilly-king-10285
09/30/2025, 6:48 PMhttp:// 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: <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.quiet-painting-32952
10/01/2025, 11:21 AMchilly-king-10285
10/01/2025, 11:21 AMchilly-king-10285
10/01/2025, 11:22 AM/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.quiet-painting-32952
10/01/2025, 11:59 AMb9ae853b-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.chilly-king-10285
10/01/2025, 11:59 AMchilly-king-10285
10/01/2025, 11:59 AMquiet-painting-32952
10/01/2025, 12:03 PM8951d188-75ca-4d9a-9997-4181cb1efb1a
Time: Wed, 01 Oct 2025 12:03:14 GMT
Message: No active session was found in this request.chilly-king-10285
10/01/2025, 12:03 PMchilly-king-10285
10/01/2025, 12:04 PMNo 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.quiet-painting-32952
10/01/2025, 12:09 PMchilly-king-10285
10/01/2025, 12:09 PMchilly-king-10285
10/01/2025, 12:09 PMreturn_to or after_verification_return_to query parameter when initializing the registration flow. For example: 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: 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).quiet-painting-32952
10/01/2025, 12:24 PMchilly-king-10285
10/01/2025, 12:24 PMchilly-king-10285
10/01/2025, 12:24 PM<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.quiet-painting-32952
10/01/2025, 2:29 PMchilly-king-10285
10/01/2025, 2:29 PMchilly-king-10285
10/01/2025, 2:30 PMquiet-painting-32952
10/02/2025, 12:41 PMchilly-king-10285
10/02/2025, 12:41 PMchilly-king-10285
10/02/2025, 12:41 PMcommon. - 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: 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.quiet-painting-32952
10/02/2025, 12:42 PMchilly-king-10285
10/02/2025, 12:42 PMchilly-king-10285
10/02/2025, 12:43 PMsub 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: 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).quiet-painting-32952
10/03/2025, 7:10 PMchilly-king-10285
10/03/2025, 7:10 PMchilly-king-10285
10/03/2025, 7:11 PMrequired_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.quiet-painting-32952
10/07/2025, 5:32 PMchilly-king-10285
10/07/2025, 5:32 PMchilly-king-10285
10/07/2025, 5:33 PM@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.quiet-painting-32952
10/07/2025, 5:33 PMchilly-king-10285
10/07/2025, 5:34 PMchilly-king-10285
10/07/2025, 5:34 PM@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.quiet-painting-32952
10/07/2025, 5:39 PMchilly-king-10285
10/07/2025, 5:39 PMchilly-king-10285
10/07/2025, 5:39 PMquiet-painting-32952
10/08/2025, 10:29 AMchilly-king-10285
10/08/2025, 10:29 AMchilly-king-10285
10/08/2025, 10:29 AM<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.quiet-painting-32952
10/08/2025, 4:21 PMchilly-king-10285
10/08/2025, 4:21 PMchilly-king-10285
10/08/2025, 4:21 PMDELETE /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.quiet-painting-32952
10/08/2025, 4:22 PMquiet-painting-32952
10/08/2025, 4:22 PMchilly-king-10285
10/08/2025, 4:22 PMchilly-king-10285
10/08/2025, 4:23 PMoidc 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: 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.quiet-painting-32952
10/10/2025, 2:05 PMchilly-king-10285
10/10/2025, 2:05 PMchilly-king-10285
10/10/2025, 2:05 PM429 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).quiet-painting-32952
10/10/2025, 4:10 PMchilly-king-10285
10/10/2025, 4:10 PMchilly-king-10285
10/10/2025, 4:10 PMquiet-painting-32952
11/05/2025, 1:04 PMchilly-king-10285
11/05/2025, 1:04 PMchilly-king-10285
11/05/2025, 1:04 PMquiet-painting-32952
11/05/2025, 3:06 PMchilly-king-10285
11/05/2025, 3:06 PMchilly-king-10285
11/05/2025, 3:07 PM/selfservice/methods/link/config/base_url configuration key. You can do this with the Ory CLI: 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.quiet-painting-32952
11/05/2025, 3:16 PMchilly-king-10285
11/05/2025, 3:16 PMchilly-king-10285
11/05/2025, 3:16 PM<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`).quiet-painting-32952
11/05/2025, 4:11 PMchilly-king-10285
11/05/2025, 4:11 PMchilly-king-10285
11/05/2025, 4:12 PM{{ .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: 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.quiet-painting-32952
11/05/2025, 4:25 PMchilly-king-10285
11/05/2025, 4:25 PMchilly-king-10285
11/05/2025, 4:26 PM/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.quiet-painting-32952
11/05/2025, 8:25 PMchilly-king-10285
11/05/2025, 8:25 PMchilly-king-10285
11/05/2025, 8:26 PMquiet-painting-32952
11/05/2025, 10:06 PMchilly-king-10285
11/05/2025, 10:06 PMchilly-king-10285
11/05/2025, 10:06 PMquiet-painting-32952
11/05/2025, 10:34 PMchilly-king-10285
11/05/2025, 10:34 PMchilly-king-10285
11/05/2025, 10:35 PMflow query parameter), fetch the recovery flow: 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: 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!quiet-painting-32952
11/05/2025, 10:49 PMchilly-king-10285
11/05/2025, 10:49 PMchilly-king-10285
11/05/2025, 10:49 PMquiet-painting-32952
11/06/2025, 7:34 AMchilly-king-10285
11/06/2025, 7:34 AMchilly-king-10285
11/06/2025, 7:35 AMquiet-painting-32952
11/06/2025, 7:40 AMchilly-king-10285
11/06/2025, 7:40 AMchilly-king-10285
11/06/2025, 7:40 AMquiet-painting-32952
11/06/2025, 2:51 PMchilly-king-10285
11/06/2025, 2:51 PMchilly-king-10285
11/06/2025, 2:51 PM{
"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: 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.quiet-painting-32952
12/02/2025, 10:35 PMchilly-king-10285
12/02/2025, 10:35 PMchilly-king-10285
12/02/2025, 10:36 PMdefault + password nodes and posts the form back to Ory, as shown in the docs examples.[^1] 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.quiet-painting-32952
12/02/2025, 10:55 PMquiet-painting-32952
12/02/2025, 10:56 PMchilly-king-10285
12/02/2025, 10:56 PMchilly-king-10285
12/02/2025, 10:56 PMidentifier_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: 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: 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.quiet-painting-32952
12/02/2025, 11:02 PMchilly-king-10285
12/02/2025, 11:02 PMchilly-king-10285
12/02/2025, 11:03 PMselfservice.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`: 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)]quiet-painting-32952
12/02/2025, 11:05 PMpassword group nodes (identifier + password) from the flow.?chilly-king-10285
12/02/2025, 11:05 PMchilly-king-10285
12/02/2025, 11:05 PMdefault 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): 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.quiet-painting-32952
12/02/2025, 11:06 PMchilly-king-10285
12/02/2025, 11:06 PMchilly-king-10285
12/02/2025, 11:07 PMidentifier_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: 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.quiet-painting-32952
12/02/2025, 11:07 PMselfservice:
flows:
login:
style: unified # instead of identifier_firstchilly-king-10285
12/02/2025, 11:07 PMchilly-king-10285
12/02/2025, 11:08 PMunified 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 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)]quiet-painting-32952
12/08/2025, 12:30 PMimport 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>
);
};
{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"chilly-king-10285
12/08/2025, 12:30 PMchilly-king-10285
12/08/2025, 12:31 PMtsx <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: 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: 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: 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: 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: 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)quiet-painting-32952
12/08/2025, 12:33 PMchilly-king-10285
12/08/2025, 12:34 PMchilly-king-10285
12/08/2025, 12:34 PMupdateLoginFlow, - 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) 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> ) }quiet-painting-32952
12/08/2025, 7:41 PMchilly-king-10285
12/08/2025, 7:41 PMchilly-king-10285
12/08/2025, 7:41 PM303 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.quiet-painting-32952
12/08/2025, 7:43 PMimport 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>
);
};chilly-king-10285
12/08/2025, 7:43 PMchilly-king-10285
12/08/2025, 7:44 PMAccept 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`: 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)quiet-painting-32952
12/08/2025, 7:49 PMchilly-king-10285
12/08/2025, 7:49 PMchilly-king-10285
12/08/2025, 7:50 PM<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: 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.quiet-painting-32952
12/08/2025, 8:00 PMchilly-king-10285
12/08/2025, 8:00 PMchilly-king-10285
12/08/2025, 8:00 PM401 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).quiet-painting-32952
12/08/2025, 8:22 PMconst 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
},
}),
);chilly-king-10285
12/08/2025, 8:22 PMchilly-king-10285
12/08/2025, 8:22 PMFrontendApi 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.quiet-painting-32952
12/08/2025, 8:53 PMchilly-king-10285
12/08/2025, 8:53 PMchilly-king-10285
12/08/2025, 8:53 PM303 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.quiet-painting-32952
12/15/2025, 11:25 PMchilly-king-10285
12/15/2025, 11:25 PMchilly-king-10285
12/15/2025, 11:25 PM/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.: 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.quiet-painting-32952
12/16/2025, 4:29 PM{
"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 inchilly-king-10285
12/16/2025, 4:29 PMchilly-king-10285
12/16/2025, 4:29 PMpassword → 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.quiet-painting-32952
12/16/2025, 4:52 PMchilly-king-10285
12/16/2025, 4:52 PMchilly-king-10285
12/16/2025, 4:53 PMui.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)quiet-painting-32952
12/16/2025, 5:00 PM{
"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"
}
}
}
}chilly-king-10285
12/16/2025, 5:00 PMchilly-king-10285
12/16/2025, 5:01 PM"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: 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)]quiet-painting-32952
12/16/2025, 5:02 PM{
"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?chilly-king-10285
12/16/2025, 5:02 PMchilly-king-10285
12/16/2025, 5:03 PMgroup: "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: 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): 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.quiet-painting-32952
12/16/2025, 6:03 PM{
"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 ?chilly-king-10285
12/16/2025, 6:03 PMchilly-king-10285
12/16/2025, 6:04 PMtitle 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: json "email": { "type": "string", "format": "email", "title": "Email", "<http://ory.sh/kratos|ory.sh/kratos>": { ... } } produces a node like: 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: 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.quiet-painting-32952
12/16/2025, 7:05 PMchilly-king-10285
12/16/2025, 7:05 PMchilly-king-10285
12/16/2025, 7:06 PMupdateLoginFlow 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: 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: 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`: 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)]quiet-painting-32952
12/16/2025, 7:10 PM{
"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"
}chilly-king-10285
12/16/2025, 7:10 PMchilly-king-10285
12/16/2025, 7:10 PMidentifier field. Ory is responding with a new flow where the identifier node has a validation error: json "messages": [ { "id": 4000002, "text": "Property identifier is missing.", "type": "error", "context": { "property": "identifier" } } ] and the flow state is still: 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: 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: 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.quiet-painting-32952
12/16/2025, 7:24 PMchilly-king-10285
12/16/2025, 7:24 PMchilly-king-10285
12/16/2025, 7:24 PMcsrf_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: 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: 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.quiet-painting-32952
12/16/2025, 7:34 PMchilly-king-10285
12/16/2025, 7:34 PMchilly-king-10285
12/16/2025, 7:34 PM4000006 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: 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)]quiet-painting-32952
12/17/2025, 8:39 AMchilly-king-10285
12/17/2025, 8:39 AMchilly-king-10285
12/17/2025, 8:39 AMhttp 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: 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: http GET /self-service/recovery/api Example from the docs: 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: 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): 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.quiet-painting-32952
12/17/2025, 8:40 AMchilly-king-10285
12/17/2025, 8:40 AMchilly-king-10285
12/17/2025, 8:41 AMts 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: 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: 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: 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)]quiet-painting-32952
12/23/2025, 7:53 PMchilly-king-10285
12/23/2025, 7:53 PMchilly-king-10285
12/23/2025, 7:53 PMoidc 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: 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: 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`: 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: 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.quiet-painting-32952
12/23/2025, 8:31 PM{
"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"
}chilly-king-10285
12/23/2025, 8:31 PMchilly-king-10285
12/23/2025, 8:31 PMAccept: 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: 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`: 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.quiet-painting-32952
12/24/2025, 6:24 PMchilly-king-10285
12/24/2025, 6:24 PMchilly-king-10285
12/24/2025, 6:25 PMui.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: 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.quiet-painting-32952
12/26/2025, 2:56 PMchilly-king-10285
12/26/2025, 2:56 PMchilly-king-10285
12/26/2025, 2:57 PM/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: 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.quiet-painting-32952
12/26/2025, 2:58 PMchilly-king-10285
12/26/2025, 2:58 PMchilly-king-10285
12/26/2025, 2:59 PMjs 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: 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 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)]quiet-painting-32952
12/26/2025, 3:05 PMchilly-king-10285
12/26/2025, 3:05 PMchilly-king-10285
12/26/2025, 3:05 PM/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): 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.quiet-painting-32952
12/27/2025, 5:35 PMchilly-king-10285
12/27/2025, 5:35 PMchilly-king-10285
12/27/2025, 5:36 PMid_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: 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: 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: 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`: 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: 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)]quiet-painting-32952
12/27/2025, 5:37 PMchilly-king-10285
12/27/2025, 5:37 PMchilly-king-10285
12/27/2025, 5:38 PMid_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.quiet-painting-32952
12/27/2025, 11:53 PMconst { 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 401chilly-king-10285
12/27/2025, 11:53 PMchilly-king-10285
12/27/2025, 11:53 PMsession_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`: 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`: 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: 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)]quiet-painting-32952
12/28/2025, 3:53 AMchilly-king-10285
12/28/2025, 3:53 AMchilly-king-10285
12/28/2025, 3:53 AMreturn_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.quiet-painting-32952
12/28/2025, 4:03 AMchilly-king-10285
12/28/2025, 4:03 AMchilly-king-10285
12/28/2025, 4:04 AMquiet-painting-32952
01/02/2026, 2:54 AM{
method: "oidc",
provider: "microsoft",
}
I get:
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>"}chilly-king-10285
01/02/2026, 2:54 AMchilly-king-10285
01/02/2026, 2:54 AMtype: api): 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: 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: 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.quiet-painting-32952
01/02/2026, 3:01 AM{
"method": "oidc",
"provider": "microsoft"
}
and I get
{
"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 yetchilly-king-10285
01/02/2026, 3:01 AMchilly-king-10285
01/02/2026, 3:02 AMjson "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: 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: 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: 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.quiet-painting-32952
03/11/2026, 3:06 PMchilly-king-10285
03/11/2026, 3:07 PM/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.quiet-painting-32952
03/11/2026, 3:58 PMchilly-king-10285
03/11/2026, 3:58 PM/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.quiet-painting-32952
03/11/2026, 4:00 PMchilly-king-10285
03/11/2026, 4:00 PM/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.quiet-painting-32952
05/18/2026, 11:23 AMchilly-king-10285
05/18/2026, 11:23 AMexpires_in field when calling the recovery link endpoint:
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:
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.quiet-painting-32952
05/21/2026, 3:25 PMquiet-painting-32952
05/21/2026, 3:25 PMchilly-king-10285
05/21/2026, 3:25 PMcurl --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:
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.quiet-painting-32952
07/14/2026, 12:28 PMchilly-king-10285
07/14/2026, 12:28 PMui.nodes array:
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:
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:
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:
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.quiet-painting-32952
07/14/2026, 12:30 PMquiet-painting-32952
07/14/2026, 12:30 PMchilly-king-10285
07/14/2026, 12:30 PMlog:
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.quiet-painting-32952
09/25/2026, 10:24 AMchilly-king-10285
09/25/2026, 10:24 AM