Hey Al.. I'm having trouble getting kratos to send...
# ory-selfhosting
b
Hey Al.. I'm having trouble getting kratos to send a verification email after registration success. What am i missing in this config
Copy code
dsn: <cockroach://root@cockroach:26257/kratos_db?sslmode=disable>

serve:
  public:
    base_url: <http://127.0.0.1:4433/>
    cors:
      enabled: true
      allowed_origins:
        - <http://localhost:5173>
        - <http://localhost:5174>
        - <http://localhost:3000>
        - <http://localhost:4455>
      allowed_methods:
        - POST
        - GET
        - PUT
        - PATCH
        - DELETE
        - OPTIONS
      allowed_headers:
        - Authorization
        - Content-Type
        - Cookie
      exposed_headers:
        - Content-Type
        - Set-Cookie
      allow_credentials: true
  admin:
    base_url: <http://127.0.0.1:4434/>

selfservice:
  default_browser_return_url: <http://localhost:5173/enrollment>
  flows:
    login:
      ui_url: <http://localhost:5173/login>
    registration:
      ui_url: <http://localhost:5173/registration>
      enabled: true
      after:
        default_browser_return_url: <http://localhost:5173/enrollment>
        password:
          hooks:
            - hook: show_verification_ui
            - hook: show_recovery_ui
    settings:
      ui_url: <http://127.0.0.1:4455/settings>
    recovery:
      ui_url: <http://127.0.0.1:4455/recovery>
    verification:
      ui_url: <http://127.0.0.1:4455/verification>
  methods:
    password:
      enabled: true

identity:
  default_schema_id: default
  schemas:
    - id: default
      url: file:///etc/config/kratos/identity-schemas/tpp.schema.json


courier:
  templates:
    verification:
      valid:
        email:
          body:
            html: file:///etc/config/kratos/email-templates/verification/body.html.gotmpl
            plaintext: file:///etc/config/kratos/email-templates/verification/body.plaintext.gotmpl
          subject: file:///etc/config/kratos/email-templates/verification/subject.gotmpl
  delivery_strategy: smtp
  smtp:
    connection_uri: <smtps://sandbox-3>:***@smtp.devmail.email:2525/
    from_address: <mailto:[email protected]|[email protected]>
    from_name: FAPI Gateway
@chilly-king-10285 can you help with this?
c
A message from kapa.ai
I'm happy to help with questions about Ory! However, your message "can you help with this?" does not specify what you need assistance with. Please provide more details or clarify your question regarding Ory, and I'll do my best to assist you based on the available knowledge sources.
b
@chilly-king-10285 'm having trouble getting kratos to send a verification email after registration success. What am i missing in this config
Copy code
dsn: <cockroach://root@cockroach:26257/kratos_db?sslmode=disable>

serve:
  public:
    base_url: <http://127.0.0.1:4433/>
    cors:
      enabled: true
      allowed_origins:
        - <http://localhost:5173>
        - <http://localhost:5174>
        - <http://localhost:3000>
        - <http://localhost:4455>
      allowed_methods:
        - POST
        - GET
        - PUT
        - PATCH
        - DELETE
        - OPTIONS
      allowed_headers:
        - Authorization
        - Content-Type
        - Cookie
      exposed_headers:
        - Content-Type
        - Set-Cookie
      allow_credentials: true
  admin:
    base_url: <http://127.0.0.1:4434/>

selfservice:
  default_browser_return_url: <http://localhost:5173/enrollment>
  flows:
    login:
      ui_url: <http://localhost:5173/login>
    registration:
      ui_url: <http://localhost:5173/registration>
      enabled: true
      after:
        default_browser_return_url: <http://localhost:5173/enrollment>
        password:
          hooks:
            - hook: show_verification_ui
            - hook: show_recovery_ui
    settings:
      ui_url: <http://127.0.0.1:4455/settings>
    recovery:
      ui_url: <http://127.0.0.1:4455/recovery>
    verification:
      ui_url: <http://127.0.0.1:4455/verification>
  methods:
    password:
      enabled: true

identity:
  default_schema_id: default
  schemas:
    - id: default
      url: file:///etc/config/kratos/identity-schemas/tpp.schema.json


courier:
  templates:
    verification:
      valid:
        email:
          body:
            html: file:///etc/config/kratos/email-templates/verification/body.html.gotmpl
            plaintext: file:///etc/config/kratos/email-templates/verification/body.plaintext.gotmpl
          subject: file:///etc/config/kratos/email-templates/verification/subject.gotmpl
  delivery_strategy: smtp
  smtp:
    connection_uri: <smtps://sandbox-3>:***@smtp.devmail.email:2525/
    from_address: <mailto:[email protected]|[email protected]>
    from_name: FAPI Gateway
c
A message from kapa.ai
Based on your configuration and the available knowledge sources, here are the key requirements for Ory Kratos to send a verification email after successful registration: ### 1. Verification Flow Must Be Enabled You need to ensure that the verification flow is enabled and properly configured in your
selfservice.flows.verification
section. In your config, you have:
Copy code
yaml selfservice:   flows:     verification:       ui_url: <http://127.0.0.1:4455/verification>
However, you are missing the
enabled: true
and the
use: code
(or
link
) fields. According to the documentation, it should look like:
Copy code
yaml selfservice:   flows:     verification:       enabled: true       use: code # or 'link'       ui_url: <http://127.0.0.1:4455/verification>
[Address verification](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation) ### 2. Identity Schema Must Mark Email as Verifiable Your identity schema (referenced as
tpp.schema.json
) must mark the email trait as verifiable. For example:
Copy code
json "email": {   "type": "string",   "format": "email",   "<http://ory.sh/kratos|ory.sh/kratos>": {     "credentials": {       "password": {         "identifier": true       }     },     "verification": {       "via": "email"     }   } }
[Address verification](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation) ### 3. Registration Flow and Hooks You have the
show_verification_ui
hook enabled after password registration, which is correct if you want to show the verification screen after registration. This is optional for sending the email, but required if you want to force users to verify before getting a session. By default, Ory will send the verification email if verification is enabled and the schema is correct, even without this hook. [Verification on sign up](https://www.ory.sh/docs/actions/require-verified-address#verification-on-sign-up) ### 4. Courier Configuration Your courier configuration looks correct, assuming your SMTP credentials and template paths are valid. ### 5. Common Pitfalls - If you do not see emails being sent, check the Kratos logs for errors related to the courier or SMTP delivery. - Make sure your SMTP server is reachable and credentials are correct. - Ensure your identity schema is loaded correctly and the
verification
property is present for the email trait. --- Summary of what you are missing: You need to add
enabled: true
and
use: code
(or
link
) to your
selfservice.flows.verification
section. Also, double-check your identity schema to ensure the email trait is marked as verifiable. If you make these changes and still do not receive verification emails, check the logs for courier or SMTP errors, and verify that your SMTP configuration is correct. For more details, see the [Ory Kratos Address Verification documentation](https://www.ory.sh/docs/kratos/self-service/flows/verify-email-account-activation).
b
@chilly-king-10285 give me the config for courier sending email using http
c
A message from kapa.ai
Certainly! Here is an example configuration for Ory Kratos (or Ory Network) to send emails using an HTTP server (instead of SMTP) via the
courier
section. This config can be used in your YAML configuration file:
Copy code
yaml courier:   delivery_strategy: http   http:     request_config:       url: <https://mail-sender.example.com>       method: POST       body: base64://... # Base64-encoded Jsonnet template for the payload (see below)       headers:         Content-Type: application/json       auth:         type: basic_auth # or api_key         config:           user: my-username           password: my-password           # For api_key, use:           # name: my-api-name           # value: my-api-key-value           # in: header # or cookie
Notes: - The
body
field should contain a Base64-encoded Jsonnet template that defines the payload sent to your HTTP email provider. If omitted, a default template is used. - The
auth
section is optional and depends on your provider's requirements. Default Jsonnet template for the payload:
Copy code
jsonnet function(ctx) {   recipient: ctx.recipient,   template_type: ctx.template_type,   to: if "template_data" in ctx && "to" in ctx.template_data then <http://ctx.template_data.to|ctx.template_data.to> else null,   recovery_code: if "template_data" in ctx && "recovery_code" in ctx.template_data then ctx.template_data.recovery_code else null,   recovery_url: if "template_data" in ctx && "recovery_url" in ctx.template_data then ctx.template_data.recovery_url else null,   verification_url: if "template_data" in ctx && "verification_url" in ctx.template_data then ctx.template_data.verification_url else null,   verification_code: if "template_data" in ctx && "verification_code" in ctx.template_data then ctx.template_data.verification_code else null,   login_code: if "template_data" in ctx && "login_code" in ctx.template_data then ctx.template_data.login_code else null,   registration_code: if "template_data" in ctx && "registration_code" in ctx.template_data then ctx.template_data.registration_code else null,   subject: if "template_data" in ctx && "subject" in ctx.template_data then ctx.template_data.subject else null,   body: if "template_data" in ctx && "body" in ctx.template_data then ctx.template_data.body else null }
You can use this template as-is or customize it for your provider, then Base64-encode it and reference it in the
body
field. For more details and examples, see the official documentation: [Send emails using an HTTP server](https://www.ory.sh/docs/kratos/emails-sms/sending-emails-smtp#send-emails-using-an-http-server) and [HTTP based email delivery (self-hosted)](https://www.ory.sh/docs/kratos/self-hosted/email-http#configuration).
b
@chilly-king-10285 i am using an api_key
c
A message from kapa.ai
Certainly! Here is how you configure Ory Kratos to send emails using an HTTP server with API key authentication:
Copy code
yaml courier:   delivery_strategy: http   http:     request_config:       url: <https://api.your-email-provider.com/send>       method: POST       body: file:///etc/config/kratos/mail.api.request.jsonnet # or use <base64://ENCODED_JSONNET>       headers:         "Content-Type": "application/json"       auth:         type: api_key         config:           name: Authorization         # The header or cookie name for the API key           value: Bearer <API_KEY>     # The value of your API key (prefix with 'Bearer ' if required by your provider)           in: header                  # Use 'header' or 'cookie' as required by your provider
Key points: - Replace
url
with your provider’s endpoint. - The
body
can be a file path or a base64-encoded Jsonnet template. If omitted, the default template is used. - Set the
name
,
value
, and
in
fields in the
auth.config
section according to your provider’s requirements. For example, for Sendgrid, you would use:
Copy code
yaml auth:   type: api_key   config:     name: Authorization     value: Bearer <API_KEY>     in: header
This configuration is directly supported and documented in the Ory Kratos docs for HTTP-based email delivery with API key authentication [HTTP based email delivery](https://www.ory.sh/docs/kratos/self-hosted/email-http#configuration) and [Send emails using an HTTP server](https://www.ory.sh/docs/kratos/emails-sms/sending-emails-smtp#send-emails-using-an-http-server). If you need a sample Jsonnet template for the body, let me know!
b
@chilly-king-10285 how do i use jsonnet for the body parameter? Giuve me an example
c
A message from kapa.ai
To use Jsonnet for the
body
parameter in your HTTP email delivery configuration, you provide a Jsonnet template that defines the payload sent to your email provider. This template can be referenced as a file (e.g., `file://path/to/template.jsonnet`) or as a base64-encoded string (e.g.,
<base64://ENCODED_JSONNET>
). Ory Kratos will pass a
ctx
object to your Jsonnet function, containing relevant data for the email. Example Jsonnet template (
mail.api.request.jsonnet
):
Copy code
jsonnet function(ctx) {   recipient: ctx.recipient,   template_type: ctx.template_type,   to: if "template_data" in ctx && "to" in ctx.template_data then <http://ctx.template_data.to|ctx.template_data.to> else null,   recovery_code: if "template_data" in ctx && "recovery_code" in ctx.template_data then ctx.template_data.recovery_code else null,   recovery_url: if "template_data" in ctx && "recovery_url" in ctx.template_data then ctx.template_data.recovery_url else null,   verification_url: if "template_data" in ctx && "verification_url" in ctx.template_data then ctx.template_data.verification_url else null,   verification_code: if "template_data" in ctx && "verification_code" in ctx.template_data then ctx.template_data.verification_code else null }
How to reference it in your config:
Copy code
yaml courier:   delivery_strategy: http   http:     request_config:       url: <https://api.your-email-provider.com/send>       method: POST       body: file:///etc/config/kratos/mail.api.request.jsonnet       headers:         "Content-Type": "application/json"       auth:         type: api_key         config:           name: Authorization           value: Bearer <API_KEY>           in: header
Alternatively, you can base64-encode your Jsonnet file and use:
Copy code
yaml body: base64://<BASE64_ENCODED_JSONNET>
What fields are available in
ctx
?
- `recipient`: The email address of the recipient. - `template_type`: The type of template (e.g.,
verification_valid
). - `template_data`: An object with template-specific data (e.g.,
verification_url
,
recovery_code
, etc.). For more details and a list of available fields, see the [official documentation](https://www.ory.sh/docs/kratos/self-hosted/email-http#configuration) and [Jsonnet reference](https://www.ory.sh/docs/kratos/reference/jsonnet).
b
@chilly-king-10285 give me config for totp:
c
A message from kapa.ai
Certainly! To enable and configure TOTP (Time-based One-Time Password) in Ory Kratos, you need to add the following section to your configuration file:
Copy code
yaml selfservice:   methods:     totp:       config:         # The "issuer" is the name shown in the TOTP app (e.g., Google Authenticator)         issuer: <http://ExampleIssuer.com|ExampleIssuer.com>       enabled: true
Key points: - The
issuer
value will be displayed in the authenticator app. - Make sure
enabled: true
is set. This configuration enables TOTP as a second-factor authentication method for your users. After enabling, users can pair their account with a TOTP app (like Google Authenticator) via the Ory Account Experience or your custom UI. For more details, see the official documentation: [Time-based one-time passwords (TOTP)](https://www.ory.sh/docs/kratos/mfa/totp) and [Self-hosted MFA configuration](https://www.ory.sh/docs/kratos/self-hosted/mfa).
b
@chilly-king-10285 how do you totp after login success. Show sample config
c
A message from kapa.ai
To require TOTP (Time-based One-Time Password) after a successful login (i.e., as a second authentication step), you need to configure Ory Kratos to enforce multi-factor authentication (MFA) with TOTP as a second factor. ### 1. Enable TOTP in your config
Copy code
yaml selfservice:   methods:     totp:       config:         issuer: <http://ExampleIssuer.com|ExampleIssuer.com>       enabled: true
This enables TOTP as a second factor and sets the issuer name that appears in authenticator apps [Time-based one-time passwords (TOTP)](https://www.ory.sh/docs/kratos/mfa/totp). ### 2. Enforce MFA after login To require users to complete TOTP after logging in, set the required Authenticator Assurance Level (AAL) for sessions and/or sensitive flows. For example, to require the highest available AAL (which includes TOTP if configured) for accessing account settings:
Copy code
yaml selfservice:   flows:     settings:       required_aal: highest_available session:   whoami:     required_aal: highest_available
With this configuration, after a user logs in with their password, they will be prompted for TOTP if they have it configured. If not, they can proceed with just their password. If you want to require TOTP for all users who have it set up, this is the recommended approach [Configure multi-factor authentication in Ory Kratos](https://www.ory.sh/docs/kratos/self-hosted/mfa). ### 3. How it works in practice - User logs in with their first factor (e.g., password). - If the session or flow requires
aal2
(second factor), and the user has TOTP configured, Ory will prompt for the TOTP code. - Upon successful TOTP entry, the session is upgraded to
aal2
. For more on step-up authentication and enforcing TOTP after login, see [Step-up authentication](https://www.ory.sh/docs/kratos/mfa/step-up-authentication). --- Summary: Enable TOTP in
selfservice.methods
, and set
required_aal: highest_available
for the relevant flows or session. This will prompt users for TOTP after a successful login if they have TOTP configured.
b
i mean google authenticator
@chilly-king-10285 i mean google authenticator
c
A message from kapa.ai
To require users to enter a code from Google Authenticator (or any TOTP app) after a successful login, you need to enable TOTP as a second factor and enforce multi-factor authentication (MFA) in your Ory Kratos configuration. Here’s a sample configuration based on the official documentation:
Copy code
yaml selfservice:   methods:     totp:       config:         # This is the name that will appear in Google Authenticator         issuer: <http://ExampleIssuer.com|ExampleIssuer.com>       enabled: true    flows:     settings:       required_aal: highest_available  session:   whoami:     required_aal: highest_available
How it works: - After a user logs in with their password, if they have TOTP (e.g., Google Authenticator) configured, Ory will prompt them to enter the code from their app. - The
issuer
field is what users will see in Google Authenticator when they pair their account. - The
required_aal: highest_available
setting ensures that users must complete the second factor (TOTP) for sensitive actions or to upgrade their session to AAL2. This setup is directly supported and described in the Ory documentation for TOTP and step-up authentication [Time-based one-time passwords (TOTP)](https://www.ory.sh/docs/kratos/mfa/totp) [Step-up authentication](https://www.ory.sh/docs/kratos/mfa/step-up-authentication). If you want TOTP to be required immediately after login for all users who have it configured, this is the recommended approach. If a user does not have TOTP set up, they will not be prompted for it until they do so. If you need to customize which flows require TOTP, adjust the
required_aal
setting under the relevant flow (e.g.,
login
,
settings
, etc.) as shown above.
b
@chilly-king-10285 How do we delete an account
c
A message from kapa.ai
To delete an account (identity) in Ory Kratos, you need to use the Ory Identities API. Specifically, you can use the
deleteIdentityCredentials
method, which requires authentication with an Ory access token. This operation is typically performed via the admin API and is not a self-service flow exposed to end users by default. Here’s what the knowledge sources state: > To perform this operation, you must be authenticated by means of one of the following methods: oryAccessToken [Kratos API Reference](https://raw.githubusercontent.com/ory/kratos/master/spec/api.json) The documentation does not provide a full example of the API call, but the process generally involves: - Authenticating as an admin or with appropriate permissions. - Calling the relevant admin API endpoint to delete the identity. If you need to delete an account via the Ory Console or CLI, you would typically use the admin interface or CLI commands to manage identities, but the knowledge sources do not provide explicit step-by-step instructions for this process. Summary: Account deletion is performed via the admin API using an authenticated request. The knowledge sources do not provide a detailed example, but you must use an Ory access token and the appropriate API endpoint to delete an identity.
b
@chilly-king-10285 i want to show normal login then after that show a google authenticator challenge, how do i configure this with kratos
c
A message from kapa.ai
To show a normal login (e.g., password) and then, after successful login, prompt the user for a Google Authenticator (TOTP) challenge, you need to enable TOTP as a second factor and enforce multi-factor authentication (MFA) in your Ory Kratos configuration. Here’s how you can configure this: 1. Enable TOTP as a second factor:
Copy code
yaml selfservice:   methods:     totp:       config:         issuer: <http://ExampleIssuer.com|ExampleIssuer.com>  # This is the name shown in Google Authenticator       enabled: true
This enables TOTP (Google Authenticator) for your project and sets the issuer name that appears in the authenticator app [Time-based one-time passwords (TOTP)](https://www.ory.sh/docs/kratos/mfa/totp). 2. Enforce MFA after login: Set the required Authenticator Assurance Level (AAL) to require the highest available factor (which includes TOTP if configured):
Copy code
yaml session:   whoami:     required_aal: highest_available
With this configuration, after a user logs in with their password, Ory Kratos will check if the user has TOTP configured. If so, the user will be prompted for their TOTP code (e.g., from Google Authenticator) before the session is fully established at AAL2. If the user does not have TOTP set up, they will not be prompted for it. How it works: - User enters their username and password (normal login). - If TOTP is enabled and configured for the user, Ory will prompt for the TOTP code after the password is verified. - Upon successful TOTP entry, the user gains access with an AAL2 session. This approach is described in the official documentation for step-up authentication and TOTP configuration [Step-up authentication](https://www.ory.sh/docs/kratos/mfa/step-up-authentication) [Time-based one-time passwords (TOTP)](https://www.ory.sh/docs/kratos/mfa/totp). If you want to require TOTP only for specific flows (e.g., only for settings changes), adjust the
required_aal
setting under the relevant flow (such as
selfservice.flows.settings.required_aal
). Note: The knowledge sources do not mention a way to force TOTP for all users regardless of whether they have it configured, nor do they describe a way to force TOTP enrollment immediately after registration. Users must enable TOTP themselves in their account settings after registration [Time-based one-time passwords (TOTP)](https://www.ory.sh/docs/kratos/mfa/totp). --- Summary: Enable TOTP and set
session.whoami.required_aal: highest_available
to prompt for a Google Authenticator challenge after normal login if the user has TOTP configured.
b
@chilly-king-10285 Do i redirect from login to TOTP screen manually within my app or will kratos do the redirection for me
c
A message from kapa.ai
Ory Kratos will handle the redirection for you as part of the self-service login flow when multi-factor authentication (such as TOTP/Google Authenticator) is required. After the user successfully completes the first factor (e.g., password), Kratos will automatically present the TOTP challenge screen if the user has TOTP configured and the flow or session requires a higher AAL (Authenticator Assurance Level). You do not need to manually redirect from the login to the TOTP screen within your app. Kratos manages the flow state and will return the appropriate UI screen (or API response) based on what is required next in the authentication process. Your UI should render the form based on the current flow state provided by Kratos. This is described in the documentation for self-service flows and step-up authentication, where Kratos manages the flow and prompts for the second factor as needed, depending on the configuration and the user's state [Step-up authentication](https://www.ory.sh/docs/kratos/mfa/step-up-authentication) [Login flow](https://www.ory.sh/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#login-flow). If you are building a custom UI, you should use the flow information returned by Kratos to determine which form to display, but the transition between steps (e.g., from password to TOTP) is managed by Kratos itself.
b
@chilly-king-10285 After clicking email verification link, i am getting this error
Copy code
{
  "error": {
    "id": "security_csrf_violation",
    "code": 403,
    "status": "Forbidden",
    "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"
  }
}
after redirecting to my app to check verification state and process with onboarding
c
A message from kapa.ai
The error message you are seeing:
Copy code
"error": {     "id": "security_csrf_violation",     ...     "reason": "Please retry the flow and optionally clear your cookies. The request was rejected to protect you from Cross-Site-Request-Forgery (CSRF)...",     "details": {         ...         "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."     } }
indicates a CSRF protection failure. This typically happens when the CSRF token in the request does not match the anti-CSRF cookie, or one of them is missing. Common causes and solutions based on the knowledge sources: 1. Cookies and CSRF Token Mismatch: This can occur if the verification link is opened in a different browser, incognito window, or if cookies were cleared or not sent with the request. Ory Kratos expects the CSRF cookie and the CSRF token (from the form or header) to match. If they do not, the request is rejected for security reasons [CSRF troubleshooting](https://www.ory.sh/docs/troubleshooting/csrf). 2. Public Suffix Domains: If you are using a domain that is on the [public suffix list](https://publicsuffix.org/), cookies may not be set correctly, leading to CSRF errors. Use a subdomain that is not on the PSL, such as
<http://app.myftp.org|app.myftp.org>
instead of
<http://myftp.org|myftp.org>
 [CSRF troubleshooting](https://www.ory.sh/docs/troubleshooting/csrf). 3. Mixing Domains or Inconsistent URLs: Make sure you are not mixing
localhost
and
127.0.0.1
, or using different subdomains, as cookies are domain-specific. Use the same domain consistently across your app and Ory Kratos [CSRF debug](https://www.ory.sh/docs/kratos/debug/csrf). 4. Flow Handling: When handling verification flows, ensure you are forwarding the
Cookie
header from the client when fetching the flow, especially in server-side applications. Without forwarding the
Cookie
header, you can't fetch the flow due to a security error [CSRF troubleshooting](https://www.ory.sh/docs/troubleshooting/csrf). 5. How to Fix: - Ask the user to retry the flow and clear cookies for the domain if necessary. - Ensure the verification link is opened in the same browser and context where the registration was performed. - Double-check your domain and cookie settings in the Kratos config. - If you are using a custom UI, make sure to include the CSRF token from the flow in your form submissions. For more details, see the [Ory Kratos CSRF troubleshooting guide](https://www.ory.sh/kratos/docs/debug/csrf). --- Summary: This error is caused by a mismatch or absence of the CSRF token and cookie. Ensure the verification link is opened in the same browser session, cookies are not blocked or cleared, and your domain configuration is correct. If the problem persists, clear cookies and retry the flow [CSRF troubleshooting](https://www.ory.sh/docs/troubleshooting/csrf).
b
@chilly-king-10285 how do we set Access-Control-Allow-Origin in the cors for kratos
c
A message from kapa.ai
To set the
Access-Control-Allow-Origin
header for Ory Kratos, you need to configure the CORS settings in your Kratos configuration file under the
serve.public.cors.allowed_origins
section. This determines which origins are allowed to make cross-origin requests to your Kratos public API. Here is an example configuration:
Copy code
yaml serve:   public:     cors:       enabled: true       allowed_origins:         - <https://example.org>         - https://*.<http://example.org|example.org>       allowed_methods:         - POST         - GET         - PUT         - PATCH         - DELETE       allowed_headers:         - Authorization         - Cookie         - Content-Type       exposed_headers:         - Content-Type         - Set-Cookie
In this example, the
allowed_origins
list sets the values for the
Access-Control-Allow-Origin
header. You can specify exact origins or use wildcards for subdomains (e.g.,
https://*.<http://example.org|example.org>
). For more details, see the official documentation: [Cross-origin resource sharing (CORS) settings](https://www.ory.com/docs/kratos/guides/setting-up-cors) and [Configure cross-origin resource sharing (CORS)](https://www.ory.com/docs/hydra/guides/cors).
b
@chilly-king-10285 how do we do logout flows
c
A message from kapa.ai
Ory supports several logout flows depending on your application type and use case. Here’s how you can implement logout flows with Ory Kratos and Ory OAuth2 & OpenID Connect, based on the knowledge sources: --- ### 1. Browser-Based Logout Flow (Ory Kratos) For browser applications, the logout flow is a two-step process: 1. Create a Logout URL: Call the
/self-service/logout/browser
endpoint to generate a logout URL associated with the current session cookie. 2. Complete the Logout: Redirect the user to the generated logout URL (which points to
/self-service/logout?token=...
). This will invalidate the session and remove the session cookie. You can also specify a
return_to
parameter to redirect the user after logout. Example (Express.js):
Copy code
js // Create logout route   app.get("/logout", async (req, res) => {     try {       // Create a logout flow       const { logout_url } = await ory.createBrowserLogoutFlow({         cookie: req.header("cookie"),       })       // Redirect to logout URL       res.redirect(logout_url)     } catch (err) {       res.redirect("/")     }   })
After successful logout, the user is redirected to the specified return URL, the session is invalidated, and the session cookie is removed [Sign out](https://www.ory.com/docs/identities/get-started/sign-out) [Logout flow](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#logout-flow). --- ### 2. Native/API Logout Flow (Ory Kratos) For native or API clients (such as mobile apps), use the session token to log out: - Call the
/self-service/logout/api
endpoint with the session token in the request body. Example (TypeScript):
Copy code
ts await frontend.performNativeLogout({   performNativeLogoutBody: {     session_token: token,   }, })
This revokes the session token and logs out the user [Native API applications](https://www.ory.com/docs/kratos/self-service/flows/user-logout#native-api-applications). --- ### 3. OpenID Connect/OAuth2 Logout (Ory Hydra) For OIDC/OAuth2, Ory supports: - Front-Channel Logout: Redirects the browser to a registered
frontchannel_logout_uri
after logout. - Back-Channel Logout: Sends a POST request with a
logout_token
to a registered
backchannel_logout_uri
when the user logs out. To initiate logout, call the
/oauth2/sessions/logout
endpoint. You can use parameters like
post_logout_redirect_uri
,
state
, and
id_token_hint
to control the redirect and state after logout [OpenID Connect logout](https://www.ory.com/docs/oauth2-oidc/oidc-logout#redirect-after-logout). --- ### Summary Table | Application Type | Endpoint | Method | Notes | |------------------|------------------------------------------|----------|-----------------------------------------------------------------------| | Browser |
/self-service/logout/browser
| GET | Generates logout URL, then redirect user to it | | Browser |
/self-service/logout
| GET | Completes logout with token from previous step | | Native/API |
/self-service/logout/api
| DELETE | Send session token in body | | OIDC/OAuth2 |
/oauth2/sessions/logout
| GET | Initiates OIDC/OAuth2 logout, supports redirect and state parameters | --- For more details and code samples, see the [Ory Kratos Logout documentation](https://www.ory.com/docs/kratos/self-service/flows/user-logout) and [Ory OAuth2 & OpenID Connect logout documentation](https://www.ory.com/docs/oauth2-oidc/oidc-logout).
b
@chilly-king-10285 how do i notify my backend that a user has verified thier account
c
A message from kapa.ai
To notify your backend when a user has verified their account in Ory Kratos, you can use Ory Actions with webhooks. Specifically, you can configure a webhook to trigger after a successful verification flow. Here's how to set it up: ### 1. Configure a Webhook for the Verification Flow Add a webhook action to the
after
verification flow in your Kratos configuration. This webhook will be called when a user successfully verifies their account. Example configuration:
Copy code
yaml selfservice:   flows:     verification:       enabled: true       use: code  # or 'link'       after:         hooks:           - hook: web_hook             config:               url: <https://your-backend.com/webhooks/verification-success>               method: POST               body: <base64://ENCODED_JSONNET>  # Optional: customize the payload               auth:                 type: api_key  # Optional: if your backend requires authentication                 config:                   name: Authorization                   value: Bearer YOUR_API_KEY                   in: header
### 2. Customize the Webhook Payload (Optional) You can use a Jsonnet template to customize the data sent to your backend. For example: verification-webhook.jsonnet:
Copy code
jsonnet function(ctx) {   user_id: ctx.identity.id,   email: ctx.identity.verifiable_addresses[0].value,   verified: ctx.identity.verifiable_addresses[0].verified,   verification_status: ctx.identity.verifiable_addresses[0].status }
Base64-encode this file and reference it in the
body
field:
Copy code
bash cat verification-webhook.jsonnet | base64 | pbcopy
Then update your config:
Copy code
yaml body: <base64://ENCODED_JSONNET>
### 3. Handle the Webhook in Your Backend Your backend should listen for POST requests at the configured endpoint (e.g.,
/webhooks/verification-success
) and process the verification event. The webhook payload will include the identity data, including the verified status. Example payload received by your backend:
Copy code
json {   "user_id": "abc123",   "email": "[email protected]",   "verified": true,   "verification_status": "completed" }
### 4. Apply the Configuration Update your Ory Identities configuration using the Ory CLI:
Copy code
bash ory update identity-config --project <project-id> --workspace <workspace-id> --file updated_config.yaml
--- ### Alternative: Use Live Event Streams If you need real-time notifications for verification events, you can also use Ory Live Event Streams. The
VerificationSucceeded
event is emitted when a user successfully verifies their account [Event names](https://www.ory.com/docs/actions/live-events#event-names). --- ### Summary Configure a webhook in the
after
verification flow to notify your backend when a user verifies their account. Use Jsonnet to customize the payload if needed, and handle the webhook in your backend to process the verification event. For more details, see: - [Trigger custom logic with webhooks](https://www.ory.com/docs/guides/integrate-with-ory-cloud-through-webhooks) - [Configure hooks](https://www.ory.com/docs/kratos/hooks/configure-hooks) - [Live event streams](https://www.ory.com/docs/actions/live-events)
b
@chilly-king-10285 i get this The configuration contains values or keys which are invalid: selfservice.flows: map[aftermap[default browser return urlhttp://localhost:3000/main/overview/] errormap[ui urlhttps://www.ory.sh/kratos/docs/fallback/error] loginmap[lifespan1h ui_url:http://localhost:3000/auth/login] recoverymap[enabledfalse lifespan:1h notify_unknown_recipients:false ui_url:http://localhost:3000/auth/recovery use:code] registrationmap[aftermap[codemap[hooks[map[hook:session]]]] enabled:true lifespan:1h login_hints:false ui_url:http://localhost:3000/auth/register] settingsmap[lifespan1h privileged_session_max_age:1h required_aal:highest_available ui_url:http://localhost:3000/auth/settings] verificationmap[aftermap[default_browser_return_url:http://localhost:3000/onboarding hooks[map[configmap[bodyfile///etc/config/kratos/hooks/verification.jsonnet method:POST url:http://localhost:8080/api/v1/account/webhook/email-verification] hook:web_hook]]] enabled:true lifespan:10m notify_unknown_recipients:false ui_url:http://localhost:3000/account/verify use:link]] ^-- additionalProperties "after" not allowed time=2025-10-18T224024Z level=info msg=A change to a configuration file was detected. audience=application event_type=*watcherx.ChangeEvent file=/etc/config/kratos/kratos.yml service_name=Ory Kratos service_version=v1.1.0 time=2025-10-18T224024Z level=error msg=The changed configuration is invalid and could not be loaded. Rolling back to the last working configuration revision. Please address the validation errors before restarting the process. audience=application event=&jsonschema.ValidationError{Message:"additionalProperties \"after\" not allowed", InstancePtr:"#/selfservice/flows", SchemaURL:"https://github.com/ory/kratos/embedx/config.schema.json", SchemaPtr:"#/properties/selfservice/properties/flows/additionalProperties", Context:jsonschema.ValidationErrorContext(nil), Causes:[]*jsonschema.ValidationError(nil)} service_name=Ory Kratos service_version=v1.1.0
c
A message from kapa.ai
The error message indicates that you have an
after
configuration at the wrong level in your
selfservice.flows
section. Specifically, you have:
Copy code
yaml selfservice:   flows:     after:       default_browser_return_url: <http://localhost:3000/main/overview/>
This is not allowed. The
after
configuration should be nested inside specific flows (like
registration
,
verification
, etc.), not at the top level of
selfservice.flows
. ### How to Fix Remove the top-level
after
configuration and place it inside the appropriate flow. For example, if you want to set a default redirect after registration, it should be:
Copy code
yaml selfservice:   flows:     registration:       after:         default_browser_return_url: <http://localhost:3000/main/overview/>
### Corrected Configuration Structure Here's the corrected structure based on your config:
Copy code
yaml selfservice:   flows:     error:       ui_url: <https://www.ory.sh/kratos/docs/fallback/error>          login:       lifespan: 1h       ui_url: <http://localhost:3000/auth/login>          recovery:       enabled: false       lifespan: 1h       notify_unknown_recipients: false       ui_url: <http://localhost:3000/auth/recovery>       use: code          registration:       enabled: true       lifespan: 1h       login_hints: false       ui_url: <http://localhost:3000/auth/register>       after:         default_browser_return_url: <http://localhost:3000/main/overview/>  # Move here if needed         code:           hooks:             - hook: session          settings:       lifespan: 1h       privileged_session_max_age: 1h       required_aal: highest_available       ui_url: <http://localhost:3000/auth/settings>          verification:       enabled: true       lifespan: 10m       notify_unknown_recipients: false       ui_url: <http://localhost:3000/account/verify>       use: link       after:         default_browser_return_url: <http://localhost:3000/onboarding>         hooks:           - hook: web_hook             config:               url: <http://localhost:8080/api/v1/account/webhook/email-verification>               method: POST               body: file:///etc/config/kratos/hooks/verification.jsonnet
### Key Points - The
after
configuration must be inside a specific flow (e.g.,
registration
,
verification
), not at the top level of
selfservice.flows
. - Each flow can have its own
after
section with hooks and redirect URLs. For more details on configuring hooks and flows, see the [Ory Kratos hooks documentation](https://www.ory.com/docs/kratos/hooks/configure-hooks).
b
i get this. The configuration contains values or keys which are invalid: selfservice.flows: map[aftermap[default browser return urlhttp://localhost:3000/main/overview/] errormap[ui urlhttps://www.ory.sh/kratos/docs/fallback/error] loginmap[lifespan1h ui_url:http://localhost:3000/auth/login] recoverymap[enabledfalse lifespan:1h notify_unknown_recipients:false ui_url:http://localhost:3000/auth/recovery use:code] registrationmap[aftermap[codemap[hooks[map[hook:session]]]] enabled:true lifespan:1h login_hints:false ui_url:http://localhost:3000/auth/register] settingsmap[lifespan1h privileged_session_max_age:1h required_aal:highest_available ui_url:http://localhost:3000/auth/settings] verificationmap[aftermap[default_browser_return_url:http://localhost:3000/onboarding hooks[map[configmap[bodyfile///etc/config/kratos/hooks/verification.jsonnet method:POST url:http://localhost:8080/api/v1/account/webhook/email-verification] hook:web_hook]]] enabled:true lifespan:10m notify_unknown_recipients:false ui_url:http://localhost:3000/account/verify use:link]] ^-- additionalProperties "after" not allowed time=2025-10-18T224024Z level=info msg=A change to a configuration file was detected. audience=application event_type=*watcherx.ChangeEvent file=/etc/config/kratos/kratos.yml service_name=Ory Kratos service_version=v1.1.0 time=2025-10-18T224024Z level=error msg=The changed configuration is invalid and could not be loaded. Rolling back to the last working configuration revision. Please address the validation errors before restarting the process. audience=application event=&jsonschema.ValidationError{Message:"additionalProperties \"after\" not allowed", InstancePtr:"#/selfservice/flows", SchemaURL:"https://github.com/ory/kratos/embedx/config.schema.json", SchemaPtr:"#/properties/selfservice/properties/flows/additionalProperties", Context:jsonschema.ValidationErrorContext(nil), Causes:[]*jsonschema.ValidationError(nil)} service_name=Ory Kratos service_version=v1.1.0 with this config
Copy code
dsn: <cockroach://root@cockroach:26257/defaultdb?sslmode=disable>

serve:
  public:
    base_url: <http://localhost:4433/>
    cors:
      enabled: true
      allowed_origins:
        - <http://localhost:3000>
      allowed_methods:
        - POST
        - GET
        - PUT
        - PATCH
        - DELETE
        - OPTIONS
      allowed_headers:
        - Authorization
        - Content-Type
        - Cookie
      exposed_headers:
        - Content-Type
        - Set-Cookie
      allow_credentials: true
  admin:
    base_url: <http://localhost:4434/>

selfservice:
  default_browser_return_url: <http://localhost:3000/main/overview>
  flows:
    login:
      ui_url: <http://localhost:3000/auth/login>
    after:
      default_browser_return_url: <http://localhost:3000/main/overview>
    registration:
      enabled: true
      ui_url: <http://localhost:3000/auth/register>
      after:
        code:
          hooks:
            - hook: session
    settings:
      ui_url: <http://localhost:3000/auth/settings>
    recovery:
      ui_url: <http://localhost:3000/auth/recovery>
    verification:
      enabled: true
      ui_url: <http://localhost:3000/account/verify>
      lifespan: 10m
      use: link
      after:
        default_browser_return_url: <http://localhost:3000/onboarding>
        hooks:
          - hook: web_hook
            config:
              url: <http://localhost:8080/api/v1/account/webhook/email-verification>
              method: POST
              body: file:///etc/config/kratos/hooks/verification.jsonnet
  methods:
    password:
      enabled: true
    link:
      enabled: true
identity:
  default_schema_id: default
  schemas:
    - id: default
      url: file:///etc/config/kratos/identity-schemas/user.schema.json

courier:
  delivery_strategy: smtp
  smtp:
    connection_uri: "<smtps://resend:[email protected]:465>"
    from_address: "[email protected]"
  template_override_path: /etc/config/kratos/email-templates
  templates:
    verification:
      valid:
        email:
          body:
            html: file:///etc/config/kratos/email-templates/verification/body.html.gotmpl
@chilly-king-10285 i get this. The configuration contains values or keys which are invalid: selfservice.flows: map[aftermap[default browser return urlhttp://localhost:3000/main/overview/] errormap[ui urlhttps://www.ory.sh/kratos/docs/fallback/error] loginmap[lifespan1h ui_url:http://localhost:3000/auth/login] recoverymap[enabledfalse lifespan:1h notify_unknown_recipients:false ui_url:http://localhost:3000/auth/recovery use:code] registrationmap[aftermap[codemap[hooks[map[hook:session]]]] enabled:true lifespan:1h login_hints:false ui_url:http://localhost:3000/auth/register] settingsmap[lifespan1h privileged_session_max_age:1h required_aal:highest_available ui_url:http://localhost:3000/auth/settings] verificationmap[aftermap[default_browser_return_url:http://localhost:3000/onboarding hooks[map[configmap[bodyfile///etc/config/kratos/hooks/verification.jsonnet method:POST url:http://localhost:8080/api/v1/account/webhook/email-verification] hook:web_hook]]] enabled:true lifespan:10m notify_unknown_recipients:false ui_url:http://localhost:3000/account/verify use:link]] ^-- additionalProperties "after" not allowed time=2025-10-18T224024Z level=info msg=A change to a configuration file was detected. audience=application event_type=*watcherx.ChangeEvent file=/etc/config/kratos/kratos.yml service_name=Ory Kratos service_version=v1.1.0 time=2025-10-18T224024Z level=error msg=The changed configuration is invalid and could not be loaded. Rolling back to the last working configuration revision. Please address the validation errors before restarting the process. audience=application event=&jsonschema.ValidationError{Message:"additionalProperties \"after\" not allowed", InstancePtr:"#/selfservice/flows", SchemaURL:"https://github.com/ory/kratos/embedx/config.schema.json", SchemaPtr:"#/properties/selfservice/properties/flows/additionalProperties", Context:jsonschema.ValidationErrorContext(nil), Causes:[]*jsonschema.ValidationError(nil)} service_name=Ory Kratos service_version=v1.1.0 with this config
Copy code
dsn: <cockroach://root@cockroach:26257/defaultdb?sslmode=disable>

serve:
  public:
    base_url: <http://localhost:4433/>
    cors:
      enabled: true
      allowed_origins:
        - <http://localhost:3000>
      allowed_methods:
        - POST
        - GET
        - PUT
        - PATCH
        - DELETE
        - OPTIONS
      allowed_headers:
        - Authorization
        - Content-Type
        - Cookie
      exposed_headers:
        - Content-Type
        - Set-Cookie
      allow_credentials: true
  admin:
    base_url: <http://localhost:4434/>

selfservice:
  default_browser_return_url: <http://localhost:3000/main/overview>
  flows:
    login:
      ui_url: <http://localhost:3000/auth/login>
    after:
      default_browser_return_url: <http://localhost:3000/main/overview>
    registration:
      enabled: true
      ui_url: <http://localhost:3000/auth/register>
      after:
        code:
          hooks:
            - hook: session
    settings:
      ui_url: <http://localhost:3000/auth/settings>
    recovery:
      ui_url: <http://localhost:3000/auth/recovery>
    verification:
      enabled: true
      ui_url: <http://localhost:3000/account/verify>
      lifespan: 10m
      use: link
      after:
        default_browser_return_url: <http://localhost:3000/onboarding>
        hooks:
          - hook: web_hook
            config:
              url: <http://localhost:8080/api/v1/account/webhook/email-verification>
              method: POST
              body: file:///etc/config/kratos/hooks/verification.jsonnet
  methods:
    password:
      enabled: true
    link:
      enabled: true
identity:
  default_schema_id: default
  schemas:
    - id: default
      url: file:///etc/config/kratos/identity-schemas/user.schema.json

courier:
  delivery_strategy: smtp
  smtp:
    connection_uri: "<smtps://resend:[email protected]:465>"
    from_address: "[email protected]"
  template_override_path: /etc/config/kratos/email-templates
  templates:
    verification:
      valid:
        email:
          body:
            html: file:///etc/config/kratos/email-templates/verification/body.html.gotmpl
c
A message from kapa.ai
The error message indicates that you have an
after
configuration at the wrong level in your
selfservice.flows
section. Looking at your config, you have:
Copy code
yaml selfservice:   flows:     login:       ui_url: <http://localhost:3000/auth/login>     after:       default_browser_return_url: <http://localhost:3000/main/overview>
This is not allowed. The
after
configuration cannot be a direct child of
selfservice.flows
. It must be nested inside specific flows (like
login
,
registration
, etc.). ### How to Fix Remove the standalone
after
block and place it inside the
login
flow (or whichever flow you want to configure). Here's the corrected configuration:
Copy code
yaml dsn: <cockroach://root@cockroach:26257/defaultdb?sslmode=disable>  serve:   public:     base_url: <http://localhost:4433/>     cors:       enabled: true       allowed_origins:         - <http://localhost:3000>       allowed_methods:         - POST         - GET         - PUT         - PATCH         - DELETE         - OPTIONS       allowed_headers:         - Authorization         - Content-Type         - Cookie       exposed_headers:         - Content-Type         - Set-Cookie       allow_credentials: true   admin:     base_url: <http://localhost:4434/>  selfservice:   default_browser_return_url: <http://localhost:3000/main/overview>   flows:     login:       ui_url: <http://localhost:3000/auth/login>       after:         default_browser_return_url: <http://localhost:3000/main/overview>     registration:       enabled: true       ui_url: <http://localhost:3000/auth/register>       after:         code:           hooks:             - hook: session     settings:       ui_url: <http://localhost:3000/auth/settings>     recovery:       ui_url: <http://localhost:3000/auth/recovery>     verification:       enabled: true       ui_url: <http://localhost:3000/account/verify>       lifespan: 10m       use: link       after:         default_browser_return_url: <http://localhost:3000/onboarding>         hooks:           - hook: web_hook             config:               url: <http://localhost:8080/api/v1/account/webhook/email-verification>               method: POST               body: file:///etc/config/kratos/hooks/verification.jsonnet   methods:     password:       enabled: true     link:       enabled: true  identity:   default_schema_id: default   schemas:     - id: default       url: file:///etc/config/kratos/identity-schemas/user.schema.json  courier:   delivery_strategy: smtp   smtp:     connection_uri: "<smtps://resend:[email protected]:465>"     from_address: "[email protected]"   template_override_path: /etc/config/kratos/email-templates   templates:     verification:       valid:         email:           body:             html: file:///etc/config/kratos/email-templates/verification/body.html.gotmpl
### Key Changes - Moved the
after
block from being a direct child of
selfservice.flows
to inside the
login
flow. - Each flow (login, registration, verification, etc.) can have its own
after
section with hooks and redirect URLs. This should resolve the validation error. For more details on configuring flows and redirects, see the [Ory Kratos browser redirect documentation](https://www.ory.com/docs/kratos/concepts/browser-redirect-flow-completion).
b
@chilly-king-10285 using ory network console, can i use my own ui designs
c
A message from kapa.ai
Yes, you can use your own UI designs with Ory Network! Ory provides multiple options for integrating custom user interfaces with your project. ### Options for Custom UI 1. Custom User Interface with Ory SDKs: You can build a fully custom UI using Ory SDKs, which gives you complete control over the design and user experience. This approach uses Ory's headless APIs to integrate authentication flows into your custom interface [Custom UI Quickstarts](https://www.ory.com/docs/getting-started/custom-ui). 2. Custom User Interface with Ory Elements: If you're working with React, you can use [Ory Elements](https://www.ory.com/docs/elements/), a component library that provides pre-built components for authentication flows. This allows you to customize the UI while leveraging ready-made components that automatically adapt to your Ory configuration [Ory Elements Introduction](https://www.ory.com/docs/elements). 3. Configure Custom UI URLs in Ory Console: To point Ory to your custom UI, go to Branding → UI URLs in the [Ory Console](https://console.ory.sh/projects/current/ui). Here you can define the URLs for each self-service flow (login, registration, settings, etc.) to use your custom UI instead of the default Account Experience [Configure Ory to use your UI](https://www.ory.com/docs/kratos/bring-your-own-ui/configure-ory-to-use-your-ui). ### Important Notes - When you define a custom UI base URL, you lose access to the Ory Account Experience. The custom UI base URL takes precedence over the SDK Configuration URL [Configure Ory to use your UI](https://www.ory.com/docs/kratos/bring-your-own-ui/configure-ory-to-use-your-ui). - Ory and your UI must be hosted on the same top-level domain for cookies and sessions to work correctly [Initialize login flow](https://www.ory.com/docs/kratos/self-service/flows/user-login#initialize-login-flow). - For development, you can use [Ory Tunnel](https://www.ory.com/docs/guides/cli/proxy-and-tunnel) to connect your local UI to Ory Network [Development](https://www.ory.com/docs/kratos/bring-your-own-ui/configure-ory-to-use-your-ui#development). ### Getting Started Check out the [custom UI examples and quickstarts](https://www.ory.com/docs/getting-started/custom-ui) for Node.js, Next.js/React, and React Native to help you build your custom UI [Custom UI](https://www.ory.com/docs/getting-started/custom-ui).
b
@chilly-king-10285does ory network url let us configure cors
c
A message from kapa.ai
Yes, Ory Network allows you to configure CORS (Cross-Origin Resource Sharing) for your project. Here's what you need to know: ### CORS Configuration for Ory Network You can configure CORS for both public and admin endpoints in your Ory Network project. CORS is important for Single Page Applications (SPAs) to make cross-origin requests to Ory APIs [Set up CORS for Ory Network](https://www.ory.com/docs/guides/cors). ### How to Enable CORS You can enable CORS using the Ory CLI:
Copy code
bash ory patch project <your-project-id> \   --replace '/cors_public/enabled=true' \   --replace '/cors_public/origins=["https://*.foobar.ory"]'
[Set up CORS for Ory Network](https://www.ory.com/docs/guides/cors) ### CORS Configuration Options The Ory Network API supports configuring: - `cors_public`: CORS settings for public endpoints - `cors_admin`: CORS settings for admin endpoints Each has: - `enabled`: Whether CORS is enabled for the endpoint - `origins`: The allowed origins. You can use wildcards in subdomains (e.g.,
https://*.<http://example.com|example.com>
) [Ory API Reference](https://www.ory.sh/docs/reference/api) ### Important Restrictions - Ory Network allows up to 50 origins - You cannot set CORS origins to
*
,
null
, or
localhost/127.0.0.1
- You can use wildcard subdomains like
https://*.foobar.ory
- For local development with
localhost/127.0.0.1
, use [Ory Tunnel](https://www.ory.com/docs/guides/cli/proxy-and-tunnel) instead [Set up CORS for Ory Network](https://www.ory.com/docs/guides/cors) ### CORS on Custom Domains If you're using custom domains, you can configure specific CORS settings for them. See the [Custom Domains documentation](https://www.ory.com/docs/guides/custom-domains#cors) for details. ### Supported Endpoints All public endpoints apply the global CORS settings. For OAuth2, per-client CORS settings are also supported if CORS is enabled globally [Set up CORS for Ory Network](https://www.ory.com/docs/guides/cors).
b
@chilly-king-10285 getting this in ory network console Access to XMLHttpRequest at 'https://kind-poincare-9pwd3nyapa.projects.oryapis.com/self-service/login/flows?id=71dd3c52-2692-4225-a03b-054e078a6bf1' from origin 'https://app.useinvolt.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
c
A message from kapa.ai
This error means the browser is calling your project slug URL directly and CORS for that origin is not (or not correctly) configured. There are two relevant CORS configurations in Ory Network: 1. Global project CORS (
cors_public
)
– applies to
https://<project-slug>.<http://projects.oryapis.com|projects.oryapis.com>
.[\[CORS guide\]](https://www.ory.com/docs/guides/cors) 2. Custom domain CORS – applies if you front Ory with your own domain (e.g.
<https://auth.example.com>
).[\[Custom domains\]](https://www.ory.com/docs/guides/custom-domains#cors) You are calling: >
<https://kind-poincare-9pwd3nyapa.projects.oryapis.com/>...
from >
<https://app.useinvolt.com>
So you must configure global project CORS and allow
<https://app.useinvolt.com>
. ### 1. Enable CORS for your project Use the Ory CLI and set `cors_public`:
Copy code
bash ory patch project <your-project-id> \   --replace '/cors_public/enabled=true' \   --replace '/cors_public/origins=["<https://app.useinvolt.com>"]'
You can also add more origins up to the 50-origin limit.[\[CORS guide\]](https://www.ory.com/docs/guides/cors) Notes: - Origins must be exact (
<https://app.useinvolt.com>
, not just the domain). -
*
,
null
,
localhost
,
127.0.0.1
are not allowed as origins; use a real domain or wildcards like
https://*.<http://example.com|example.com>
.[\[CORS guide\]](https://www.ory.com/docs/guides/cors) ### 2. Make sure you don’t mix CORS configs If you also use a custom domain for Ory, configure CORS either: - on the custom domain entry or - globally, but don’t stack conflicting settings; this can cause CORS to appear “not working”.[\[Custom-domain CORS note\]](https://github.com/ory/network/issues/427) ### 3. Call the right URL and send credentials From your SPA: - Use the project slug base URL you configured CORS for. - Send cookies with
withCredentials: true
/
credentials: "include"
when needed, as described in the cookie/CORS docs.[\[Cookie model\]](https://www.ory.com/docs/security-model#cross-origin-http-cookies) Example (fetch):
Copy code
js fetch("<https://kind-poincare-9pwd3nyapa.projects.oryapis.com/self-service/login/flows?id=...>", {   credentials: "include", })
If, after setting
cors_public
as above, you still don’t see an
Access-Control-Allow-Origin
header for that origin, double‑check that: - The origin string in
cors_public.origens
exactly matches
<https://app.useinvolt.com>
. - You patched the correct project ID.
b
@kapa I am using https://auth.useinvolt.com asa cname pointing to my ory network ul. domain have been configured and validated. i am getting Access to XMLHttpRequest at 'https://kind-poincare-9pwd3nyapa.projects.oryapis.com/sessions/whoami' from origin 'https://app.useinvolt.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
@chilly-king-10285 I am using https://auth.useinvolt.com asa cname pointing to my ory network ul. domain have been configured and validated. i am getting Access to XMLHttpRequest at 'https://kind-poincare-9pwd3nyapa.projects.oryapis.com/sessions/whoami' from origin 'https://app.useinvolt.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
c
A message from kapa.ai
You’re fronting Ory Network with a custom domain (
<https://auth.useinvolt.com>
→ CNAME →
<http://kind-poincare-9pwd3nyapa.projects.oryapis.com|kind-poincare-9pwd3nyapa.projects.oryapis.com>
), but your SPA is still calling the project slug URL directly: - Browser origin:
<https://app.useinvolt.com>
- Request URL:
<https://kind-poincare-9pwd3nyapa.projects.oryapis.com/sessions/whoami>
- Error: no
Access-Control-Allow-Origin
Two important points from the docs: 1. When you add a custom domain, that domain becomes your SDK Configuration URL – it’s the base URL your frontend should call. [[Custom domains](https://www.ory.com/docs/guides/custom-domains)] 2. CORS for custom domains is configured on the custom domain itself (and is enabled by default, but you must add allowed origins). [[Custom domains CORS](https://www.ory.com/docs/guides/custom-domains#cors)] So right now: - Your browser origin
<https://app.useinvolt.com>
is making CORS requests against the slug domain, which uses the global project CORS config. - But you actually want to use the custom domain (
<https://auth.useinvolt.com>
) with its own CORS settings. ### Fix 1. Call the custom domain from your SPA Change all frontend base URLs / SDK config to:
Copy code
ts const ory = new FrontendApi(   new Configuration({     basePath: "<https://auth.useinvolt.com>",     baseOptions: {       withCredentials: true,     },   }), )
So your
whoami
call becomes:
Copy code
js fetch("<https://auth.useinvolt.com/sessions/whoami>", {   credentials: "include", })
2. Configure CORS on the custom domain In Ory Console: - Go to Branding → Custom domains → auth.useinvolt.com - In the CORS section, add: -
<https://app.useinvolt.com>
(No
*
,
null
, or
localhost
– these are not allowed, but wildcard subdomains like
https://*.<http://example.com|example.com>
are. [[Custom domains CORS](https://www.ory.com/docs/guides/custom-domains#cors)]) 3. Avoid double / conflicting CORS configs For most setups with a custom domain you only need the custom domain CORS, not both custom-domain CORS and global project
cors_public
. Having both can be confusing and has caused issues for others. [[CORS not working custom domain](https://github.com/ory/network/issues/427)] After: - SPA origin:
<https://app.useinvolt.com>
- Ory base URL:
<https://auth.useinvolt.com>
- CORS origin configured on the custom domain for
<https://app.useinvolt.com>
the
Access-Control-Allow-Origin
header should be present and the CORS error should go away.
b
@chilly-king-10285 i am getting Access to XMLHttpRequest at 'https://auth.useinvolt.com/sessions/whoami' from origin 'https://app.useinvolt.com' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values 'https://app.useinvolt.com,https://app.useinvolt.com', but only one is allowed.
c
A message from kapa.ai
This error means CORS headers are being applied twice, so the browser sees:
Copy code
http Access-Control-Allow-Origin: <https://app.useinvolt.com>,<https://app.useinvolt.com>
This exact problem has occurred before when both: 1. Global / service CORS is configured, and 2. Custom domain CORS is also configured for the same origin. In Ory Network this was seen when Hydra’s
public_cors
(or
cors_public
) config and the custom domain CORS both listed the same origin, causing duplicated CORS headers.[[Invalid header](https://github.com/ory/network/issues/316)][[Custom-domain CORS note](https://github.com/ory/network/issues/427)] From the investigation in that issue: > you are using the new CORS settings for Ory Hydra, and also set the CORS domain in the custom hostname. > That causes the domain to be duplicated. To fix that, remove the
public_cors
setting or remove the CORS domain from the custom hostname config.[[Invalid header](https://github.com/ory/network/issues/316)] For your setup: - Frontend origin:
<https://app.useinvolt.com>
- Ory custom domain:
<https://auth.useinvolt.com>
You likely have both: - Project‑level CORS (e.g.
cors_public
/ service CORS) that includes
<https://app.useinvolt.com>
, and - Custom‑domain CORS (on
<http://auth.useinvolt.com|auth.useinvolt.com>
) that also includes
<https://app.useinvolt.com>
. So the browser gets CORS headers twice and merges them into a comma‑separated value. ### How to fix 1. Decide where you want to configure CORS for browser access: - Recommended with custom domains: use only the custom domain CORS on
<http://auth.useinvolt.com|auth.useinvolt.com>
and remove overlapping global/service CORS for that origin.[[Custom domains CORS](https://www.ory.com/docs/guides/custom-domains#cors)][[CORS not working custom domain](https://github.com/ory/network/issues/427)] 2. In practice: - In the Ory Console, under your custom domain
<http://auth.useinvolt.com|auth.useinvolt.com>
, keep
<https://app.useinvolt.com>
in the CORS origins. - Remove
<https://app.useinvolt.com>
(and any wildcards that match it) from: - Global CORS settings (
cors_public
/
cors_admin
) or - Any per‑service CORS you might have configured for the same endpoints. 3. Wait a moment for config to propagate, then call:
Copy code
bash curl -v '<https://auth.useinvolt.com/sessions/whoami>' \   -H 'Origin: <https://app.useinvolt.com>'
You should now see a single
Access-Control-Allow-Origin: <https://app.useinvolt.com>
header. If you’re unsure where the second CORS config is coming from, check: - Project‑level CORS settings in Ory Console / via
ory get project
. - CORS section on the custom domain in Branding → Custom domains.[[Custom domains CORS](https://www.ory.com/docs/guides/custom-domains#cors)]
b
@chilly-king-10285 i removed the glabel config settings as mentioned but still get Access to XMLHttpRequest at 'https://auth.useinvolt.com/self-service/login?flow=970ac87a-86a5-49f8-9103-55a10c040c7f' from origin 'https://app.useinvolt.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
c
A message from kapa.ai
For your current error there are two key points from the docs and issues: 1. Use only the custom domain URL from the browser With a validated custom domain, that domain becomes the SDK/base URL and CORS is configured on that domain, not on the slug URL.[\[Custom domains\]](https://www.ory.com/docs/guides/custom-domains#cors) You’re already calling:
Copy code
text    Origin: <https://app.useinvolt.com>    URL:    <https://auth.useinvolt.com/>...
That part is correct – keep all browser calls on
<https://auth.useinvolt.com>
, not on
kind-poincare-…<http://projects.oryapis.com|projects.oryapis.com>
. 2. CORS is only applied on API (XHR/fetch) endpoints, not on browser redirect endpoints The URL that is failing now is:
Copy code
text    <https://auth.useinvolt.com/self-service/login?flow=>...
This is the browser login URL, which is meant to be navigated to (changed
window.location
/ normal link), not called via XHR/fetch. For browser flows, the SPA should: - First initialize the flow via the API endpoint (SDK does this):
GET /self-service/login/browser
or
/self-service/login/api
- Then redirect the browser to the returned
ui.action
URL, instead of using `fetch`/XHR for that URL.[\[Custom UI integration\]](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#login-flow) If you attempt to
fetch
the browser URL (
/self-service/login?flow=...
), the preflight hits an endpoint that does not send CORS headers and you get exactly the error you’re seeing. So at this point the problem is very likely not missing CORS config on the domain, but calling the wrong URL with XHR. ### What to change in your frontend In your SPA, make sure you: - Use the Ory Network Frontend API endpoints for XHR: -
GET /self-service/login/browser
(or
/api
variant) -
GET /self-service/login/flows?id=...
-
POST
to
flow.ui.action
when submitting the form - Only navigate to the browser URLs (no
fetch
on
/self-service/login?flow=...
):
Copy code
ts   // correct: browser navigation   window.location.href = "<https://auth.useinvolt.com/self-service/login/browser>"
Do not:
Copy code
ts   // wrong: causes CORS error   await fetch("<https://auth.useinvolt.com/self-service/login?flow=...>")
This pattern is the same as in the “Bring your own UI” docs: use the SDK / FrontendApi to talk to
/self-service/*/browser
or
/self-service/*/api
and render the returned flow JSON in your own UI; do not call the redirect URLs via XHR.[\[Custom UI login flow\]](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#login-flow) ### CORS config recap Given you removed the global CORS and only use the custom domain: - In Branding → Custom domains → auth.useinvolt.com, ensure CORS origins include exactly:
Copy code
text   <https://app.useinvolt.com>
Ory Network does not allow
*
,
null
, or
localhost
, but allows exact origins or wildcard subdomains; use an exact match here.[\[Network CORS guide\]](https://www.ory.com/docs/guides/cors)[\[Custom domains CORS\]](https://www.ory.com/docs/guides/custom-domains#cors) If you still see issues after switching away from
fetch
on
/self-service/login?flow=...
, the knowledge sources don’t provide more specific debugging steps for this exact combination; at that point I’d recommend posting the exact request/response headers to the Ory community or support.
b
@chilly-king-10285 i get this i get this while trying to regiuster a user in my email service. 11-25-2025 085153 PM [90m2025-11-25T205153Z[0m [33mWRN[0m [1mHTTP Request[0m [36mip=[0m35.242.228.133 [36mlatency=[0m0.258494 [36mmethod=[0mPOST [36mpath=[0m/api/v1/email/send [36mstatus=[0m400 [36muser-agent=[0mGo-http-client/1.1 11-25-2025 085154 PM [90m2025-11-25T205154Z[0m [33mWRN[0m [1mInvalid email request[0m [36merror=[0m[31m[1m"json: cannot unmarshal string into Go struct field SendEmailRequest.to of type []string"[0m[0m 11-25-2025 085154 PM [90m2025-11-25T205154Z[0m [33mWRN[0m [1mHTTP Request[0m [36mip=[0m35.242.228.133 [36mlatency=[0m0.279712 [36mmethod=[0mPOST [36mpath=[0m/api/v1/email/send [36mstatus=[0m400 [36muser-agent=[0mGo-http-client/1.1 11-25-2025 085156 PM [90m2025-11-25T205156Z[0m [33mWRN[0m [1mInvalid email request[0m [36merror=[0m[31m[1m"json: cannot unmarshal string into Go struct field SendEmailRequest.to of type []string"[0m[0m 11-25-2025 085156 PM [90m2025-11-25T205156Z[0m [33mWRN[0m [1mHTTP Request[0m [36mip=[0m35.242.228.133 [36mlatency=[0m0.299401 [36mmethod=[0mPOST [36mpath=[0m/api/v1/email/send [36mstatus=[0m400 [36muser-agent=[0mGo-http-client/1.1 11-25-2025 085158 PM [90m2025-11-25T205158Z[0m [33mWRN[0m [1mInvalid email request[0m [36merror=[0m[31m[1m"json: cannot unmarshal string into Go struct field SendEmailRequest.to of type []string"[0m[0m 11-25-2025 085158 PM [90m2025-11-25T205158Z[0m [33mWRN[0m [1mHTTP Request[0m [36mip=[0m35.242.228.133 [36mlatency=[0m0.248262 [36mmethod=[0mPOST [36mpath=[0m/api/v1/email/send [36mstatus=[0m400 [36muser-agent=[0mGo-http-client/1.1 11-25-2025 085200 PM [90m2025-11-25T205200Z[0m [33mWRN[0m [1mInvalid email request[0m [36merror=[0m[31m[1m"json: cannot unmarshal string into Go struct field SendEmailRequest.to of type []string"[0m[0m 11-25-2025 085200 PM [90m2025-11-25T205200Z[0m [33mWRN[0m [1mHTTP Request[0m [36mip=[0m35.242.228.133 [36mlatency=[0m0.386555 [36mmethod=[0mPOST [36mpath=[0m/api/v1/email/send [36mstatus=[0m400 [36muser-agent=[0mGo-http-client/1.1 11-25-2025 085201 PM [90m2025-11-25T205201Z[0m [32mINF[0m [1mHTTP Request[0m [36mip=[0m169.254.172.3 [36mlatency=[0m0.017065 [36mmethod=[0mGET [36mpath=[0m/health [36mstatus=[0m200 [36muser-agent=[0mGo-http-client/1.1 11-25-2025 085201 PM [90m2025-11-25T205201Z[0m [33mWRN[0m [1mInvalid email request[0m [36merror=[0m[31m[1m"json: cannot unmarshal string into Go struct field SendEmailRequest.to of type []string"[0m[0m WHat is the object being sent to my email service
c
A message from kapa.ai
Your email service log says: >
json: cannot unmarshal string into Go struct field <http://SendEmailRequest.to|SendEmailRequest.to> of type []string
So your service expects
to
to be an array of strings, but Ory is currently sending it as a single string. From the Ory email HTTP delivery docs, the default Jsonnet payload Ory generates for HTTP email providers is:
Copy code
jsonnet function(ctx) {   recipient: ctx.recipient,   template_type: ctx.template_type,   to: if "template_data" in ctx && "to" in ctx.template_data then <http://ctx.template_data.to|ctx.template_data.to> else null,   recovery_code: if "template_data" in ctx && "recovery_code" in ctx.template_data then ctx.template_data.recovery_code else null,   recovery_url: if "template_data" in ctx && "recovery_url" in ctx.template_data then ctx.template_data.recovery_url else null,   verification_url: if "template_data" in ctx && "verification_url" in ctx.template_data then ctx.template_data.verification_url else null,   verification_code: if "template_data" in ctx && "verification_code" in ctx.template_data then ctx.template_data.verification_code else null,   login_code: if "template_data" in ctx && "login_code" in ctx.template_data then ctx.template_data.login_code else null,   registration_code: if "template_data" in ctx && "registration_code" in ctx.template_data then ctx.template_data.registration_code else null,   subject: if "template_data" in ctx && "subject" in ctx.template_data then ctx.template_data.subject else null,   body: if "template_data" in ctx && "body" in ctx.template_data then ctx.template_data.body else null }
Here
to
is whatever Kratos puts into
<http://ctx.template_data.to|ctx.template_data.to>
, which is a string email address for verification/recovery templates.[\[HTTP payload\]](https://www.ory.com/docs/kratos/emails-sms/sending-emails-smtp#payload)[\[HTTP email config\]](https://www.ory.com/docs/kratos/self-hosted/email-http#configuration) That means the JSON Ory is posting to your
/api/v1/email/send
looks roughly like:
Copy code
json {   "recipient": "[email protected]",   "template_type": "verification_valid",   "to": "[email protected]",   "verification_url": "https://…",   "subject": "…",   "body": "…" }
Your Go struct expects:
Copy code
go type SendEmailRequest struct {   To []string `json:"to"`   // ... }
so it rejects the string. To fix this, override the Jsonnet body in your Kratos/Ory Network email HTTP config so
to
is an array:
Copy code
jsonnet function(ctx) {   recipient: ctx.recipient,   template_type: ctx.template_type,   to: if "template_data" in ctx && "to" in ctx.template_data         then [ <http://ctx.template_data.to|ctx.template_data.to> ]  // wrap string in array         else [],   subject: if "template_data" in ctx && "subject" in ctx.template_data then ctx.template_data.subject else null,   body: if "template_data" in ctx && "body" in ctx.template_data then ctx.template_data.body else null }
Then reference this Jsonnet in your courier HTTP config (
body: file://...
or
base64://...
).[\[HTTP email config\]](https://www.ory.com/docs/kratos/self-hosted/email-http#configuration)[\[HTTP payload\]](https://www.ory.com/docs/kratos/emails-sms/sending-emails-smtp#payload) So: the object Ory sends is a JSON with
to
as a string; your service needs
to
as
[]string
, so adjust the Jsonnet to wrap it in an array.
b
@chilly-king-10285 i get this during email verification 1. error 2. : 3. {id: "self_service_flow_replaced", code: 410, status: "Gone",…} 4. code 5. : a. 410 6. id 7. : a. "self_service_flow_replaced" 8. message 9. : a. "self-service flow replaced" 10. reason 11. : a. "An error occured, please use the new flow." 12. status 13. : a. "Gone" 14. use_flow_id 15. : 16. "98671a07-5644-4eec-b3eb-2d162955cd8b"
c
A message from kapa.ai
That error means the verification flow you’re using is no longer valid and Kratos/Ory Network is telling you which new flow ID to use instead. From the API spec, a
410 Gone
on self-service flows (login, registration, verification, recovery, etc.) is returned in a standard JSON error with an ID like
self_service_flow_replaced
, and may include a
use_flow_id
field that points to the replacement flow. [[getLoginFlow](https://www.ory.sh/docs/kratos/reference/api); [updateVerificationFlow](https://www.ory.sh/docs/kratos/reference/api)] In your case: -
code: 410
,
id: "self_service_flow_replaced"
→ current flow is no longer usable. -
use_flow_id: "98671a07-5644-4eec-b3eb-2d162955cd8b"
→ this is the new verification flow you must use. This typically happens when: - The flow has expired (check
lifespan
in
selfservice.flows.verification
), or - The server decided to issue a fresh flow (common if something about the original request is no longer consistent). ### What you should do in your UI / client When you receive this error during verification: 1. Read
use_flow_id
from the error.
2. Reload the flow using that ID, instead of the old one:
Copy code
http    GET /self-service/verification/flows?id=98671a07-5644-4eec-b3eb-2d162955cd8b
(or via the SDK’s
getVerificationFlow
/
frontend.getVerificationFlow
call). [[getRecoveryFlow](https://www.ory.sh/docs/kratos/reference/api); [getRegistrationFlow](https://www.ory.sh/docs/reference/api)] 3. Render the new flow in your UI and let the user continue. If you don’t handle this and keep calling your backend with the old flow ID, you’ll keep getting
410 self_service_flow_replaced
. If you need concrete code for your specific stack (React/Next/etc.), share how you’re currently fetching the verification flow and I can show how to plug in the
use_flow_id
handling.
b
@chilly-king-10285 how do you use the new flow id
c
A message from kapa.ai
When you get
self_service_flow_replaced
(410) with a
use_flow_id
, you must stop using the old flow ID and fetch/render the new flow using that
use_flow_id
. The pattern is the same as in the docs for handling expired flows: when you see 410, redirect or refetch with a new flow ID. [[Create verification flow](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#create-verification-flow)] ### Client-side (React) example If you’re currently doing something like this for verification:
Copy code
ts frontend   .getVerificationFlow({ id })   .then(({ data: flow }) => setFlow(flow))   .catch((err) => {     // handle error   })
extend the error handling to use
use_flow_id
when you see 410:
Copy code
ts frontend   .getVerificationFlow({ id })   .then(({ data: flow }) => setFlow(flow))   .catch((err: AxiosError<any>) => {     if (err.response?.status === 410) {       const newId = err.response.data?.use_flow_id       if (newId) {         // refetch with the new flow id         frontend           .getVerificationFlow({ id: newId })           .then(({ data: flow }) => setFlow(flow))       } else {         // fallback: start a brand new flow by redirecting         window.location.href = "/account/verify" // your verification UI URL       }     } else {       // other error handling     }   })
The same idea applies when submitting a flow (
updateVerificationFlow
):
Copy code
ts frontend   .updateVerificationFlow({ flow: flow.id, updateVerificationFlowBody: body })   .then(() => { /* success */ })   .catch((err: AxiosError<any>) => {     if (err.response?.status === 410) {       const newId = err.response.data?.use_flow_id       if (newId) {         // reload page / router to use ?flow=newId         navigate(`/account/verify?flow=${newId}`, { replace: true })       } else {         navigate("/account/verify", { replace: true })       }     }     // handle 400 (validation) etc.   })
### Server-side example (similar to docs) In the server-rendered example from the docs, when
getVerificationFlow
returns 410, they redirect to the init URL to create a new flow; your variant can also read
use_flow_id
if present. [[Create verification flow](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#create-verification-flow)]
Copy code
ts sdk   .getVerificationFlow({ id: flow, cookie: req.header("cookie") })   .then(({ data: flow }) => { /* render */ })   .catch((err) => {     if (err.response?.status === 410) {       // start a fresh flow       res.redirect(303, initFlowUrl)       return     }   })
### Summary -
self_service_flow_replaced
+
use_flow_id
means: throw away the old ID and use
use_flow_id
. - Practically: on 410, either: - refetch the flow with
GET /self-service/.../flows?id=<use_flow_id>
, or - redirect the user to your flow init URL (e.g.
/verification
) so a fresh flow is created.
b
@chilly-king-10285 Access to XMLHttpRequest at 'https://auth.useinvolt.com/self-service/registration?flow=e599ff43-a714-42af-8f7a-628000ae5768' from origin 'https://app.useinvolt.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
c
A message from kapa.ai
The endpoint you’re calling is the root cause:
Copy code
text <https://auth.useinvolt.com/self-service/registration?flow=>...
This is the browser redirect URL of the registration flow, not the JSON API endpoint. When you call it with `XMLHttpRequest`/`fetch`, the browser sends a CORS preflight, but that endpoint is not meant for XHR and does not send CORS headers, so you get: > No
Access-Control-Allow-Origin
header is present on the requested resource. From the self-service docs, the pattern is: - Use the browser/API endpoints for AJAX: - Initialize:
GET /self-service/registration/browser
with
Accept: application/json
for SPA. [[Registration AJAX](https://www.ory.com/docs/kratos/self-service/flows/user-registration#registration-for-client-side-ajax-browser-clients)] - Fetch flow:
GET /self-service/registration/flows?id=<flow-id>
- Submit:
POST
to
flow.ui.action
with
credentials: "include"
and CSRF token, all under the same top‑level domain and with CORS configured. [[Cookie security](https://www.ory.com/docs/security-model#cross-origin-http-cookies)] - Do not XHR the plain
/self-service/registration?flow=...
browser URL – that one is for normal browser navigation / redirects, not CORS/XHR. Given your setup: - Ory custom domain:
<https://auth.useinvolt.com>
- App origin:
<https://app.useinvolt.com>
- CORS for the custom domain is configured in the Ory Console under that custom domain entry. [[Custom-domain CORS](https://www.ory.com/docs/guides/custom-domains#cors)] What to change in your frontend: 1. Ensure you are calling flow APIs, not the redirect URL, from XHR. For example (using fetch):
Copy code
js    // initialize flow    fetch("<https://auth.useinvolt.com/self-service/registration/browser>", {      credentials: "include",      headers: { Accept: "application/json" },    }).then(r => r.json()).then(flow => {      // use flow.ui.action, flow.id, etc.    })
2. When you need to navigate the user (e.g. to hosted UI), use
window.location
or a link to
/self-service/registration/browser
, not `fetch`:
Copy code
js    window.location.href = "<https://auth.useinvolt.com/self-service/registration/browser>"
3. Keep CORS origins on the custom domain set to exactly
<https://app.useinvolt.com>
(no
*
,
null
, or
localhost
). [[CORS guide](https://www.ory.com/docs/guides/custom-domains#cors)] If you still see this error while calling only the JSON registration flow endpoints (
/self-service/registration/browser
or
/self-service/registration/flows
), then the knowledge here doesn’t provide more detailed debugging steps for that combination, and you’d want to capture the full request/response headers and share them with Ory support or the community.
b
@chilly-king-10285 Both Login and Signup pages are already using the correct pattern: 1. Flow Initialization: Using browser redirect to /self-service/*/browser endpoints (lines 57 and 46) 2. Flow Fetching: Using the SDK's getLoginFlow and getRegistrationFlow which call the correct API endpoints with CORS support 3. Flow Submission: Using the SDK's submitLoginFlow and submitRegistrationFlow methods The implementation is correct. The CORS configuration I have is already set up properly: { "allowed_origins": [ "https://app.useinvolt.com", "http://localhost:3001" ], "enabled": true } This allows my SPA at https://app.useinvolt.com to make AJAX calls to the Ory API endpoints (like /self-service/login/flows?id=... and the submission endpoints), while the browser redirects to /self-service/*/browser don't need CORS since they're full-page navigations. My setup is correct! The CORS is properly configured at the Ory project level, and My frontend code is using the right endpoints.
c
A message from kapa.ai
From the perspective of the docs and the issues you’re hitting, there are two important constraints that don’t fully match your current setup: 1. Where CORS is supposed to be configured in Ory Network 2. Which endpoints actually participate in CORS ### 1. CORS configuration location (project vs. custom domain) For Ory Network there are two CORS layers: - Global/project CORS (
cors_public
/
cors_admin
), configured via CLI. [[CORS guide](https://www.ory.com/docs/guides/cors)] - Custom-domain CORS, configured on the custom domain entry in Console. [[Custom domains CORS](https://www.ory.com/docs/guides/custom-domains#cors)] You wrote: > This allows my SPA at https://app.useinvolt.com to make AJAX calls … The CORS is properly configured at the Ory project level… But you are calling
<https://auth.useinvolt.com>
(a custom domain, CNAME to your project). With a custom domain, the docs are explicit: CORS for that hostname is configured on the custom domain, not on the slug / global project URL. [[Custom domains CORS](https://www.ory.com/docs/guides/custom-domains#cors)] If you still have project‑level CORS enabled for the same origins and CORS set on the custom domain, you can get duplicated or conflicting headers (which you already hit earlier). The recommended pattern with a custom domain is: - Configure CORS only on the custom domain for browser clients that use that domain. - Avoid overlapping per‑project / per‑service CORS for the same host/origin pair. [[CORS not working for custom domain](https://github.com/ory/network/issues/427)] So: double‑check that
<https://app.useinvolt.com>
is listed under Branding → Custom domains → auth.useinvolt.com → CORS, and that you’re not also layering global
cors_public
for the same combination. ### 2. Endpoints that actually use CORS Your last error was for:
Copy code
text <https://auth.useinvolt.com/self-service/registration?flow=>...
The docs describe these as browser redirect endpoints, not the JSON API endpoints. They are meant to be hit by normal browser navigation, not by XHR/fetch, and therefore they don’t participate in CORS the way your SPA expects. [[Registration flow – browser vs. AJAX](https://www.ory.com/docs/kratos/self-service/flows/user-registration#registration-for-client-side-ajax-browser-clients)] For SPAs, the documented pattern is: - Initialize with
GET /self-service/registration/browser
and
Accept: application/json
(or use the SDK helper that does this). - Fetch with
GET /self-service/registration/flows?id=<flow-id>
. - Submit by POSTing to
flow.ui.action
. [[Custom UI registration flow](https://www.ory.com/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#registration-flow)] If at any point your code uses `fetch`/XHR directly against
/self-service/registration?flow=...
or
/self-service/login?flow=...
(instead of
/self-service/*/flows?id=...
), you’ll see exactly the preflight CORS failure you pasted, because those “browser” endpoints are not supposed to be used as CORS APIs. You say: > Flow Initialization: Using browser redirect to /self-service/*/browser > Flow Fetching: Using the SDK’s getLoginFlow / getRegistrationFlow > Flow Submission: Using the SDK’s submit* methods That is the pattern the docs describe. At this point, the logs show at least one request is still going to
/self-service/registration?flow=...
via XHR (the error you pasted), so either: - Some code path is still using `fetch`/XHR against that URL, or - A library/SDK wrapper is misconfigured and using the browser URL instead of the flow API URLs. From the available sources, there isn’t more detailed tracing for your concrete code, only the general requirements: - CORS for custom domains must be set on the custom domain entry, not just at “project level”. [[Custom domains CORS](https://www.ory.com/docs/guides/custom-domains#cors)] - SPAs must use the JSON flow endpoints for XHR, not the
?flow=
browser endpoints. [[Registration AJAX](https://www.ory.com/docs/kratos/self-service/flows/user-registration#registration-for-client-side-ajax-browser-clients)] If you’ve checked those and your SDK calls still cause CORS on
…/self-service/registration?flow=…
, the knowledge here doesn’t include more low‑level debugging steps; you’d need to inspect the actual network tab (request URLs and methods for each call) or share that with Ory support/community for deeper investigation.
b
Yes
c
A message from kapa.ai
You’re patching the identity config, but the CORS you’re trying to turn off (
serve.public.cors
) is part of the Kratos service config, which Ory Network does not let you control directly. On Ory Network: - Runtime /
serve.*
, cookies, TLS, etc. are managed by the platform and **cannot be changed**; they’re either ignored or only shown as effective values.[\[Config with CLI](https://www.ory.com/docs/guides/cli/config-with-cli#configure-projects); \[patch identity-config](https://www.ory.com/docs/cli/ory-patch-identity-config#ory-patch-identity-config)] - For CORS on Ory Network you must use the project‑level CORS (
cors_public
/
cors_admin
) and/or custom‑domain CORS, not
serve.public.cors
like in self‑hosted Kratos.[\[Network CORS guide](https://www.ory.com/docs/guides/cors); \[Custom domains CORS](https://www.ory.com/docs/guides/custom-domains#cors)] That’s why your patch:
Copy code
bash ory patch identity-config <project> \   --replace '/serve/public/cors/enabled=false'
produces a big dump of the identity config plus warnings like: > Configuration key
serve.*
can not be set and will be ignored. Those warnings are exactly the platform telling you that
serve.public.cors
(and other
serve.*
keys) are not configurable on Ory Network and are being ignored.[\[patch identity-config](https://www.ory.com/docs/cli/ory-patch-identity-config#ory-patch-identity-config)] ### What you can actually do On Ory Network the supported ways to control CORS are: 1. Global project CORS (for the slug
…<http://projects.oryapis.com|projects.oryapis.com>
and also for services when not using custom domains):
Copy code
bash    ory patch project <project-id> \      --replace '/cors_public/enabled=false'
or to control origins:
Copy code
bash    ory patch project <project-id> \      --replace '/cors_public/origins=["<https://app.useinvolt.com>"]'
The
cors_public
/
cors_admin
schema is documented in the project APIs.[\[setProject](https://www.ory.sh/docs/reference/api#operation/updateProject); \[cors fields](https://www.ory.sh/docs/reference/api#operation/patchProject)] 2. Custom‑domain CORS (for
<https://auth.useinvolt.com>
): configure it in the Console under Branding → Custom domains → your domain → CORS, as described in the custom‑domains docs.[\[Custom domains CORS](https://www.ory.com/docs/guides/custom-domains#cors)] You cannot disable
serve.public.cors
via
ory patch identity-config
on Ory Network; that key is from the self‑hosted Kratos schema and is intentionally ignored in Network.
b
@chilly-king-10285 how do i switch from code verification to link verification in registration
c
A message from kapa.ai
To switch from code verification to link verification for registration (email address verification), you change the verification flow strategy from
code
to
link
in your Identities (Kratos) config. ### 1. Update the verification flow to use
link
In your identity config:
Copy code
yaml selfservice:   methods:     link:       enabled: true       config:         lifespan: 15m   # how long the verification link is valid    flows:     verification:       enabled: true       use: link         # switch from 'code' to 'link'       lifespan: 15m       notify_unknown_recipients: false
This follows the documented pattern where
verification.use
can be
code
or
link
, and the selected method controls how verification emails are sent. [[Address verification](https://www.ory.com/docs/kratos/self-service/flows/verify-email-account-activation#configuration)] ### 2. Make sure your identity schema supports verification Your identity schema must still mark the email as verifiable (this doesn’t change between
code
and
link
):
Copy code
json "email": {   "type": "string",   "format": "email",   "<http://ory.sh/kratos|ory.sh/kratos>": {     "credentials": {       "password": {         "identifier": true       }     },     "verification": {       "via": "email"     }   } }
[[Address verification](https://www.ory.com/docs/kratos/self-service/flows/verify-email-account-activation#identity-schema)] ### 3. Registration behavior - Registration itself doesn’t care whether verification is
code
or `link`; it just triggers the verification flow when verification is enabled. - The method you set (
use: link
) controls how the verification email is sent and which templates are used: - For `link`:
verification.valid
/
verification.invalid
templates are used. - For `code`:
verification_code.valid
/
verification_code.invalid
. [[Email templates](https://www.ory.com/docs/kratos/emails-sms/custom-email-templates)] If you’re requiring verification right after registration (via the
show_verification_ui
hook), that works with both strategies; you’re only changing the email method. [[Verification on sign up](https://www.ory.com/docs/actions/require-verified-address#verification-on-sign-up)] ### 4. Important note The docs mark magic links (
link
) as legacy and scheduled for removal
, and recommend using
code
instead. [[Choosing the right strategy](https://www.ory.com/docs/kratos/self-service/flows/verify-email-account-activation#choosing-the-right-strategy)]
b
@chilly-king-10285 during code verification i get this error { "code": 500, "status": "Internal Server Error", "request": "47d4442e-b23f-9cff-9423-cb90174ee154", "message": "jsonnetsecure: ERROR: RUNTIME ERROR: Field does not exist: verified_at\n\thttps://storage.googleapis.com/bac-gcs-production/1f3ea4979ea4882606e9ca57ecdaa4df8cead8072f8b90a7e38fb6d78dff06ba3349281ab1fbb234425cf9605946acb09aa0566c18a69aadc9880d0f86d20e7d.jsonnet:5:18-38\tobject <anonymous>\n\tField \"verified_at\"\t\n\tDuring manifestation\t\n" } this is my action http body function(ctx) { identity_id: ctx.identity.id, flow_id: ctx.flow.id, event: "verification.success", verified_at: ctx.flow.verified_at, identity: { id: ctx.identity.id, schema_id: ctx.identity.schema_id, state: ctx.identity.state, traits: ctx.identity.traits, verifiable_addresses: ctx.identity.verifiable_addresses } }
c
A message from kapa.ai
jsonnetsecure: ERROR: RUNTIME ERROR: Field does not exist: verified_at
means that
ctx.flow
in your webhook Jsonnet does not have a field named
verified_at
, so Jsonnet throws at:
Copy code
jsonnet verified_at: ctx.flow.verified_at,
In the webhook Jsonnet context,
ctx
contains the full flow object (including
flow.id
,
flow.type
,
flow.ui
, etc.), but the docs’ example payload for flows does not mention a
verified_at
field on the flow itself. Instead you see fields like
expires_at
,
issued_at
,
request_url
,
type
,
ui
, etc. [[Jsonnet templating](https://www.ory.com/docs/guides/integrate-with-ory-cloud-through-webhooks#jsonnet-templating)] Because that property doesn’t exist, any direct access (
ctx.flow.verified_at
) causes a runtime error in Jsonnet, which then surfaces as a 500. You have two options: ### 1. Remove
verified_at
if you don’t strictly need it Just drop that line:
Copy code
jsonnet function(ctx) {   identity_id: ctx.identity.id,   flow_id: ctx.flow.id,   event: "verification.success",   identity: {     id: ctx.identity.id,     schema_id: ctx.identity.schema_id,     state: ctx.identity.state,     traits: ctx.identity.traits,     verifiable_addresses: ctx.identity.verifiable_addresses,   }, }
This will stop the Jsonnet runtime error. ### 2. Make access to
verified_at
safe / optional If you want to keep a timestamp-like field but it may not exist, wrap it in a conditional so Jsonnet doesn’t crash when the field is missing:
Copy code
jsonnet function(ctx) {   identity_id: ctx.identity.id,   flow_id: ctx.flow.id,   event: "verification.success",   verified_at: if "verified_at" in ctx.flow then ctx.flow.verified_at else null,   identity: {     id: ctx.identity.id,     schema_id: ctx.identity.schema_id,     state: ctx.identity.state,     traits: ctx.identity.traits,     verifiable_addresses: ctx.identity.verifiable_addresses,   }, }
Using
if "field" in obj then obj.field else null
is the recommended pattern from the Jsonnet reference to handle optional fields without runtime errors. [[Jsonnet optionality](https://www.ory.com/docs/kratos/reference/jsonnet#optionality)] --- So the fix is: don’t assume
ctx.flow.verified_at
exists; either remove it or guard it with an
if "verified_at" in ctx.flow
check.
b
@chilly-king-10285 i added a templates for account verification in ory console but i keep getting the default Verify your email address Your verification code is: ******** Or click this link to verify: Verify Email
c
A message from kapa.ai
Two things must be true for your custom verification templates to be used in Ory Network: 1. A custom email server must be configured 2. The correct verification template variant must be customized If either is missing or mismatched, Ory falls back to the default templates. --- ### 1. Make sure you’re using a custom email server Custom templates only work when you’ve configured your own SMTP/HTTP email delivery. If you use the default Ory courier, the built‑in templates are always used. [[Using custom templates](https://www.ory.com/docs/kratos/emails-sms/custom-email-templates#using-custom-message-templates)] In Ory Network this means your identity config must have something like:
Copy code
yaml courier:   smtp:     from_name: MyProject via Ory     # or http: ... if using HTTP
and your custom templates under
courier.smtp.templates
/
courier.http
as in the docs. [[Using custom templates](https://www.ory.com/docs/kratos/emails-sms/custom-email-templates#using-custom-message-templates)] If no custom server is configured, your templates in the Console UI are ignored and defaults are used. --- ### 2. Customize the right verification template Which template is used depends on your verification method: - If
selfservice.flows.verification.use: code
→ templates: -
verification_code.valid
-
verification_code.invalid
[[Email templates](https://www.ory.com/docs/kratos/emails-sms/custom-email-templates#email-templates)] - If
use: link
→ templates: -
verification.valid
-
verification.invalid
[[Email templates](https://www.ory.com/docs/kratos/emails-sms/custom-email-templates#email-templates)] Your email content: > “Your verification code is: … Or click this link to verify: …” is the default code template. So: - Check your config:
selfservice.flows.verification.use
is most likely
code
. - In Branding → Email templates, make sure you edited Verification code / valid (and plaintext + HTML) – not just the link‑based variant. [[Account verification templates](https://www.ory.com/docs/kratos/self-service/flows/verify-email-account-activation#email-templates)] --- ### 3. Ensure the templates are actually wired in config In Ory Network, the Console UI writes Base64 templates into the identity config under
courier.smtp.templates
. That section must exist and contain non‑empty bodies, e.g.:
Copy code
yaml courier:   smtp:     templates:       verification_code:         valid:           email:             body:               html: "<base64://ENCODED_HTML>"               plaintext: "<base64://ENCODED_TEXT>"
If the method is unsupported (e.g.
http://
or
file://
URIs) or missing, Ory falls back to defaults. [[Using custom templates](https://www.ory.com/docs/kratos/emails-sms/custom-email-templates#using-custom-message-templates)][[Self-hosted mail templates](https://www.ory.com/docs/kratos/self-hosted/mail-courier-templates)] --- ### 4. Why you still see the default So if you: - Have not configured a custom SMTP/HTTP courier → defaults are used. - Edited
verification.valid
instead of
verification_code.valid
while using
use: code
→ defaults for code are used. - Misconfigured templates (unsupported URL instead of
base64://
in Network) → Ory falls back to defaults. [[Using custom templates](https://www.ory.com/docs/kratos/emails-sms/custom-email-templates#using-custom-message-templates)] --- Next steps 1. Confirm
selfservice.flows.verification.use: code
or
link
. 2. In Console, edit the matching verification template (Verification code vs Verification link) for both HTML and plaintext. 3. Ensure you have a custom email server configured and templates show up under
courier.smtp.templates
in
ory get identity-config
. 4. Verify that the template entries are
base64://…
(not `http://`/`file://`) in Ory Network. [[GitOps + templates](https://www.ory.com/docs/guides/gitops#email-templates); [[Network templates via base64](https://www.ory.com/docs/kratos/emails-sms/custom-email-templates#using-custom-message-templates)]
b
@chilly-king-10285 i am using ory network
c
A message from kapa.ai
On Ory Network, custom verification templates are only used if all of these are true: 1. You have a custom email delivery configured (SMTP or HTTP) Custom templates are ignored when using the default Ory SMTP. You must configure your own server under
courier
for templates to take effect.[\[Send emails](https://www.ory.com/docs/kratos/emails-sms/sending-emails-smtp)\] In Network this means your identity config (via
ory get identity-config
) must have something like:
Copy code
yaml    courier:      delivery_strategy: smtp    # or http      smtp:        connection_uri: ...        from_address: ...        from_name: ...
or an HTTP config. If you don’t see that, Ory is still using the default courier and will always send the built‑in templates.[\[Using custom templates](https://www.ory.com/docs/kratos/emails-sms/custom-email-templates#using-custom-message-templates)\] 2. You edited the template that matches your verification method Which template is used depends on `selfservice.flows.verification.use`:[\[Email templates](https://www.ory.com/docs/kratos/emails-sms/custom-email-templates)\] - If you use code:
Copy code
yaml      selfservice:        flows:          verification:            use: code
then Ory uses: -
verification_code.valid
-
verification_code.invalid
- If you use link:
Copy code
yaml      selfservice:        flows:          verification:            use: link
then Ory uses: -
verification.valid
-
verification.invalid
The text you see: > Your verification code is: … > Or click this link to verify: … is the default code template. So you must customize “Verification code / valid”, not just the link-based “Verification / valid” template.[\[Verification templates](https://www.ory.com/docs/kratos/self-service/flows/verify-email-account-activation#email-templates)\] 3. Templates are stored as Base64 in the identity config On Ory Network, templates must be `base64://…`;
file://
or
http://
are not supported and will make Ory fall back to defaults.[\[Using custom templates](https://www.ory.com/docs/kratos/emails-sms/custom-email-templates#using-custom-message-templates)\] After saving in the Console (Branding → Email templates), check with:
Copy code
bash    ory get identity-config --project <project-id> --format yaml
You should see something like:
Copy code
yaml    courier:      smtp:        templates:          verification_code:            valid:              email:                body:                  html: "<base64://ENCODED_HTML>"                  plaintext: "<base64://ENCODED_TEXT>"
If
body
is
{}
or missing, or uses unsupported URI schemes, Ory will send the default template instead.[\[Self-hosted vs Network templates](https://www.ory.com/docs/kratos/self-service/flows/verify-email-account-activation#email-templates)\[\[Using custom templates](https://www.ory.com/docs/kratos/emails-sms/custom-email-templates#using-custom-message-templates)\] --- What to do now 1. Verify in the Console (Authentication → Account verification) whether
use
is
code
or
link
. 2. In Branding → Email templates, edit the matching verification template: -
Verification code / valid
if
use: code
. 3. Confirm you have a custom SMTP/HTTP configured (Authentication → Email configuration) and that
courier.smtp
or
courier.http
exists in
identity-config
.[\[Send emails using your SMTP server](https://www.ory.com/docs/kratos/emails-sms/sending-emails-smtp#send-emails-using-your-smtp-server)\] 4. Check that the customized template appears under
courier.smtp.templates.verification_code.valid
(for
use: code
) with
base64://…
entries. If all of that is in place and you still see the default text, the knowledge sources don’t provide more detailed troubleshooting steps; in that case, sharing your
ory get identity-config
(redacted) with the Ory community/support would be the next step.
112 Views