<@U04UQ68083H> what does state active mean in Krat...
# ory-copilot
q
@chilly-king-10285 what does state active mean in Kratos?
b
A message from kapa.ai
In Ory Kratos, the
state: "active"
property in an identity object indicates that the identity (user account) is active and can be used. This is part of the identity's state property which can have two possible values: - `active`: The identity is active and can be used - `inactive`: The identity is inactive According to the API documentation, this value currently has no effect on the system's behavior, meaning it's primarily an informational field rather than one that controls functionality. When you see this in a session response or identity object, it's telling you that the user account associated with that session or identity is in an active state. For example, in session responses, you'll typically see something like:
Copy code
json {   "identity": {     "id": "c4d24c01-feba-4213-858b-262c8d427f8c",     "state": "active",     "state_changed_at": "2023-02-03T15:41:22.835918Z",     // other identity properties...   },   // other session properties... }
If an account is not active yet, users might see an error message with code 4000010: "Account not active yet. Did you forget to verify your email address?" This suggests that account activation might be related to email verification in some implementations, though the state field itself doesn't directly control this behavior. [Kratos API Reference](https://www.ory.sh/docs/kratos/reference/api) shows this state property in various API responses related to identity management.
q
@chilly-king-10285 can kratos courier give identity information such as the name of the user?
b
A message from kapa.ai
Yes, Ory Kratos courier can include identity information such as the user's name when sending messages. The courier service is responsible for delivering emails and SMS messages to users, and it can include personalized information from the identity in these messages. When configuring HTTP-based email delivery, for example, the courier passes identity information through the
template_data
variable. This allows you to access user details in your message templates:
Copy code
jsonnet function(ctx) {   recipient: ctx.recipient,   template_type: ctx.template_type,   name: if "template_data" in ctx && "identity" in ctx.template_data && "name" in ctx.template_data.identity then ctx.template_data.identity.name else null,   // other fields... }
As shown in this example, you can access the user's name through
ctx.template_data.identity.name
[HTTP based email delivery](https://www.ory.sh/docs/kratos/self-hosted/email-http#configuration). The identity object is available in various message templates, particularly for: - Recovery emails (valid) - Recovery code emails (valid) - Verification emails (valid) - Verification code emails (valid) When creating custom email templates, you can access the identity information directly. For example, in a recovery email template, you could personalize the greeting with the user's first name:
Copy code
Hello {{ upper .Identity.traits.firstName }},  to recover your account, please enter the following code:
This example shows how to access the user's first name from the identity traits [Email templates in self-hosted Ory Kratos](https://www.ory.sh/docs/kratos/self-hosted/mail-courier-templates). The available identity information depends on your identity schema configuration, but typically includes all the traits you've defined in your schema, such as email, name, phone number, etc.
q
@chilly-king-10285 my traits look like this "traits": { "name": { "last": "Boulpaep", "first": "Luka" }, "email": "lukaboulpaep@outlook.com" }, what do I do to get Luka Boulpaep as name attribute in the json http request of the courrier
b
A message from kapa.ai
Based on your identity traits structure, you want to combine the first and last name to get "Luka Boulpaep" as a single name attribute in the JSON HTTP request of the Ory Kratos courier. To achieve this, you need to customize the Jsonnet template used for the HTTP courier request. Here's how you can do it: In your courier configuration, you need to specify a custom Jsonnet template that combines the first and last name fields from the identity traits. You can create a file with the following Jsonnet code:
Copy code
jsonnet function(ctx) {   recipient: ctx.recipient,   template_type: ctx.template_type,   name: if "template_data" in ctx && "identity" in ctx.template_data && "traits" in ctx.template_data.identity && "name" in ctx.template_data.identity.traits          then ctx.template_data.identity.traits.name.first + " " + ctx.template_data.identity.traits.name.last          else null,   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, }
Then, in your configuration file, reference this template:
Copy code
yaml courier:   delivery_strategy: http   http:     request_config:       url: <https://your-api-endpoint.com>       method: POST       body: <file://path/to/your/template.jsonnet>       headers:         Content-Type: application/json
This template specifically combines the first and last name from your identity traits structure to create a single "name" field with the value "Luka Boulpaep" in the HTTP request payload [HTTP based email delivery](https://www.ory.sh/docs/kratos/self-hosted/email-http#configuration). Note that the template checks for the existence of each property in the path to avoid errors if any part of the structure is missing. If you're using email templates directly rather than HTTP delivery, you can also access the combined name in your email templates using:
Copy code
Hello {{ .Identity.traits.name.first }} {{ .Identity.traits.name.last }},
This would render as "Hello Luka Boulpaep," in your email [Email templates in self-hosted Ory Kratos](https://www.ory.sh/docs/kratos/self-hosted/mail-courier-templates).
q
@chilly-king-10285 is this a correct struct to parse the html from it? type EmailTemplate struct { Recipient string
json:"recipient"
TemplateType string
json:"template_type"
Name *string
json:"name,omitempty"
To *string
json:"to,omitempty"
RecoveryCode *string
json:"recovery_code,omitempty"
RecoveryURL *string
json:"recovery_url,omitempty"
VerificationURL *string
json:"verification_url,omitempty"
VerificationCode *string
json:"verification_code,omitempty"
}
b
A message from kapa.ai
Your struct looks mostly correct for parsing the JSON payload from the Ory Kratos courier HTTP delivery, but it's missing a few fields that are available in the default payload. Here's an analysis of your struct and what might need to be added: Your current struct includes: -
Recipient
- The email address of the recipient -
TemplateType
- The type of template being used -
Name
- The name of the user (which you've customized in previous examples) -
To
- The recipient email address (duplicated in template_data) -
RecoveryCode
,
RecoveryURL
- For recovery flows -
VerificationURL
,
VerificationCode
- For verification flows However, based on the default Jsonnet template used by Ory Kratos for HTTP delivery, you should also include:
Copy code
go type EmailTemplate struct {  Recipient        string  `json:"recipient"`  TemplateType     string  `json:"template_type"`  Name             *string `json:"name,omitempty"`  To               *string `json:"to,omitempty"`  RecoveryCode     *string `json:"recovery_code,omitempty"`  RecoveryURL      *string `json:"recovery_url,omitempty"`  VerificationURL  *string `json:"verification_url,omitempty"`  VerificationCode *string `json:"verification_code,omitempty"`  LoginCode        *string `json:"login_code,omitempty"`  RegistrationCode *string `json:"registration_code,omitempty"`  Subject          *string `json:"subject,omitempty"`  Body             *string `json:"body,omitempty"` }
The additional fields are: -
LoginCode
- For one-time code login flows -
RegistrationCode
- For one-time code registration flows -
Subject
- The email subject -
Body
- The email body These fields are included in the default Jsonnet template as shown in the [Use a custom server to send Ory Identity messages to users](https://www.ory.sh/docs/kratos/emails-sms/sending-emails-smtp#send-emails-using-an-http-server) documentation. If you're only handling recovery and verification flows, your current struct might be sufficient, but including all possible fields will make your code more robust for future use cases. Kapa.ai: I've analyzed your Go struct for parsing Ory Kratos courier email templates. It covers most fields but is missing a few that are in the default template. I've provided the complete struct with all fields you should include for full compatibility.
q
this is my jsonnet though: function(ctx) { recipient: ctx.recipient, template_type: ctx.template_type, name: if "template_data" in ctx && "identity" in ctx.template_data && "traits" in ctx.template_data.identity && "name" in ctx.template_data.identity.traits then ctx.template_data.identity.traits.name.first + " " + ctx.template_data.identity.traits.name.last else null, to: if "template_data" in ctx && "to" in ctx.template_data then 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, }
@chilly-king-10285 this is my jsonnet though: function(ctx) { recipient: ctx.recipient, template_type: ctx.template_type, name: if "template_data" in ctx && "identity" in ctx.template_data && "traits" in ctx.template_data.identity && "name" in ctx.template_data.identity.traits then ctx.template_data.identity.traits.name.first + " " + ctx.template_data.identity.traits.name.last else null, to: if "template_data" in ctx && "to" in ctx.template_data then 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, }
b
A message from kapa.ai
Your Jsonnet template looks good, but there are a few issues with the formatting that need to be fixed. The HTML entities (
&
) and URL formatting (
<http://template_data.to|template_data.to>
) appear to be incorrectly encoded. Here's the corrected version:
Copy code
jsonnet function(ctx) {   recipient: ctx.recipient,   template_type: ctx.template_type,   name: if "template_data" in ctx && "identity" in ctx.template_data && "traits" in ctx.template_data.identity && "name" in ctx.template_data.identity.traits     then ctx.template_data.identity.traits.name.first + " " + ctx.template_data.identity.traits.name.last     else null,   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, }
This template will correctly extract the user's first and last name from the identity traits and combine them into a single "name" field. However, as mentioned in my previous response, you might want to consider adding the additional fields that are available in the default template: -
login_code
-
registration_code
-
subject
-
body
These fields are included in the default Jsonnet template as shown in the [Use a custom server to send Ory Identity messages to users](https://www.ory.sh/docs/kratos/emails-sms/sending-emails-smtp#send-emails-using-an-http-server) documentation. If you only need to handle the fields you've included, then your template (with the formatting fixes) should work correctly for your use case.
q
@chilly-king-10285 why do I get this? I create a recovery code with type api??? 2025-04-26 143931 time=2025-04-26T123931Z level=info msg=started handling request http_request=map[headersmap[accept encodinggzip content-length:91 user-agent:Go-http-client/1.1] hostkratos4433 method:POST path:/admin/recovery/code query:<nil> remote172.18.0.742882 scheme:http] 2025-04-26 143931 time=2025-04-26T123931Z level=warning msg=A request failed due to a missing or invalid csrf_token value audience=application error=map[debug: detailsmap[docshttps://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 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.
b
A message from kapa.ai
You're encountering a CSRF (Cross-Site Request Forgery) error when trying to create a recovery code with type "api". This error occurs because Ory Kratos is detecting a mismatch between the CSRF token in your cookies and the one provided in your request. Based on the error message, there are a few key issues: 1. The anti-CSRF cookie was found, but the CSRF token was not included in either: - The HTTP request body as
csrf_token
parameter - The HTTP header as
X-CSRF-Token
2. Or, if you did include a CSRF token, it doesn't match the one in the cookie. ## How to fix this issue: For API-based flows (which you're trying to use), you have a few options: 1. Include the CSRF token in your request body: When submitting the recovery flow, you need to include the
csrf_token
field in your request body. This token can be found in the UI nodes of the flow response as a hidden input field named
csrf_token
[CSRF troubleshooting](https://www.ory.sh/docs/kratos/debug/csrf). 2. Include the CSRF token in the X-CSRF-Token header: Alternatively, you can include the same token in the
X-CSRF-Token
HTTP header. 3. Make sure cookies are properly passed: Ensure that you're passing all cookies from previous requests in your current request. Here's an example of how to properly submit a recovery flow with the CSRF token:
Copy code
curl -X POST \     -H 'Content-Type: application/json' \     -H 'Accept: application/json' \     -b cookies.txt \     -d '{"method":"code","email":"<mailto:example@example.com|example@example.com>","csrf_token":"your-csrf-token"}' \     "<https://your-kratos-domain/self-service/recovery?flow=<your-flow-id>>"
If you're using the admin API, note that even though you're creating a recovery with type "api", the browser-based CSRF protection might still be active if cookies are present in your request [Submit recovery flow](https://www.ory.sh/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#submit-recovery-flow). The maintainer's comment in a similar issue confirms this: "The error in your log clearly indicates that you are not sending the CSRF Token in your request body
csrf_token=…
which is needed to verify the token" [CSRF Error during Login Flow](https://github.com/ory/kratos/discussions/4307). To completely avoid CSRF issues with API flows, you might want to ensure you're not sending any cookies with your request, or use a fresh HTTP client for each request that doesn't store cookies between requests.
q
@chilly-king-10285 This is via postman locally: 025-04-26 144747 time=2025-04-26T124747Z level=info msg=started handling request http_request=map[headersmap[accept*/* accept-encoding:gzip, deflate, br cache-control:no-cache connection:keep-alive content-length:104 content-type:application/json postman-token:5595b45d-87b1-4917-9861-52c72f588000 user-agent:PostmanRuntime/7.43.3] hostlocalhost4434 method:POST path:/admin/recovery/code query:<nil> remote172.18.0.161780 scheme:http] 2025-04-26 144747 time=2025-04-26T124747Z level=info msg=A recovery code has been created. audience=audit identity_id=9e603962-98a1-47e8-8605-9d1e8f073160 recovery_code=Value is sensitive and has been redacted. To see the value set config key "log.leak_sensitive_values = true" or environment variable "LOG_LEAK_SENSITIVE_VALUES=true". service_name=Ory Kratos service_version=v1.3.1 2025-04-26 144747 time=2025-04-26T124747Z level=info msg=completed handling request http_request=map[headersmap[accept*/* accept-encoding:gzip, deflate, br cache-control:no-cache connection:keep-alive content-length:104 content-type:application/json postman-token:5595b45d-87b1-4917-9861-52c72f588000 user-agent:PostmanRuntime/7.43.3] hostlocalhost4434 method:POST path:/admin/recovery/code query:<nil> remote172.18.0.161780 scheme:http] http_response=map[headersmap[cache controlprivate, no-cache, no-store, must-revalidate content-type:application/json; charset=utf-8] size:164 status:201 text_status:Created took:11.417209ms] I will send the logs in the next message for when I call it through my service running in docker-compose
b
A message from kapa.ai
I can see that your Postman request to the
/admin/recovery/code
endpoint was successful. The logs show: 1. The request was received at the admin endpoint 2. A recovery code was successfully created for identity ID
9e603962-98a1-47e8-8605-9d1e8f073160
3. The request completed with a 201 Created status This is working correctly because you're using the admin API endpoint, which doesn't require CSRF protection. The admin API is designed for server-to-server communication and bypasses the CSRF checks that were causing your previous error. When you mentioned in your previous message that you were getting CSRF errors, it was likely because you were either: 1. Using a public-facing endpoint instead of the admin endpoint 2. Or mixing cookie-based authentication with API requests The admin endpoints (like
/admin/recovery/code
that you're using now) don't require CSRF tokens because they're protected by different authentication mechanisms (typically API keys or other admin credentials). I'll wait for your next message with the logs from your service running in docker-compose to help diagnose any issues you might be having there.
q
@chilly-king-10285 logs when I call from my service: 2025-04-26 144617 time=2025-04-26T124617Z level=info msg=started handling request http_request=map[headersmap[accept encodinggzip content-length:91 user-agent:Go-http-client/1.1] hostkratos4433 method:POST path:/admin/recovery/code query:<nil> remote172.18.0.745274 scheme:http] 2025-04-26 144617 time=2025-04-26T124617Z level=warning msg=A request failed due to a missing or invalid csrf_token value audience=application error=map[debug: detailsmap[docshttps://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 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.
b
A message from kapa.ai
I can see the issue in your logs. When you're calling the
/admin/recovery/code
endpoint from your service, you're encountering a CSRF error, unlike when you call it from Postman. The key difference appears to be related to cookies. The error message states: > 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). This suggests that your Go HTTP client is sending cookies with your request, but not including the corresponding CSRF token. Here are a few ways to fix this: 1. Include the CSRF token from the cookie in your request: - Add it to the request body as
csrf_token
- Or add it as an
X-CSRF-Token
header 2. Don't send cookies with your request: - Create a new HTTP client for each request that doesn't store cookies - Or disable cookie jar in your HTTP client Here's how you might modify your Go code to create a client that doesn't use cookies:
Copy code
go client := &http.Client{     Jar: nil, // Explicitly set to nil to disable cookie storage }
According to the Ory maintainer's response to a similar issue: "The token value you send in the body comes from the API response of /self-service/login/flows and is a different value than the one from the cookie" [CSRF Error during Login Flow](https://github.com/ory/kratos/discussions/4307). This suggests that there might be a mismatch between the CSRF token in your cookies and what you're sending (if you're sending one at all). Since the admin API endpoint should work without CSRF protection (as it did in Postman), the most likely issue is that your service is somehow maintaining cookies from previous interactions with Kratos, and those cookies contain CSRF tokens that are triggering the protection mechanism. Try implementing one of the solutions above, particularly disabling cookie storage in your HTTP client, and see if that resolves the issue.
q
@chilly-king-10285 but servers cant store cookies right
b
A message from kapa.ai
Servers don't automatically store cookies like browsers do, but they can receive, forward, and manipulate cookies in HTTP requests and responses. In your case, it appears your Go HTTP client is maintaining cookies between requests. The standard Go HTTP client has a cookie jar that can store cookies from responses and automatically include them in subsequent requests to the same domain. This is likely why you're experiencing different behavior between Postman and your service: 1. Postman (working): Likely not storing/sending cookies with your admin API request 2. Your service (CSRF error): Automatically storing cookies from previous interactions with Kratos and sending them with your admin API request As mentioned in the Ory documentation: > When building a server-side application, make sure to include the
Cookie
header from the client when fetching the self-service flows... Without forwarding the
Cookie
HTTP header you can't fetch the flow due to a security error. [CSRF troubleshooting](https://www.ory.sh/docs/troubleshooting/csrf) For your admin API calls, you should either: 1. Create a fresh HTTP client for each request that doesn't use cookies:
Copy code
go client := &http.Client{     Jar: nil, // Explicitly disable cookie storage }
2. Or clear any existing cookies before making the admin API call This should resolve the CSRF error you're encountering when calling the admin API from your service. Would you like me to provide more specific guidance on how to modify your Go HTTP client implementation?
q
@chilly-king-10285 I asked for a recovery through the admin api. But after saving the password I keep being on the settings page. Is there a way for me to navigate to another page on submit?
b
A message from kapa.ai
I understand you're trying to redirect users to a different page after they complete the recovery flow and save their password, rather than staying on the settings page. This is a known behavior in Ory Kratos. When a recovery flow is successful, Kratos automatically redirects the user to the settings page where they can update their password. After the password is updated, the user remains on the settings page instead of being redirected elsewhere. According to the documentation and GitHub issues, there are a few approaches to handle this: ## Option 1: Configure a redirect URL for the settings flow Since the recovery flow transitions to the settings flow, you can configure a redirect URL for the settings flow:
Copy code
yaml selfservice:   flows:     settings:       after:         default_browser_return_url: <https://your-desired-redirect-url/>
This will redirect users after they complete the settings flow (which includes updating their password after recovery) [Browser redirects and flow completion](https://www.ory.sh/docs/concepts/redirects#redirect-flows). ## Option 2: Use the
return_to
parameter You can include a
return_to
parameter when initializing the recovery flow:
Copy code
sdk.createBrowserRecoveryFlow({   return_to: "<https://your-desired-redirect-url/>" })
However, there's a known issue with this approach. According to a GitHub issue: "When creating a recovery flow with the
return_to
query parameter the url is not respected when the account has two factor authentication" [Recovery flow with Two-Factor does not respect
return_to
](https://github.com/ory/network/issues/222). ## Option 3: Manual redirect in your UI Since the recovery flow redirects to the settings page, you can implement a custom redirect in your settings UI by checking if the user came from a recovery flow and then redirecting them after they submit the password change. ## Important note There's an open issue in the Ory Kratos GitHub repository about this exact problem: "Recovery flow redirect after success not working as expected" [GitHub Issue #2371](https://github.com/ory/kratos/issues/2371). According to this issue, the
default_browser_return_url
configuration for the recovery flow doesn't work as expected because the recovery flow redirects to the settings flow. A user in the issue comments mentioned: "The only way to reliably achieve this behaviour is to cause a manual overriding redirect INSIDE the settings page - By checking the source redirect that brought you to that page, and checking if that value contains the recovery flow identifier." Would you like me to provide more details on any of these approaches?
q
@chilly-king-10285 Can a selfhosted version of Kratos make use of organizations?
b
A message from kapa.ai
Based on the knowledge sources provided, organizations are not available in self-hosted Ory Kratos. This feature is exclusively available in Ory Network. According to the documentation: > Organizations are only available in Ory Network and are not supported in self-hosted Ory Kratos. If you have any questions, or if you would like more information about transitioning to Ory Network, please don't hesitate to [reach out](https://www.ory.sh/contact/). [B2B Single Sign-On & SAML](https://www.ory.sh/docs/kratos/organizations) Similarly, for multi-tenancy functionality: > [Ory Network](https://console.ory.sh) is the only available option to have a multi-tenant Ory Kratos set up. It is not possible to self-host Ory Kratos as a multi-tenant service as its data model does not support this due to data, scalability, and operational complexity. [Multitenancy](https://www.ory.sh/docs/kratos/guides/multi-tenancy-multitenant) Organizations in Ory provide functionality for grouping users within a project, managing B2B enterprise SSO with OIDC or SAML, and handling domain-based registrations. These features are part of Ory Network's commercial offering and are not available in the self-hosted open source version of Kratos. If you need organization functionality, you would need to use Ory Network instead of self-hosting Kratos.
q
@chilly-king-10285 can hydra or kratos run in serverless modes like in AWS Lambda?
b
A message from kapa.ai
The knowledge sources do not explicitly mention support for running Ory Hydra or Ory Kratos in serverless environments such as AWS Lambda. Ory’s architecture is designed around stateless, disposable processes that scale horizontally and run best on container orchestration systems like Kubernetes, CloudFoundry, and OpenShift. The documentation emphasizes that Ory services are typically deployed as long-running processes or containers, relying on a persistent SQL database for state, and do not require additional dependencies beyond the database and the process itself. There is no guidance or example provided for deploying Hydra or Kratos in a serverless function environment like AWS Lambda, which is optimized for short-lived, event-driven workloads rather than persistent HTTP servers [Architecture principles](https://www.ory.sh/docs/ecosystem/software-architecture-philosophy). If you are considering serverless deployment, you may encounter challenges related to connection management, cold starts, and maintaining persistent HTTP endpoints, which are fundamental to how Ory services operate. For best results, Ory recommends using container-based or VM-based deployments.
q
@chilly-king-10285 Does OSS Kratos support webhooks for user events like deletion or creation?
b
A message from kapa.ai
Open Source (OSS) Ory Kratos does not natively support webhooks for user events like identity creation or deletion. Webhooks and the broader "Ory Actions" extensibility mechanism—which allow you to trigger external logic in response to user-related events such as registration, login, account recovery, verification, or settings updates—are features of the Ory Network (the managed, commercial offering) and not available in the self-hosted OSS version of Kratos [Overview of Ory Actions](https://www.ory.sh/docs/kratos/hooks/configure-hooks). While OSS Kratos does allow you to configure hooks for certain self-service flows (like before/after registration, login, settings, etc.), there is no mention in the knowledge sources of webhook support for direct identity lifecycle events such as creation or deletion via the admin API or SCIM. The event streaming and webhook features for these types of events are documented as part of Ory Network’s enterprise capabilities [Live event streams](https://www.ory.sh/docs/actions/live-events/). If you require webhooks for user creation or deletion events, you would need to use Ory Network or implement your own integration layer that listens for these events and triggers webhooks accordingly.
q
@chilly-king-10285 what happens when you initiate a recovery flow when one was already created?
b
A message from kapa.ai
When you attempt to initiate a new recovery flow while one already exists, Ory Kratos expects you to use the existing recovery flow instead of creating a new one. If you try to create a new recovery flow (for example, using the
createNativeRecoveryFlow
endpoint) while a valid session cookie or session token is present, you will receive a
400 Bad Request
error. The documentation states: > On an existing recovery flow, use the
getRecoveryFlow
API endpoint. This means you should retrieve and continue the existing flow using its ID, rather than starting a new one. This approach helps prevent issues such as CSRF attacks and ensures the integrity of the recovery process [Ory API Reference – createNativeRecoveryFlow](https://www.ory.sh/docs/reference/api). If the existing flow has expired, you will need to initiate a new one. Otherwise, always use the current flow until it is completed or expires.
q
What if you do it through the admin api @chilly-king-10285
b
A message from kapa.ai
The knowledge sources do not explicitly state what happens if you initiate a new recovery flow through the admin API when one already exists. The available documentation and examples focus on the self-service (public) API, where you are expected to use the existing flow if one is active, and a new flow is only created if the previous one has expired [Ory API Reference – createNativeRecoveryFlow](https://www.ory.sh/docs/reference/api). For the admin API, the documentation describes how to initiate a recovery flow for a user and receive a recovery link and code, but it does not specify whether multiple concurrent admin-initiated recovery flows are allowed, or if a new request invalidates the previous one [Administrative account recovery](https://www.ory.sh/docs/kratos/manage-identities/account-recovery). Therefore, based on the knowledge sources, it is unclear whether the admin API enforces the same flow reuse/expiration logic as the public API, or if it always creates a new flow and code regardless of existing ones.
q
@chilly-king-10285 What body do I need to adjust my traits of a user with a PATCH request?
b
A message from kapa.ai
To adjust the traits of a user (identity) with a PATCH request in Ory Kratos, you need to use the [patchIdentity](https://www.ory.sh/docs/kratos/reference/api#patchidentity) endpoint and provide a JSON Patch document in the request body. The JSON Patch document should specify the operations you want to perform on the identity's traits. For example, to replace the
first
name in the
traits.name
object, your request body would look like this:
Copy code
json [   {     "op": "replace",     "path": "/traits/name/first",     "value": "NewFirstName"   } ]
You can also add or remove traits using the
"op": "add"
or
"op": "remove"
operations, respectively. The
path
uses [JSON Pointer](https://datatracker.ietf.org/doc/html/rfc6901#section-5) notation to specify the field you want to modify. Example cURL request:
Copy code
bash curl --location --request PATCH "https://<kratos-admin-url>/admin/identities/<identity-id>" \ --header "Content-Type: application/json" \ --header "Authorization: Bearer <your-admin-api-key>" \ --data-raw '[   {     "op": "replace",     "path": "/traits/name/first",     "value": "NewFirstName"   } ]'
The fields
id
,
stateChangedAt
, and
credentials
cannot be updated using this method [Kratos API Reference – patchIdentity](https://www.ory.sh/docs/kratos/reference/api#patchidentity). For more complex updates, you can include multiple operations in the array:
Copy code
json [   { "op": "replace", "path": "/traits/name/first", "value": "NewFirstName" },   { "op": "replace", "path": "/traits/name/last", "value": "NewLastName" } ]
This approach allows you to precisely adjust the traits of a user using the PATCH method.
q
@chilly-king-10285 Can we filter on names in Kratos?
b
A message from kapa.ai
Ory Kratos does not support filtering identities by name (or other arbitrary traits) via its API. The available filtering options are limited and do not include searching or filtering by fields such as first name, last name, or similar traits. This limitation is confirmed in the documentation and user reports. For example, the
/admin/identities
endpoint does not support search at the moment, and filtering is only possible by credential type (like username or email), not by arbitrary traits like names [REST API Guidelines](https://www.ory.sh/docs/open-source/guidelines/rest-api-guidelines#the-design-for-openapi) [Kratos API Reference](https://raw.githubusercontent.com/ory/kratos/master/spec/api.json). Additionally, a user reported that searching for identities by domain (e.g., part of an email address) is not supported in the Ory Console, and a team member confirmed that while you can filter by organization ID in the API, there is no mention of filtering by name or similar traits [Identities cannot be search by domain](https://github.com/ory/network/issues/405). Therefore, at this time, filtering on names in Kratos is not supported.
q
@chilly-king-10285 2025-06-22 173241 time=2025-06-22T153241Z level=info msg=started handling request http_request=map[headersmap[accepttext/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 accept-encoding:gzip, deflate, br, zstd accept-language:en-US,en;q=0.9,fr;q=0.8,nl;q=0.7 cache-control:max-age=0 connection:keep-alive content-length:121 content-type:application/x-www-form-urlencoded cookie:[csrf_token_82b119fa58a0a1cb6faa9738c1d0dbbf04fcc89a657b7beb31fcde400ced48ab=sUXHvgj5wnb5sVig6shr7fj8Zw3y+po4u85dk8ukOYc=; ory_kratos_session=MTc1MDUzNDgzNHxmUWFXOHlBbE41ZnBKSjlaWXVrRHJ4MnBtbERBUmsyN3FzQ21hdUcwU01DNUFnU20wVXE2UGFQUmRJRzNia2plUzY4bXgxYVlIdFg3QTdFbGhYUFlHcF9tVDE5VFBOUVctelhKWUp2TWw2R25VSE9VMkVVbjNMQ2l1MmlpN0VHcndvV1cxMVNSaVQxbU9HLTZBbTg2M3gybDdKRS05ZFlsSHpiNDFQdm1PVTMzWGtRSmNMQ3FXSWNkWDIwdjVnc1dFNHFJX25qaDRRMFIyZEZFOGhYakdSRFh1a2VOUGhQU3NUdm95alE1ZzM4M0VwSWl4NzBia0YxMUp0TzdUTFRCUlpfX3ZjczNrN19hTFpTMkFRMjB8v5N87TyLzFvpU6xJIaQL7vs-UesiutoGB-YGJtwzMMg=] dnt:1 origin:http://localhost:3002 referer:http://localhost:3002/ sec-ch-ua:"Chromium";v="137", "Not/A)Brand";v="24" sec-ch-ua-mobile:?0 sec-ch-ua-platform:"macOS" sec-fetch-dest:document sec-fetch-mode:navigate sec-fetch-site:same-site sec-fetch-user:?1 upgrade-insecure-requests:1 user-agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36] hostlocalhost4433 method:POST path:/self-service/recovery query:flow=0136af13-4a2d-4225-8b6f-0ce7a3f8c1d6 remote172.18.0.155306 scheme:http] 2025-06-22 173241 time=2025-06-22T153241Z level=info msg=Encountered self-service recovery error. audience=audit error=map[message:I[#/email] S[] missing properties: email stack_trace: 2025-06-22 173241 github.com/ory/kratos/schema.NewRequiredError 2025-06-22 173241 /project/schema/errors.go:22 2025-06-22 173241 github.com/ory/kratos/selfservice/strategy/code.(*Strategy).recoveryHandleFormSubmission 2025-06-22 173241 /project/selfservice/strategy/code/strategy_recovery.go:374 2025-06-22 173241 github.com/ory/kratos/selfservice/strategy/code.(*Strategy).Recover 2025-06-22 173241 /project/selfservice/strategy/code/strategy_recovery.go:167 2025-06-22 173241 github.com/ory/kratos/selfservice/flow/recovery.(*Handler).updateRecoveryFlow 2025-06-22 173241 /project/selfservice/flow/recovery/handler.go:426 2025-06-22 173241 github.com/ory/kratos/x.(*RouterPublic).POST.NoCacheHandle.func1 2025-06-22 173241 /project/x/nocache.go:21 2025-06-22 173241 github.com/ory/kratos/x.(*RouterPublic).Handle.NoCacheHandle.func1 2025-06-22 173241 /project/x/nocache.go:21 2025-06-22 173241 github.com/julienschmidt/httprouter.(*Router).ServeHTTP 2025-06-22 173241 /go/pkg/mod/github.com/julienschmidt/httprouter@v1.3.0/router.go:387 2025-06-22 173241 github.com/ory/nosurf.(*CSRFHandler).handleSuccess 2025-06-22 173241 /go/pkg/mod/github.com/ory/nosurf@v1.2.7/handler.go:234 2025-06-22 173241 github.com/ory/nosurf.(*CSRFHandler).ServeHTTP 2025-06-22 173241 /go/pkg/mod/github.com/ory/nosurf@v1.2.7/handler.go:185 2025-06-22 173241 github.com/ory/kratos/cmd/daemon.servePublic.MaxBytesHandler.func4 2025-06-22 173241 /usr/local/go/src/net/http/server.go:3906 2025-06-22 173241 net/http.HandlerFunc.ServeHTTP 2025-06-22 173241 /usr/local/go/src/net/http/server.go:2220 2025-06-22 173241 github.com/urfave/negroni.(*Negroni).UseHandler.Wrap.func1 2025-06-22 173241 /go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:46 2025-06-22 173241 github.com/urfave/negroni.HandlerFunc.ServeHTTP 2025-06-22 173241 /go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:29 2025-06-22 173241 github.com/urfave/negroni.middleware.ServeHTTP 2025-06-22 173241 /go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38 2025-06-22 173241 github.com/ory/kratos/x.init.func1 2025-06-22 173241 /project/x/clean_url.go:15 2025-06-22 173241 github.com/urfave/negroni.HandlerFunc.ServeHTTP 2025-06-22 173241 /go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:29 2025-06-22 173241 github.com/urfave/negroni.middleware.ServeHTTP 2025-06-22 173241 /go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38 2025-06-22 173241 github.com/ory/kratos/cmd/daemon.servePublic.func1 2025-06-22 173241 /project/cmd/daemon/serve.go:111 2025-06-22 173241 github.com/urfave/negroni.HandlerFunc.ServeHTTP 2025-06-22 173241 /go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:29 2025-06-22 173241 github.com/urfave/negroni.middleware.ServeHTTP 2025-06-22 173241 /go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38 2025-06-22 173241 net/http.HandlerFunc.ServeHTTP 2025-06-22 173241 /usr/local/go/src/net/http/server.go:2220 2025-06-22 173241 github.com/prometheus/client_golang/prometheus/promhttp.InstrumentHandlerResponseSize.func1 2025-06-22 173241 /go/pkg/mod/github.com/prometheus/client_golang@v1.13.0/prometheus/promhttp/instrument_server.go:284 2025-06-22 173241 net/http.HandlerFunc.ServeHTTP 2025-06-22 173241 /usr/local/go/src/net/http/server.go:2220 2025-06-22 173241 github.com/prometheus/client_golang/prometheus/promhttp.InstrumentHandlerCounter.func1 2025-06-22 173241 /go/pkg/mod/github.com/prometheus/client_golang@v1.13.0/prometheus/promhttp/instrument_server.go:142 2025-06-22 173241 net/http.HandlerFunc.ServeHTTP 2025-06-22 173241 /usr/local/go/src/net/http/server.go:2220 2025-06-22 173241 github.com/prometheus/client_golang/prometheus/promhttp.InstrumentHandlerDuration.func1 2025-06-22 173241 /go/pkg/mod/github.com/prometheus/client_golang@v1.13.0/prometheus/promhttp/instrument_server.go:92 2025-06-22 173241 net/http.HandlerFunc.ServeHTTP 2025-06-22 173241 /usr/local/go/src/net/http/server.go:2220 2025-06-22 173241 github.com/prometheus/client_golang/prometheus/promhttp.InstrumentHandlerDuration.func2 2025-06-22 173241 /go/pkg/mod/github.com/prometheus/client_golang@v1.13.0/prometheus/promhttp/instrument_server.go:104 2025-06-22 173241 net/http.HandlerFunc.ServeHTTP 2025-06-22 173241 /usr/local/go/src/net/http/server.go:2220 2025-06-22 173241 github.com/prometheus/client_golang/prometheus/promhttp.InstrumentHandlerRequestSize.func1 2025-06-22 173241 /go/pkg/mod/github.com/prometheus/client_golang@v1.13.0/prometheus/promhttp/instrument_server.go:234 2025-06-22 173241 net/http.HandlerFunc.ServeHTTP 2025-06-22 173241 /usr/local/go/src/net/http/server.go:2220 2025-06-22 173241 github.com/ory/x/prometheusx.Metrics.Instrument.Metrics.instrumentHandlerStatusBucket.func1 2025-06-22 173241 /go/pkg/mod/github.com/ory/x@v0.0.660/prometheusx/metrics.go:115] http_request=map[headersmap[accepttext/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 accept-encoding:gzip, deflate, br, zstd accept-language:en-US,en;q=0.9,fr;q=0.8,nl;q=0.7 cache-control:max-age=0 connection:keep-alive content-length:121 content-type:application/x-www-form-urlencoded cookie:[csrf_token_82b119fa58a0a1cb6faa9738c1d0dbbf04fcc89a657b7beb31fcde400ced48ab=sUXHvgj5wnb5sVig6shr7fj8Zw3y+po4u85dk8ukOYc=; ory_kratos_session=MTc1MDUzNDgzNHxmUWFXOHlBbE41ZnBKSjlaWXVrRHJ4MnBtbERBUmsyN3FzQ21hdUcwU01DNUFnU20wVXE2UGFQUmRJRzNia2plUzY4bXgxYVlIdFg3QTdFbGhYUFlHcF9tVDE5VFBOUVctelhKWUp2TWw2R25VSE9VMkVVbjNMQ2l1MmlpN0VHcndvV1cxMVNSaVQxbU9HLTZBbTg2M3gybDdKRS05ZFlsSHpiNDFQdm1PVTMzWGtRSmNMQ3FXSWNkWDIwdjVnc1dFNHFJX25qaDRRMFIyZEZFOGhYakdSRFh1a2VOUGhQU3NUdm95alE1ZzM4M0VwSWl4NzBia0YxMUp0TzdUTFRCUlpfX3ZjczNrN19hTFpTMkFRMjB8v5N87TyLzFvpU6xJIaQL7vs-UesiutoGB-YGJtwzMMg=] dnt:1 origin:http://localhost:3002 referer:http://localhost:3002/ sec-ch-ua:"Chromium";v="137", "Not/A)Brand";v="24" sec-ch-ua-mobile:?0 sec-ch-ua-platform:"macOS" sec-fetch-dest:document sec-fetch-mode:navigate sec-fetch-site:same-site sec-fetch-user:?1 upgrade-insecure-requests:1 user-agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36] hostlocalhost4433 method:POST path:/self-service/recovery query:flow=0136af13-4a2d-4225-8b6f-0ce7a3f8c1d6 remote172.18.0.155306 scheme:http] recovery_flow=map[active:code id:0136af13-4a2d-4225-8b6f-0ce7a3f8c1d6 nid:e921f0ad-5a7d-451f-b6a2-7b64e62686a6 request_url:http://localhost/self-service/recovery/browser return_to: state:choose_method type:browser] service_name=Ory Kratos service_version=v1.3.1 2025-06-22 173241 time=2025-06-22T153241Z level=info msg=completed handling request http_request=map[headersmap[accepttext/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 accept-encoding:gzip, deflate, br, zstd accept-language:en-US,en;q=0.9,fr;q=0.8,nl;q=0.7 cache-control:max-age=0 connection:keep-alive content-length:121 content-type:application/x-www-form-urlencoded cookie:[csrf_token_82b119fa58a0a1cb6faa9738c1d0dbbf04fcc89a657b7beb31fcde400ced48ab=sUXHvgj5wnb5sVig6shr7fj8Zw3y+po4u85dk8ukOYc=; ory_kratos_session=MTc1MDUzNDgzNHxmUWFXOHlBbE41ZnBKSjlaWXVrRHJ4MnBtbERBUmsyN3FzQ21hdUcwU01DNUFnU20wVXE2UGFQUmRJRzNia2plUzY4bXgxYVlIdFg3QTdFbGhYUFlHcF9tVDE5VFBOUVctelhKWUp2TWw2R25VSE9VMkVVbjNMQ2l1MmlpN0VHcndvV1cxMVNSaVQxbU9HLTZBbTg2M3gybDdKRS05ZFlsSHpiNDFQdm1PVTMzWGtRSmNMQ3FXSWNkWDIwdjVnc1dFNHFJX25qaDRRMFIyZEZFOGhYakdSRFh1a2VOUGhQU3NUdm95alE1ZzM4M0VwSWl4NzBia0YxMUp0TzdUTFRCUlpfX3ZjczNrN19hTFpTMkFRMjB8v5N87TyLzFvpU6xJIaQL7vs-UesiutoGB-YGJtwzMMg=] dnt:1 origin:http://localhost:3002 referer:http://localhost:3002/ sec-ch-ua:"Chromium";v="137", "Not/A)Brand";v="24" sec-ch-ua-mobile:?0 sec-ch-ua-platform:"macOS" sec-fetch-dest:document sec-fetch-mode:navigate sec-fetch-site:same-site sec-fetch-user:?1 upgrade-insecure-requests:1 user-agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36] hostlocalhost4433 method:POST path:/self-service/recovery query:flow=0136af13-4a2d-4225-8b6f-0ce7a3f8c1d6 remote172.18.0.155306 scheme:http] http_response=map[headersmap[cache controlprivate, no-cache, no-store, must-revalidate location:http://localhost:3002/recovery?flow=0136af13-4a2d-4225-8b6f-0ce7a3f8c1d6 vary:Cookie] size:0 status:303 text_status:See Other took:7.351083ms]
b
A message from kapa.ai
Your logs show the following error when submitting a recovery flow:
Copy code
error=map[message:I[#/email] S[] missing properties: email stack_trace: ...
This means that the email property was missing from your POST request to the
/self-service/recovery
endpoint. In the recovery flow, the email field is required in the request body when you are in the
choose_method
state. What happened: - You submitted a form (likely from a browser) to the recovery endpoint. - The request did not include the required
email
field. - Kratos responded with a validation error indicating that the
email
property is missing. How to fix: Make sure your form or request body includes the
email
field. For example, if you are submitting a form, it should look like this:
Copy code
http POST /self-service/recovery?flow=<flow-id> Content-Type: application/x-www-form-urlencoded  email=your@email.com&method=code&csrf_token=...
Or, if you are using JSON:
Copy code
json {   "email": "<mailto:your@email.com|your@email.com>",   "method": "code",   "csrf_token": "..." }
The
email
field is required to initiate the recovery process, as documented in the [Ory Kratos recovery flow](https://www.ory.sh/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#submit-recovery-flow). Summary: The error is because the
email
property was missing in your request. Add the
email
field to your request body to resolve this issue.
q
@chilly-king-10285
Copy code
<form class="form" action="<http://localhost:4433/self-service/recovery?flow=0136af13-4a2d-4225-8b6f-0ce7a3f8c1d6>" method="POST">
          
            
              <input type="hidden" name="csrf_token" value="4b1nKDjHZJY9UDS7YzUIbCUZxgbdkPxjgh2nZIHAoTlQ+KCWMD6m4MThbBuJ/WOB3eWhCy9qZls50/r3SmSYvg==" required="">
            
          
            
              <input data-slot="input" class="file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive" placeholder="Enter your email address:" required="" type="email">
            
          
            
              <button type="submit" name="method" value="code" class="submit">Continue</button>
            
          
          
        </form>
b
A message from kapa.ai
Your form is almost correct, but it is missing the
name="email"
attribute on the email input field. Without this attribute, the email value will not be included in the POST request body, which is why Ory Kratos reports the error "missing properties: email". How to fix: Update your email input field to include the
name="email"
attribute:
Copy code
html <input   data-slot="input"   class="file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive"   placeholder="Enter your email address:"   required=""   type="email"   name="email" />
With this change, the form will submit the
email
field as required by the Ory Kratos recovery flow [Submit recovery flow](https://www.ory.sh/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#submit-recovery-flow). Summary: Add
name="email"
to your email input field to resolve the "missing properties: email" error.
q
@chilly-king-10285 2025-06-22 174413 hostlocalhost4433 method:GET path:/self-service/recovery query:csrf_token=61uryThAscWC6TGg8JftQ9PwKRnIwVwkjy%2F3yzn9MJBaHmx3MLlzs3tYaQAaX4auKwxOFDo7xhw04apY8lkJFw%3D%3D&email=lukaboulpaep%40outlook.com&method=code remote172.18.0.160694 scheme:http] 2025-06-22 174413 time=2025-06-22T154413Z level=info msg=Encountered self-service recovery error. audience=audit error=map[debug: message:The request was malformed or contained invalid parameters reason:The flow query parameter is missing or malformed. stack_trace: 2025-06-22 174413 github.com/ory/kratos/selfservice/flow.GetFlowID 2025-06-22 174413 /project/selfservice/flow/flow.go:29 2025-06-22 174413 github.com/ory/kratos/selfservice/flow/recovery.(*Handler).updateRecoveryFlow 2025-06-22 174413 /project/selfservice/flow/recovery/handler.go:403 2025-06-22 174413 github.com/ory/kratos/x.(*RouterPublic).GET.NoCacheHandle.func1 2025-06-22 174413 /project/x/nocache.go:21 2025-06-22 174413 github.com/ory/kratos/x.(*RouterPublic).Handle.NoCacheHandle.func1 2025-06-22 174413 /project/x/nocache.go:21 2025-06-22 174413 github.com/julienschmidt/httprouter.(*Router).ServeHTTP 2025-06-22 174413 /go/pkg/mod/github.com/julienschmidt/httprouter@v1.3.0/router.go:387 2025-06-22 174413 github.com/ory/nosurf.(*CSRFHandler).handleSuccess 2025-06-22 174413 /go/pkg/mod/github.com/ory/nosurf@v1.2.7/handler.go:234 2025-06-22 174413 github.com/ory/nosurf.(*CSRFHandler).ServeHTTP 2025-06-22 174413 /go/pkg/mod/github.com/ory/nosurf@v1.2.7/handler.go:185 2025-06-22 174413 github.com/ory/kratos/cmd/daemon.servePublic.MaxBytesHandler.func4 2025-06-22 174413 /usr/local/go/src/net/http/server.go:3906 2025-06-22 174413 net/http.HandlerFunc.ServeHTTP 2025-06-22 174413 /usr/local/go/src/net/http/server.go:2220 2025-06-22 174413 github.com/urfave/negroni.(*Negroni).UseHandler.Wrap.func1 2025-06-22 174413 /go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:46 2025-06-22 174413 github.com/urfave/negroni.HandlerFunc.ServeHTTP 2025-06-22 174413 /go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:29 2025-06-22 174413 github.com/urfave/negroni.middleware.ServeHTTP 2025-06-22 174413 /go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38 2025-06-22 174413 github.com/ory/kratos/x.init.func1 2025-06-22 174413 /project/x/clean_url.go:15 2025-06-22 174413 github.com/urfave/negroni.HandlerFunc.ServeHTTP 2025-06-22 174413 /go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:29 2025-06-22 174413 github.com/urfave/negroni.middleware.ServeHTTP 2025-06-22 174413 /go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38 2025-06-22 174413 github.com/ory/kratos/cmd/daemon.servePublic.func1 2025-06-22 174413 /project/cmd/daemon/serve.go:111 2025-06-22 174413 github.com/urfave/negroni.HandlerFunc.ServeHTTP 2025-06-22 174413 /go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:29 2025-06-22 174413 github.com/urfave/negroni.middleware.ServeHTTP 2025-06-22 174413 /go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38 2025-06-22 174413 net/http.HandlerFunc.ServeHTTP 2025-06-22 174413 /usr/local/go/src/net/http/server.go:2220 2025-06-22 174413 github.com/prometheus/client_golang/prometheus/promhttp.InstrumentHandlerResponseSize.func1 2025-06-22 174413 /go/pkg/mod/github.com/prometheus/client_golang@v1.13.0/prometheus/promhttp/instrument_server.go:284 2025-06-22 174413 net/http.HandlerFunc.ServeHTTP 2025-06-22 174413 /usr/local/go/src/net/http/server.go:2220 2025-06-22 174413 github.com/prometheus/client_golang/prometheus/promhttp.InstrumentHandlerCounter.func1 2025-06-22 174413 /go/pkg/mod/github.com/prometheus/client_golang@v1.13.0/prometheus/promhttp/instrument_server.go:142 2025-06-22 174413 net/http.HandlerFunc.ServeHTTP 2025-06-22 174413 /usr/local/go/src/net/http/server.go:2220 2025-06-22 174413 github.com/prometheus/client_golang/prometheus/promhttp.InstrumentHandlerDuration.func1 2025-06-22 174413 /go/pkg/mod/github.com/prometheus/client_golang@v1.13.0/prometheus/promhttp/instrument_server.go:92 2025-06-22 174413 net/http.HandlerFunc.ServeHTTP 2025-06-22 174413 /usr/local/go/src/net/http/server.go:2220 2025-06-22 174413 github.com/prometheus/client_golang/prometheus/promhttp.InstrumentHandlerDuration.func2 2025-06-22 174413 /go/pkg/mod/github.com/prometheus/client_golang@v1.13.0/prometheus/promhttp/instrument_server.go:104 2025-06-22 174413 net/http.HandlerFunc.ServeHTTP 2025-06-22 174413 /usr/local/go/src/net/http/server.go:2220 2025-06-22 174413 github.com/prometheus/client_golang/prometheus/promhttp.InstrumentHandlerRequestSize.func1 2025-06-22 174413 /go/pkg/mod/github.com/prometheus/client_golang@v1.13.0/prometheus/promhttp/instrument_server.go:234 2025-06-22 174413 net/http.HandlerFunc.ServeHTTP 2025-06-22 174413 /usr/local/go/src/net/http/server.go:2220 2025-06-22 174413 github.com/ory/x/prometheusx.Metrics.Instrument.Metrics.instrumentHandlerStatusBucket.func1 2025-06-22 174413 /go/pkg/mod/github.com/ory/x@v0.0.660/prometheusx/metrics.go:115 2025-06-22 174413 net/http.HandlerFunc.ServeHTTP 2025-06-22 174413 /usr/local/go/src/net/http/server.go:2220 2025-06-22 174413 github.com/ory/x/prometheusx.(*MetricsManager).ServeHTTP 2025-06-22 174413 /go/pkg/mod/github.com/ory/x@v0.0.660/prometheusx/middleware.go:41 status:Bad Request status_code:400] http_request=map[headersmap[accepttext/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 accept-encoding:gzip, deflate, br, zstd accept-language:en-US,en;q=0.9,fr;q=0.8,nl;q=0.7 connection:keep-alive cookie:[csrf_token_82b119fa58a0a1cb6faa9738c1d0dbbf04fcc89a657b7beb31fcde400ced48ab=sUXHvgj5wnb5sVig6shr7fj8Zw3y+po4u85dk8ukOYc=; ory_kratos_session=MTc1MDUzNDgzNHxmUWFXOHlBbE41ZnBKSjlaWXVrRHJ4MnBtbERBUmsyN3FzQ21hdUcwU01DNUFnU20wVXE2UGFQUmRJRzNia2plUzY4bXgxYVlIdFg3QTdFbGhYUFlHcF9tVDE5VFBOUVctelhKWUp2TWw2R25VSE9VMkVVbjNMQ2l1MmlpN0VHcndvV1cxMVNSaVQxbU9HLTZBbTg2M3gybDdKRS05ZFlsSHpiNDFQdm1PVTMzWGtRSmNMQ3FXSWNkWDIwdjVnc1dFNHFJX25qaDRRMFIyZEZFOGhYakdSRFh1a2VOUGhQU3NUdm95alE1ZzM4M0VwSWl4NzBia0YxMUp0TzdUTFRCUlpfX3ZjczNrN19hTFpTMkFRMjB8v5N87TyLzFvpU6xJIaQL7vs-UesiutoGB-YGJtwzMMg=] dnt:1 referer:http://localhost:3002/ sec-ch-ua:"Chromium";v="137", "Not/A)Brand";v="24" sec-ch-ua-mobile:?0 sec-ch-ua-platform:"macOS" sec-fetch-dest:document sec-fetch-mode:navigate sec-fetch-site:same-site sec-fetch-user:?1 upgrade-insecure-requests:1 user-agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36] hostlocalhost4433 method:GET path:/self-service/recovery query:csrf_token=61uryThAscWC6TGg8JftQ9PwKRnIwVwkjy%2F3yzn9MJBaHmx3MLlzs3tYaQAaX4auKwxOFDo7xhw04apY8lkJFw%3D%3D&email=lukaboulpaep%40outlook.com&method=code remote172.18.0.160694 scheme:http] recovery_flow=map[] service_name=Ory Kratos service_version=v1.3.1 2025-06-22 174413 time=2025-06-22T154413Z level=error msg=An error occurred and is being forwarded to the error user interface. audience=application error=map[debug: message:The request was malformed or contained invalid parameters reason:The flow query parameter is missing or malformed. stack_trace:
b
A message from kapa.ai
The error you are seeing:
Copy code
reason:The flow query parameter is missing or malformed.
means that your GET request to
/self-service/recovery
is missing the required
flow
query parameter, or the parameter is not correctly formatted. According to the Ory Kratos documentation, when interacting with a self-service flow (such as recovery), you must always include the
flow
parameter in the query string when fetching or submitting a flow. The correct usage is:
Copy code
/self-service/recovery?flow=<flow-id>
What went wrong in your request: Your request was:
Copy code
GET /self-service/recovery?csrf_token=...&email=...&method=code
This is incorrect because: - The endpoint expects a
flow
parameter, not
csrf_token
,
email
, or
method
in the query string for a GET request. - The
csrf_token
,
email
, and
method
should be sent in the POST body when submitting the flow, not as query parameters in a GET request. How to fix: - When fetching the recovery flow (GET), use only the
flow
parameter:
Copy code
GET /self-service/recovery?flow=<flow-id>
- When submitting the recovery flow (POST), send the
csrf_token
,
email
, and
method
in the request body (form or JSON), not in the query string. This is confirmed in the documentation and API reference for Ory Kratos recovery flows [Get recovery flow](https://www.ory.sh/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#get-recovery-flow). Summary: Always include the
flow
parameter in the query string when fetching a recovery flow. Do not include
csrf_token
,
email
, or
method
in the query string for GET requests; these belong in the POST body when submitting the flow.
q
Copy code
<form class="form" action="<http://localhost:4433/self-service/recovery?flow=a6f18415-6bc0-4b9f-a1ad-abe5c8f18d2b>" method="POST ">
          
            
              <input type="hidden" name="csrf_token" value="61uryThAscWC6TGg8JftQ9PwKRnIwVwkjy/3yzn9MJBaHmx3MLlzs3tYaQAaX4auKwxOFDo7xhw04apY8lkJFw==" required="">
            
          
            
              <input data-slot="input" class="file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive" placeholder="Your email address here" name="email" value="" required="" type="email">
            
          
            
              <button type="submit" name="method" value="code" class="submit">Continue</button>
            
          
          
        </form>
is this still correct?
@chilly-king-10285
Copy code
<form class="form" action="<http://localhost:4433/self-service/recovery?flow=a6f18415-6bc0-4b9f-a1ad-abe5c8f18d2b>" method="POST ">
          
            
              <input type="hidden" name="csrf_token" value="61uryThAscWC6TGg8JftQ9PwKRnIwVwkjy/3yzn9MJBaHmx3MLlzs3tYaQAaX4auKwxOFDo7xhw04apY8lkJFw==" required="">
            
          
            
              <input data-slot="input" class="file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive" placeholder="Your email address here" name="email" value="" required="" type="email">
            
          
            
              <button type="submit" name="method" value="code" class="submit">Continue</button>
            
          
          
        </form>
is this still correct?
b
A message from kapa.ai
Yes, your form is now correct for submitting a recovery flow to Ory Kratos. - The
action
attribute points to the correct endpoint with the required
flow
parameter:
/self-service/recovery?flow=a6f18415-6bc0-4b9f-a1ad-abe5c8f18d2b
- The
method
is
POST
(note: remove any trailing spaces in
"POST "
to avoid issues). - The form includes a hidden input for
csrf_token
with the correct name and value. - The email input has
name="email"
, which is required for Kratos to process the recovery request. - The submit button has
name="method"
and
value="code"
, which is correct for the code-based recovery method. This structure matches the requirements and examples in the Ory Kratos documentation for submitting a recovery flow [Submit recovery flow](https://www.ory.sh/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#submit-recovery-flow). Summary of what to check: - Ensure the
method
attribute is exactly
"POST"
(no trailing spaces). - All required fields (
csrf_token
,
email
,
method
) are present and named correctly. With these in place, your form should work as expected with Ory Kratos.
q
@chilly-king-10285 But why does KRatos redirect me to my error page?
b
A message from kapa.ai
Ory Kratos redirects you to your error page when something goes wrong during the self-service flow, such as a malformed request, missing required parameters, or a system error. In your previous logs, Kratos reported issues like missing the
email
property or a malformed or missing
flow
query parameter. When such errors occur, Kratos is configured to redirect the user to the error UI you have set in your configuration:
Copy code
yaml selfservice:   flows:     error:       ui_url: <http://my-app.com/error>
If you see a redirect to your error page, it means Kratos encountered an error it could not recover from in the flow, and is forwarding the user to the error UI as specified in your configuration. You can check the error details by inspecting the error UI or by reviewing the Kratos logs for the specific error message. For more information, see the [Kratos Fallback/Error UI documentation](https://www.ory.sh/docs/kratos/fallback/error).
q
@chilly-king-10285
Copy code
time=2025-06-23T19:18:34Z level=info msg=Encountered self-service settings error. audience=audit error=map[debug: message:The request was malformed or contained invalid parameters reason:Requested return_to URL "<https://api.stag.thommie.be/login>" is not allowed. status:Bad Request status_code:400] http_request=map[headers:map[accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 accept-encoding:gzip, deflate, br, zstd accept-language:en-US,en;q=0.9,fr;q=0.8,nl;q=0.7 cache-control:max-age=0 content-length:151 content-type:application/x-www-form-urlencoded cookie:Value is sensitive and has been redacted. To see the value set config key "log.leak_sensitive_values = true" or environment variable "LOG_LEAK_SENSITIVE_VALUES=true". dnt:1 origin:<https://identity.stag.thommie.be> priority:u=0, i referer:<https://identity.stag.thommie.be/settings?flow=e20adfc6-ddef-428b-b38a-cedd3dc274ab> sec-ch-ua:"Chromium";v="137", "Not/A)Brand";v="24" sec-ch-ua-mobile:?0 sec-ch-ua-platform:"macOS" sec-fetch-dest:document sec-fetch-mode:navigate sec-fetch-site:same-origin sec-fetch-user:?1 upgrade-insecure-requests:1 user-agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 x-amzn-trace-id:Root=1-6859a889-4016f63216b0d1ca22a9f604 x-forwarded-for:213.49.51.23 x-forwarded-port:443 x-forwarded-proto:https] host:<http://identity.stag.thommie.be|identity.stag.thommie.be> method:POST path:/self-service/settings query:Value is sensitive and has been redacted. To see the value set config key "log.leak_sensitive_values = true" or environment variable "LOG_LEAK_SENSITIVE_VALUES=true". remote:10.0.90.73:50630 scheme:http] service_name=Ory Kratos service_version=v1.3.1 settings_flow=map[Type:browser active: id:e20adfc6-ddef-428b-b38a-cedd3dc274ab nid:84521698-4582-4a5e-9268-446986f59c32 request_url:<https://identity.stag.thommie.be/self-service/recovery?flow=369f3c2b-eb5e-4e0f-9fd4-60524a5a030f&return_to=%2Flogin> return_to:/login state:show_form]
b
A message from kapa.ai
The error message you are seeing:
Copy code
reason:Requested return_to URL "<https://api.stag.thommie.be/login>" is not allowed. status:Bad Request status_code:400
means that the
return_to
URL you provided (`https://api.stag.thommie.be/login`) is not included in the list of allowed return URLs in your Ory Kratos configuration. Ory Kratos enforces an allow list for dynamic redirects using the
return_to
parameter to prevent open redirect attacks. If a
return_to
URL is not explicitly allowed in your configuration, Kratos will reject the request with this error [Browser redirects and flow completion](https://www.ory.sh/docs/concepts/redirects). How to fix: You need to add the domain and path of your desired return URL to the
selfservice.allowed_return_urls
configuration in your Kratos config file. For example:
Copy code
yaml selfservice:   allowed_return_urls:     - <https://api.stag.thommie.be/login>
After updating your configuration, restart Kratos for the changes to take effect. Summary: The error occurs because the
return_to
URL is not on the allow list. Add it to
selfservice.allowed_return_urls
in your Kratos configuration to resolve the issue.
q
@chilly-king-10285 where does the return_to get set?
b
A message from kapa.ai
The
return_to
parameter is set when you initialize a self-service flow (such as login, registration, recovery, verification, or settings) by including it as a query parameter in the URL. For example, when starting a login flow, you can set it like this:
Copy code
/self-service/login/browser?return_to=<https://myapp.com/posts>
This tells Ory Kratos where to redirect the user after the flow is successfully completed. The same applies to other flows, such as registration, recovery, and settings. The
return_to
parameter is not automatically persisted across different flows; if you want to keep it, you must explicitly pass it again when initializing a new flow [Advanced integration](https://www.ory.sh/docs/kratos/bring-your-own-ui/custom-ui-advanced-integration) [Browser redirects and flow completion](https://www.ory.sh/docs/concepts/redirects). Additionally, for security, the value of
return_to
must match one of the URLs in your
selfservice.allowed_return_urls
configuration, otherwise Kratos will reject the request [HTTP redirection configuration](https://www.ory.sh/docs/kratos/concepts/browser-redirect-flow-completion#redirection). In summary: You set
return_to
as a query parameter when initializing a self-service flow.
q
@chilly-king-10285 mine goes via Hydra
b
A message from kapa.ai
When using Ory Kratos with Ory Hydra (for OAuth2/OIDC flows), the redirection after authentication is managed primarily by Hydra’s
redirect_uri
parameter, not directly by Kratos’
return_to
parameter. Here’s how it works according to the knowledge sources: - When an OAuth2 flow is initiated, the client application sends a request to Hydra’s
/oauth2/auth
endpoint with a
redirect_uri
. - Hydra then redirects the user to the Kratos login (or registration) UI, typically passing a
login_challenge
(not a
return_to
) as a query parameter. - The Kratos UI or Account Experience should use the
login_challenge
to interact with Hydra and, after successful authentication, accept the login request and redirect the user back to Hydra, which then redirects to the original
redirect_uri
provided by the client. There is discussion in the knowledge sources about the relationship between
redirect_uri
,
login_challenge
, and
return_to
. Specifically, the Account Experience UI does not automatically transfer the
redirect_uri
from Hydra to the Kratos
return_to
parameter. Instead, the flow is designed so that after login/registration, the user is returned to Hydra, which then handles the final redirect to the client’s
redirect_uri
 [Account Experience should transfer the
redirect_uri
query parameter from Hydra to the login
return_to
query parameter](https://github.com/ory/network/issues/264). If you want to persist the OAuth2 login flow through more complex identity flows (like account recovery), Ory provides an "Always Return to OAuth2 provider" setting, which ensures the user is always redirected back to Hydra after the identity flow, regardless of the
return_to
parameter [Identity Provider Integration Settings](https://www.ory.sh/docs/oauth2-oidc/identity-provider-integration-settings#overview). Summary: When using Kratos with Hydra, the
return_to
parameter is not the main mechanism for post-authentication redirection. Instead, Hydra’s
redirect_uri
and the OAuth2 flow manage where the user ends up after authentication. Kratos’
return_to
is only relevant for direct self-service flows, not for OAuth2/OIDC flows via Hydra. If you need to ensure the user is always returned to the OAuth2 provider, enable the "Always Return to OAuth2 provider" setting in your Ory project [Identity Provider Integration Settings](https://www.ory.sh/docs/oauth2-oidc/identity-provider-integration-settings#overview).
q
who sets the redirect uri?
@chilly-king-10285 who sets the redirect uri
b
A message from kapa.ai
In the context of Ory Hydra and OAuth2/OIDC flows, the OAuth2 client application (the app that wants to authenticate the user) sets the
redirect_uri
. This is done when the client initiates the OAuth2 flow by making a request to Hydra’s
/oauth2/auth
endpoint and includes the
redirect_uri
as a query parameter. This URI tells Hydra where to send the user after the authentication and consent process is complete. For example:
Copy code
/oauth2/auth?client_id=...&redirect_uri=<https://my-app.com/callback&>...
Hydra then manages the flow, and after successful authentication (and consent), redirects the user to the
redirect_uri
that was set by the client application, provided it matches one of the URIs registered for that client [OAuth2 authorization code flow](https://www.ory.sh/docs/oauth2-oidc/authorization-code-flow). So, the redirect URI is set by the OAuth2 client application when starting the authentication flow.
q
@chilly-king-10285
Copy code
/**
 * AuthClient handles OAuth2 authentication for an application, including generating authorization URIs,
 * obtaining and refreshing tokens, and managing token data.
 */
import { generateCodeVerifier, OAuth2Client } from "@badgateway/oauth2-client";

// Interfaces
export interface TokenResponse {
  access_token: string;
  token_type: string;
  expires_in: number;
  refresh_token: string;
  scope: string;
}
export interface OAuthToken {
  accessToken: string;
  tokenType: string;
  expiresAt: number;
  refreshToken: string;
  scope: string;
}

class AuthClient {
  /**
   * A verifier string for PKCE (Proof Key for Code Exchange).
   * @type {string | undefined}
   */
  private codeVerifier: string | undefined;

  /**
   * A random string used as a state parameter in OAuth2 flows.
   * @type {string | undefined}
   */
  private randomState: string | undefined;

  /**
   * Object containing OAuth token information.
   * @type {OAuthToken}
   */
  private clientData: OAuthToken;

  /**
   * The redirect URI used for OAuth2 callbacks.
   * @type {string}
   */
  private redirectUri: string;

  /**
   * Instance of the OAuth2 client.
   * @type {OAuth2Client}
   */
  client: OAuth2Client;

  /**
   * Initializes an instance of AuthClient with predefined OAuth2 client settings.
   */
  constructor() {
    this.redirectUri = "<http://localhost:5173/callback>";
    this.client = new OAuth2Client({
      server: "<https://identity.stag.thommie.be>",
      clientId: "2a5e075c-fa14-4fbb-8b14-ce04f39358de",
      tokenEndpoint: "/oauth2/token",
      authorizationEndpoint: "/oauth2/auth",
    });
    this.clientData = {
      accessToken: "",
      tokenType: "",
      expiresAt: 0,
      refreshToken: "",
      scope: "",
    };
  }

  /**
   * Initializes the client by generating and storing the code verifier and state.
   * @async
   * @returns {Promise<void>}
   */
  initializeClient = async (): Promise<void> => {
    this.randomState = this.ensureRandomState();
    this.codeVerifier = await this.ensureCodeVerifier();
  };

  /**
   * Generates a random string for OAuth2 state or PKCE code verification.
   * @private
   * @returns {string} A random alphanumeric string.
   */
  private getRandomString = (): string =>
    Math.random().toString(36).substring(2, 15);

  /**
   * Ensures that a random state is available, generating and storing one if not found.
   * @private
   * @returns {string} The OAuth2 state.
   */
  private ensureRandomState = (): string => {
    const storedState = sessionStorage.getItem("oauth_random_state");
    if (storedState) return storedState;
    const newState = this.getRandomString() + this.getRandomString();
    sessionStorage.setItem("oauth_random_state", newState);
    return newState;
  };

  /**
   * Ensures that a code verifier is available, generating and storing one if not found.
   * @private
   * @async
   * @returns {Promise<string>} The PKCE code verifier.
   */
  private ensureCodeVerifier = async (): Promise<string> => {
    const storedCodeVerifier = sessionStorage.getItem("oauth_code_verifier");
    if (storedCodeVerifier) return storedCodeVerifier;
    const newCodeVerifier = await generateCodeVerifier();
    sessionStorage.setItem("oauth_code_verifier", newCodeVerifier);
    return newCodeVerifier;
  };

  /**
   * Extracts the authorization code from a URL.
   * @private
   * @param {string} url - The URL to extract the code from.
   * @returns {string} The authorization code.
   */
  private extractCodeFromUrl = (url: string): string => {
    try {
      return new URL(url).searchParams.get("code") ?? "";
    } catch (error) {
      console.error("Invalid URL:", error);
      return "";
    }
  };

  /**
   * Maps a token response to the OAuthToken interface.
   * @private
   * @param {TokenResponse} response - The token response from the server.
   * @returns {OAuthToken} The mapped token object.
   */
  private mapResponse = (response: TokenResponse): OAuthToken => ({
    accessToken: response?.access_token,
    tokenType: response.token_type,
    expiresAt: new Date(Date.now() + response.expires_in * 1000).getTime(),
    refreshToken: response.refresh_token,
    scope: response.scope,
  });

  /**
   * Retrieves the authorization URI for the OAuth2 flow.
   * @async
   * @returns {Promise<string>} The authorization URI.
   * @throws {Error} If the state is not initialized.
   */
  getAuthorizeUri = async (): Promise<string> => {
    if (!this.randomState)
      throw new Error("OAuth2 authentication state not found");

    return await this.client.authorizationCode.getAuthorizeUri({
      redirectUri: this.redirectUri,
      state: this.randomState,
      codeVerifier: this.codeVerifier,
      scope: ["openid offline_access"],
    });
  };

  /**
   * Exchanges the authorization code for an OAuth2 token.
   * @async
   * @returns {Promise<void>}
   * @throws {Error} If the authorization code or token response is invalid.
   */
  getOAuth2Token = async (): Promise<string> => {
    try {
      const { server, clientId, tokenEndpoint } = this.client.settings;
      const code = this.extractCodeFromUrl(document.URL);

      if (!code) throw new Error("Authorization code not found in the URL");

      const postData = new URLSearchParams({
        code,
        client_id: clientId,
        redirect_uri: this.redirectUri,
        grant_type: "authorization_code",
        code_verifier: String(this.codeVerifier),
      });

      const response = await fetch(`${server}${tokenEndpoint}`, {
        method: "POST",
        body: postData,
        headers: { "Content-Type": "application/x-www-form-urlencoded" },
      });

      if (!response.ok) this.initiateLogin();

      const responseData = this.mapResponse(await response.json());

      this.clientData = responseData;
      return responseData.accessToken;
    } catch (error) {
      console.error("Error fetching OAuth2 token:", error);
      throw error;
    }
  };

  /**
   * Refreshes the access token using the refresh token.
   * @async
   * @returns {Promise<void>}
   * @throws {Error} If the refresh token is missing or the refresh fails.
   */
  refreshAccessToken = async (): Promise<string> => {
    try {
      const { server, clientId, tokenEndpoint } = this.client.settings;
      const { refreshToken } = this.clientData;

      if (!refreshToken) throw new Error("No refreshToken found");

      const postData = new URLSearchParams({
        grant_type: "refresh_token",
        refresh_token: refreshToken,
        client_id: clientId,
      });

      const response = await fetch(`${server}${tokenEndpoint}`, {
        method: "POST",
        body: postData,
        headers: { "Content-Type": "application/x-www-form-urlencoded" },
      });

      if (!response.ok) this.initiateLogin();

      const responseData = this.mapResponse(await response.json());

      this.clientData = responseData;
      return responseData.accessToken;
    } catch (error) {
      console.error("Error refreshing token:", error);
      throw error;
    }
  };

  /**
   * Redirects the user to the login page to initiate the OAuth2 flow.
   * @async
   * @returns {Promise<void>}
   */
  initiateLogin = async (): Promise<void> => {
    await client.initializeClient();
    const authorizeUri = await client.getAuthorizeUri();

    if (!authorizeUri)
      throw new Error("Failed to retrieve the authorization URI.");

    document.location.assign(authorizeUri);
  };

  /**
   * Checks if the user is currently logged in based on token expiration.
   * @returns {boolean} True if the user is logged in, false otherwise.
   */
  isLoggedIn = (): boolean => {
    try {
      const { expiresAt } = this.clientData;
      const now = Date.now();

      if (expiresAt <= now) return false;

      return true;
    } catch (error) {
      console.error("Error while checking token validity:", error);
      return false;
    }
  };
}

export const client = new AuthClient();
how come I see that the redirect uri is not the same as in the log here:
Copy code
time=2025-06-23T19:18:33Z level=info msg=started handling request http_request=map[headers:map[accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 accept-encoding:gzip, deflate, br, zstd accept-language:en-US,en;q=0.9,fr;q=0.8,nl;q=0.7 cache-control:max-age=0 content-length:151 content-type:application/x-www-form-urlencoded cookie:Value is sensitive and has been redacted. To see the value set config key "log.leak_sensitive_values = true" or environment variable "LOG_LEAK_SENSITIVE_VALUES=true". dnt:1 origin:<https://identity.stag.thommie.be> priority:u=0, i referer:<https://identity.stag.thommie.be/settings?flow=e20adfc6-ddef-428b-b38a-cedd3dc274ab> sec-ch-ua:"Chromium";v="137", "Not/A)Brand";v="24" sec-ch-ua-mobile:?0 sec-ch-ua-platform:"macOS" sec-fetch-dest:document sec-fetch-mode:navigate sec-fetch-site:same-origin sec-fetch-user:?1 upgrade-insecure-requests:1 user-agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 x-amzn-trace-id:Root=1-6859a889-4016f63216b0d1ca22a9f604 x-forwarded-for:213.49.51.23 x-forwarded-port:443 x-forwarded-proto:https] host:<http://identity.stag.thommie.be|identity.stag.thommie.be> method:POST path:/self-service/settings query:Value is sensitive and has been redacted. To see the value set config key "log.leak_sensitive_values = true" or environment variable "LOG_LEAK_SENSITIVE_VALUES=true". remote:10.0.90.73:50630 scheme:http]
b
A message from kapa.ai
Based on your code and the log, it appears you are confused about why the
redirect_uri
you set in your OAuth2 client (`http://localhost:5173/callback`) is not the same as the URL seen in the Kratos log (`https://api.stag.thommie.be/login`). Here’s what’s happening, according to the knowledge sources: - In OAuth2/OIDC flows with Ory Hydra, the
redirect_uri
is set by the OAuth2 client application when it initiates the flow with Hydra’s
/oauth2/auth
endpoint. This is the URI to which Hydra will redirect the user after authentication and consent [OAuth2 authorization code flow](https://www.ory.sh/docs/oauth2-oidc/authorization-code-flow). - The log you posted is from a self-service settings flow in Kratos, not from the OAuth2 callback. The
return_to
parameter in Kratos flows is separate from the OAuth2
redirect_uri
. The
return_to
parameter is used for browser-based self-service flows (like settings, recovery, etc.) and must be whitelisted in Kratos’ configuration [Browser redirects and flow completion](https://www.ory.sh/docs/concepts/redirects). - If you see a
return_to
or redirect URL in the Kratos logs that does not match your OAuth2 client’s
redirect_uri
, it is likely because a self-service flow (such as settings or recovery) is being completed, and the redirect is being handled by Kratos, not by Hydra. The two mechanisms are independent, and the URLs do not have to match. Why the mismatch? - The
redirect_uri
in your OAuth2 client is only relevant to the OAuth2 flow managed by Hydra. - The
return_to
or redirect URL in the Kratos log is relevant to Kratos self-service flows and is controlled by the frontend or by the
allowed_return_urls
configuration in Kratos. - If you are chaining flows (for example, a user is sent to a settings or recovery flow during an OAuth2 login), the final redirect may be determined by the Kratos flow, not by the original OAuth2
redirect_uri
, unless you have configured Kratos to always return to the OAuth2 provider after such flows [Identity Provider Integration Settings](https://www.ory.sh/docs/oauth2-oidc/identity-provider-integration-settings#overview). Summary: The
redirect_uri
in your OAuth2 client and the
return_to
or redirect URL in Kratos logs are set and used in different contexts. If you want to ensure users always return to the OAuth2 provider after a Kratos flow, you need to enable the "Always Return to OAuth2 provider" setting in your Ory project. Otherwise, Kratos will use its own redirect logic, which may not match your OAuth2 client’s
redirect_uri
 [Account Experience should transfer the
redirect_uri
query parameter from Hydra to the login
return_to
query parameter](https://github.com/ory/network/issues/264). If you need more precise control, ensure your frontend and Kratos configuration are aligned regarding allowed return URLs and how flows are chained. If you need to debug further, check the actual requests and parameters being sent to both Hydra and Kratos during the flow.
q
@chilly-king-10285 courier: delivery_strategy: http http: request_config: url: ${identity_mail_url}/email method: POST body: file://${identity_email_jsonnet_path} headers: "Content-Type": "application/json" "X-API-KEY": ${identity_api_key} serve: public: base_url: ${base_url} selfservice: default_browser_return_url: ${base_url} allowed_return_urls: - ${base_url} methods: password: enabled: true flows: settings: ui_url: ${base_url}/settings after: default_browser_return_url: ${thommie_api_url}/ verification: enabled: false recovery: enabled: true ui_url: ${base_url}/recovery after: hooks: - hook: revoke_active_sessions logout: after: default_browser_return_url: ${base_url}/login login: ui_url: ${base_url}/login registration: enabled: false error: ui_url: ${base_url}/error log: level: ${log_level} leak_sensitive_values: false secrets: cookie: - ${openssl_key_cookie} cipher: - ${openssl_key_cipher} session: lifespan: 24h hashers: argon2: parallelism: 1 memory: 128KB iterations: 2 salt_length: 16 key_length: 16 identity: schemas: - id: default url: file://${identity_schema_path} oauth2_provider: url: ${oauth2_provider_url} the after in settings ui shouldn'
b
A message from kapa.ai
It looks like your question was cut off, but from your configuration and the context, you seem to be asking about the behavior of the
after
configuration in the
settings
flow, specifically:
Copy code
yaml flows:   settings:     ui_url: ${base_url}/settings     after:       default_browser_return_url: ${thommie_api_url}/
What does this do? According to the Ory Kratos documentation, the
after.default_browser_return_url
under the
settings
flow determines where the user is redirected after successfully completing the settings flow (for example, after updating their profile or password) [Browser redirects and flow completion](https://www.ory.sh/docs/concepts/redirects#redirect-flows). So, in your configuration, after a successful settings update, the user will be redirected to
${thommie_api_url}/
. Important notes: - The redirect will only work if
${thommie_api_url}/
is included in your
selfservice.allowed_return_urls
. In your config, only
${base_url}
is allowed, so you must add
${thommie_api_url}/
to
allowed_return_urls
for this redirect to work. Otherwise, Kratos will reject the redirect and may send the user to the error UI with a message like "Requested return_to URL is not allowed" [Browser redirects and flow completion](https://www.ory.sh/docs/kratos/concepts/browser-redirect-flow-completion#redirection). - The
after
configuration for the settings flow only affects the redirect after a successful settings update, not after other flows. Summary: The
after.default_browser_return_url
in the settings flow controls where users are sent after a successful settings update. Make sure the target URL is whitelisted in
allowed_return_urls
to avoid redirect errors.
q
@chilly-king-10285 who controls whether a return_to url is valid
b
A message from kapa.ai
The validity of a
return_to
URL in Ory Kratos is controlled by the Kratos server itself, based on the
selfservice.allowed_return_urls
configuration. When a
return_to
parameter is provided (for example, when initializing a self-service flow), Kratos checks if the URL matches one of the entries in the
allowed_return_urls
list. If the URL is not on this allow list, Kratos will reject the request and may redirect the user to the error UI with a message indicating the URL is not allowed [Browser redirects and flow completion](https://www.ory.sh/docs/concepts/redirects) [HTTP redirection configuration](https://www.ory.sh/docs/kratos/concepts/browser-redirect-flow-completion#redirection). So, the Kratos server and its configuration determine whether a
return_to
URL is valid
.
q
@chilly-king-10285 do you need /* for return_to's? And for my settings flow the return_to was probably not set by the client but by my config right
b
A message from kapa.ai
You **do not need to use `/*`** for
return_to
URLs in your
selfservice.allowed_return_urls
configuration. Ory Kratos matches the full URL or path you specify. For example, if you set:
Copy code
yaml selfservice:   allowed_return_urls:     - <https://myapp.com/dashboard>     - <https://myapp.com/login>
then only those exact URLs are allowed. If you want to allow all paths under a domain, you need to explicitly list them or use the domain root (e.g., `https://myapp.com/`), which allows any path under that domain [Browser redirects and flow completion](https://www.ory.sh/docs/concepts/redirects). Regarding your settings flow: If you did not explicitly set a
return_to
parameter when initializing the flow from the client, then the redirect after the settings flow is determined by your Kratos configuration, specifically the
after.default_browser_return_url
under the
settings
flow. This means the redirect URL is set by your config, not by the client [Browser redirects and flow completion](https://www.ory.sh/docs/concepts/redirects#redirect-flows). Summary: - You do not need
/*
for
return_to
URLs; use the domain or specific paths as needed. - For the settings flow, if the client did not set
return_to
, the redirect is controlled by your Kratos config.