<@U04UQ68083H> I have a nextjs app that handles si...
# ory-copilot
w
@chilly-king-10285 I have a nextjs app that handles sign in and stuff for kratos. Hydra should be connected to kratos as the oauth2_provider url is provided in the kratos config. How do I initiate a login flow from a client app that goes to kratos for authentication and redirects back?
c
A message from kapa.ai
To initiate a login flow in your Next.js client app that authenticates users via Ory Kratos (with Ory Hydra as the OAuth2 provider), you should follow the standard Ory self-service login flow for browser-based applications. Here’s how you can do it: ### 1. Start the OAuth2 Authorization Flow Your client app should start by redirecting the user to the Hydra OAuth2
/auth
endpoint, including the required parameters (
client_id
,
redirect_uri
,
response_type
,
scope
, etc.). This is the standard OAuth2 authorization code flow. Example:
Copy code
GET https://<hydra-domain>/oauth2/auth?response_type=code&client_id=...&redirect_uri=...&scope=openid
### 2. Hydra Redirects to Kratos Login Hydra, when configured with Kratos as the
oauth2_provider_url
, will redirect the user to the Kratos login UI (the URL you set in Kratos config as
selfservice.flows.login.ui_url
). The redirect will include a
login_challenge
parameter. Example:
Copy code
https://<kratos-login-ui>?login_challenge=...
Your Next.js app should have a route (e.g.,
/login
) that handles this and initializes or fetches the login flow from Kratos. ### 3. Initialize or Fetch the Login Flow in Next.js In your Next.js app, use the Ory SDK or REST API to fetch or create the login flow. You can use the
getLoginFlow
function from
@ory/nextjs/app
to fetch the flow using the query parameters (including
flow
and possibly
login_challenge
). Example (using Ory Elements and Next.js):
Copy code
js import { getLoginFlow, OryPageParams } from "@ory/nextjs/app"  export default async function LoginPage(props: OryPageParams) {   const flow = await getLoginFlow(config, props.searchParams)   if (!flow) return null   // Render the login form using the flow data }
This will render the login form provided by Kratos, including any social sign-in or password fields as configured in your Kratos instance. The form action will point to the correct Kratos endpoint for submitting credentials. After successful authentication, Kratos will redirect the user back to Hydra to continue the OAuth2 flow, and finally, the user will be redirected to your app’s `redirect_uri`[getLoginFlow() reference](https://www.ory.sh/docs/elements/reference/@ory/nextjs/app/functions/getLoginFlow). ### 4. Handle the Redirect Back After the user successfully logs in, Kratos and Hydra will complete the OAuth2 flow and redirect the user back to your app’s
redirect_uri
with the authorization code, which your app can then exchange for tokens. --- Summary: - Start the OAuth2 flow by redirecting to Hydra’s
/oauth2/auth
. - Hydra redirects to your Kratos login UI. - Your Next.js app fetches/creates the login flow and renders the form. - On successful login, Kratos/Hydra redirect back to your app. This flow is standard for integrating Ory Kratos and Hydra, and the Ory Elements/SDKs provide helpers to make it seamless in Next.js apps. For more details and code examples, see the [Ory Elements Next.js getLoginFlow documentation](https://www.ory.sh/docs/elements/reference/@ory/nextjs/app/functions/getLoginFlow) and [custom UI integration guide](https://www.ory.sh/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#create-login-flow).
w
@chilly-king-10285 Make sure to use the @ory/client library for exampls.
c
A message from kapa.ai
To initiate a login flow from your Next.js client app using Ory Kratos (with Hydra as the OAuth2 provider), and using the
@ory/client
library, you should: 1. Redirect the user to the OAuth2 authorization endpoint (Hydra), which will in turn redirect to your Kratos login UI. 2. In your Next.js app, use
@ory/client
to fetch or create the login flow
and render the login form. 3. On successful login, Kratos and Hydra will handle the redirects back to your app. Below is a practical example using
@ory/client
in a Next.js context: --- ### 1. Install the Ory Client
Copy code
bash npm install @ory/client
--- ### 2. Fetch or Create the Login Flow in Next.js You typically have a
/login
route in your Next.js app. When Hydra redirects to your login UI, it will include a
login_challenge
parameter. You need to fetch the login flow from Kratos using this parameter.
Copy code
js import { useEffect, useState } from "react" import { useRouter } from "next/router" import { Configuration, FrontendApi, LoginFlow, UiNode, UiNodeInputAttributes } from "@ory/client"  const kratos = new FrontendApi(   new Configuration({     basePath: "https://<your-kratos-public-endpoint>", // e.g., <https://project-slug.projects.oryapis.com>     baseOptions: {       withCredentials: true, // Important for cookies/session     },   }) )  export default function Login() {   const router = useRouter()   const [flow, setFlow] = useState<LoginFlow>()    useEffect(() => {     const { flow: flowId, login_challenge } = router.query      if (flowId) {       // Fetch existing flow       kratos.getLoginFlow({ id: flowId as string })         .then(({ data }) => setFlow(data))         .catch(() => {           // Handle error, possibly redirect to initialize a new flow         })     } else {       // Create a new login flow, passing the login_challenge if present       kratos.createBrowserLoginFlow({ loginChallenge: login_challenge as string })         .then(({ data }) => {           // Redirect to self with ?flow=<id> to keep the flow in the URL           router.replace(`/login?flow=${data.id}`, undefined, { shallow: true })         })         .catch(() => {           // Handle error         })     }   }, [router.query])    if (!flow) return <div>Loading...</div>    // Render the login form using flow.ui.nodes   return (     <form action={flow.ui.action} method={flow.ui.method}>       {flow.ui.nodes.map((node, idx) => {         if (node.type === "input") {           const attrs = node.attributes as UiNodeInputAttributes           return (             <input               key={idx}               name={attrs.name}               type={attrs.type}               defaultValue={attrs.value}               required={attrs.required}               disabled={attrs.disabled}             />           )         }         // Handle other node types as needed         return null       })}       <button type="submit">Sign in</button>     </form>   ) }
This code: - Checks for an existing flow (
flow
query param) and fetches it, or creates a new one using
createBrowserLoginFlow
. - Renders the login form using the nodes provided by Kratos. --- ### 3. What Happens Next - On successful login, Kratos will redirect the user back to Hydra (using the
login_challenge
), and Hydra will complete the OAuth2 flow, finally redirecting the user to your app’s
redirect_uri
. --- References: - [Get login flow example with @ory/client](https://www.ory.sh/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#get-login-flow) - [Create login flow example with @ory/client](https://www.ory.sh/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#create-login-flow) If you need to handle form submission programmatically (e.g., via AJAX), you can use
updateLoginFlow
from
@ory/client
as shown in the [submit login flow example](https://www.ory.sh/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#submit-login-flow).
w
@chilly-king-10285 is there a function for redirecting the user to hydra?
c
A message from kapa.ai
I'm sorry, but the knowledge sources do not mention a specific function in the
@ory/client
library (or any Ory SDK) that programmatically redirects the user to Hydra's OAuth2 authorization endpoint. The documented approach is to construct the authorization URL yourself and perform a standard browser redirect (e.g., by setting
window.location
in the browser or using a server-side redirect) to Hydra's
/oauth2/auth
endpoint with the required parameters such as
client_id
,
redirect_uri
,
response_type
, and
scope
 [OAuth2 authorization code flow](https://www.ory.sh/docs/oauth2-oidc/authorization-code-flow#step-1-get-the-users-permission). There is no mention of a helper or utility function in the Ory libraries that abstracts this redirect step. You are expected to handle the redirect in your application code.
w
@chilly-king-10285 Make a function that does that for me.
c
A message from kapa.ai
Certainly! While there is no built-in function in
@ory/client
to perform the redirect to Hydra's OAuth2 authorization endpoint, you can create a simple utility function in your application to handle this. The knowledge sources show that the redirect is done by constructing the authorization URL with the required parameters and then redirecting the browser to that URL using
window.location
 [OAuth2 authorization code flow](https://www.ory.sh/docs/oauth2-oidc/authorization-code-flow#step-1-get-the-users-permission). Here is an example function in JavaScript:
Copy code
js function redirectToHydraAuth({   projectSlug,   clientId,   redirectUri,   scope = "openid",   state,   responseType = "code",   codeChallenge,   codeChallengeMethod = "S256" }) {   const params = new URLSearchParams({     response_type: responseType,     client_id: clientId,     redirect_uri: redirectUri,     scope,     state,   })    if (codeChallenge) {     params.append("code_challenge", codeChallenge)     params.append("code_challenge_method", codeChallengeMethod)   }    const url = `https://${projectSlug}.<http://projects.oryapis.com/oauth2/auth?${params.toString()}|projects.oryapis.com/oauth2/auth?${params.toString()}>`   window.location = url }
Usage example:
Copy code
js redirectToHydraAuth({   projectSlug: "your-project-slug",   clientId: "your-client-id",   redirectUri: "<https://your-app.com/callback>",   scope: "openid offline_access",   state: "random-state-value",   codeChallenge: "your-pkce-code-challenge" })
This function follows the documented approach for starting the OAuth2 flow with Ory Hydra [SPA and public client with PKCE](https://www.ory.sh/docs/oauth2-oidc/authorization-code-flow#spa-and-public-client-with-pkce). > Note: You must generate the
state
and (if using PKCE) the
code_challenge
and
code_verifier
as described in the PKCE documentation [Perform PKCE](https://www.ory.sh/docs/oauth2-oidc/authorization-code-flow#perform-pkce). There is no official helper in
@ory/client
for this, so this is the recommended approach based on the knowledge sources.
w
@chilly-king-10285 I don't need the PKCE stuff if it isn't enabled by default. Make sure everything is typesafe in that function and im going to be using my own hydra instance.
c
A message from kapa.ai
Certainly! According to the Ory documentation, to initiate the OAuth2 Authorization Code flow with Hydra, you construct the authorization URL with the required parameters and redirect the user to it. There is no built-in function in
@ory/client
for this, so you should implement your own function. PKCE is only required for public clients (such as SPAs or mobile apps) and is not mandatory unless you have enabled it or are using a public client. For confidential clients, you can omit PKCE parameters [OAuth2 authorization code flow](https://www.ory.sh/docs/oauth2-oidc/authorization-code-flow#web-server-app-and-confidential-client). Below is a type-safe TypeScript function for redirecting the user to your own Hydra instance, without PKCE:
Copy code
ts type RedirectToHydraAuthOptions = {   hydraAdminUrl: string // e.g., "<https://hydra.example.com>"   clientId: string   redirectUri: string   scope?: string   state: string   responseType?: string   prompt?: string }  function redirectToHydraAuth({   hydraAdminUrl,   clientId,   redirectUri,   scope = "openid",   state,   responseType = "code",   prompt, }: RedirectToHydraAuthOptions): void {   const params = new URLSearchParams({     response_type: responseType,     client_id: clientId,     redirect_uri: redirectUri,     scope,     state,   })    if (prompt) {     params.append("prompt", prompt)   }    const url = `${hydraAdminUrl.replace(/\/$/, "")}/oauth2/auth?${params.toString()}`   window.location.assign(url) }
Usage example:
Copy code
ts redirectToHydraAuth({   hydraAdminUrl: "<https://hydra.example.com>",   clientId: "your-client-id",   redirectUri: "<https://your-app.com/callback>",   state: "random-state-value",   scope: "openid offline_access", })
This approach is directly based on the Ory documentation for confidential clients and does not include PKCE parameters, as they are not required unless you are using a public client or have specifically enabled PKCE [Web server app and confidential client](https://www.ory.sh/docs/oauth2-oidc/authorization-code-flow#web-server-app-and-confidential-client). If you need to support additional parameters, you can extend the function accordingly.
w
@chilly-king-10285 Make me a command to create a new oauth2-client with the hydra cli that would be for this.
c
A message from kapa.ai
To create a new OAuth2 client for your Next.js app using the Hydra CLI, you should use the
hydra create oauth2-client
command. Based on the knowledge sources, a typical command for a confidential client (such as a web server app) using the authorization code flow would look like this:
Copy code
bash hydra create oauth2-client \   --name "Next.js App Client" \   --grant-type authorization_code \   --grant-type refresh_token \   --response-type code \   --scope openid \   --redirect-uri <https://your-app.com/callback>
- Replace
<https://your-app.com/callback>
with your actual redirect URI. - You can add more scopes or redirect URIs as needed. This command will output the client ID and client secret, which you will use in your Next.js app to initiate the OAuth2 flow with Hydra [hydra create oauth2-client](https://www.ory.sh/docs/hydra/cli/hydra-create-oauth2-client#hydra-create-oauth2-client) [Example OAuth2 clients](https://www.ory.sh/docs/hydra/guides/oauth2-clients#example-oauth2-clients).
w
Copy code
hydra create oauth2-client \
  --name "Test App 1" \
  --grant-type authorization_code \
  --grant-type refresh_token \
  --response-type code \
  --scope openid \
  --redirect-uri <http://127.0.0.1:3001/auth/callback/> \
  --endpoint "<http://127.0.0.1:4433>"
What should the endpoint point too? @chilly-king-10285
c
A message from kapa.ai
The
--endpoint
flag in the
hydra create oauth2-client
command should point to the administrative API endpoint of your Ory Hydra instance, not the public endpoint. By default, this is usually
<http://127.0.0.1:4445>
for local setups. The administrative API is used for managing clients and other administrative tasks, while the public API (default
4444
) is used for OAuth2 flows. So, your command should look like:
Copy code
bash hydra create oauth2-client \   --name "Test App 1" \   --grant-type authorization_code \   --grant-type refresh_token \   --response-type code \   --scope openid \   --redirect-uri <http://127.0.0.1:3001/auth/callback/> \   --endpoint <http://127.0.0.1:4445>
This is confirmed in the documentation, which states that the administrative port (default
4445
) is used for client management and should not be exposed to the public internet [Ory Hydra OAuth2 Server Quickstart](https://www.ory.sh/docs/hydra/self-hosted/quickstart) [Exposing administrative and public API endpoints](https://www.ory.sh/docs/hydra/self-hosted/production#exposing-administrative-and-public-api-endpoints).
w
@chilly-king-10285 redirectToHydraAuth({ hydraAdminUrl: "http://127.0.0.1:4443", clientId: "fb3b9eda-566a-450c-b205-16fce888a2aa", redirectUri: "http://127.0.0.1:3001/auth/callback", state: "random-state-value", scope: "openid", }) This definitely shouldn't point to hydra admin, idk why you madfe the. function say that. If you can tell me which doc says that I'll keep it, but shouldn't client secret be here and what should state be?
c
A message from kapa.ai
You are absolutely correct: - The OAuth2 authorization endpoint for user redirection should be the public endpoint (default: `http://127.0.0.1:4444`), not the admin endpoint. - The client secret is not included in the authorization URL or the redirect; it is only used server-to-server when exchanging the authorization code for tokens. - The state parameter should be a cryptographically random string generated by your app for each flow, used to prevent CSRF attacks. It is not a secret, but it should be unpredictable and unique per request. ### Documentation Evidence - The [Ory documentation](https://www.ory.sh/docs/oauth2-oidc/authorization-code-flow#step-1-get-the-users-permission) states that the user should be redirected to the public
/oauth2/auth
endpoint, not the admin endpoint. - The
client_secret
is not included in the redirect; it is only used in the token exchange step ([see example](https://www.ory.sh/docs/oauth2-oidc/authorization-code-flow#web-server-app-and-confidential-client)). - The
state
parameter is described as a random value generated by the requesting application to prevent CSRF attacks ([see docs](https://www.ory.sh/docs/oauth2-oidc/authorization-code-flow#step-1-get-the-users-permission)). ### Correct Usage Example
Copy code
js redirectToHydraAuth({   hydraPublicUrl: "<http://127.0.0.1:4444>", // <-- public endpoint!   clientId: "fb3b9eda-566a-450c-b205-16fce888a2aa",   redirectUri: "<http://127.0.0.1:3001/auth/callback>",   state: "randomly-generated-cryptographic-string", // e.g. from crypto.randomUUID()   scope: "openid", })
Summary: - Use the public endpoint (
4444
) for user redirection. - Do not include the client secret in the redirect. - Generate a secure, random
state
for each flow. Thank you for catching this! The knowledge sources confirm your understanding is correct.
w
@chilly-king-10285 fb3b9eda-566a-450c-b205-16fce888a2aa authorization_code, refresh_token code openid http://127.0.0.1:3001/auth/callback/ time=2025-09-18T200319Z 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 connection:keep-alive cookie:[csrf_token_be481debe9e1ebcf14d99f6f631d9a520ca6701ba0f3e4398508af30ebb1f509=nTCHphYwI4feXAQyml+yJq2ptMykfI1bkb9R7GguiTA=; ory_kratos_session=MTc1ODIxNjY3N3xPWHNDTFBLb2x0SVZzeEJBdGstTjVHN0V6ZUhxUGpaaWZnN2ZWeHFWdEs3VEpaZlZZZ2w1NldOdUp2Q0pERlFhNGhyNGxQc2NrZEJIY2k4TEFHT0lFOUxjYTNPNDQwN2o5clpCRVJBNzMxYjZZUWJRa2R0Q2xnUUx2YUxvYl9ub05TUjVMbjAzREdTNTVsVmt6dnN1eW9CaFNSTlBrZDVnWVBpQTFhSjdiTWo4T2pvZm0zNjh4NmRDaXVWR19jQjg3Rk0yaktFZGtkOTN1UjZkcGt3YzE2RjdNQjNsRnVOd1JQSDZWR0lod0kwNEJNZE5XRk85S2FOM2c5UDd6VXhoNFZvMHpjYzFMaXRKNjhVanVndWZ8qADXSbtBuIqu9g2caEZVWIODHNoXdlAdDvTiI71E23E=] referer:http://127.0.0.1:3001/ sec-ch-ua:"Chromium";v="140", "Not=A?Brand";v="24", "Google Chrome";v="140" 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/140.0.0.0 Safari/537.36] host127.0.0.14444 method:GET path:/oauth2/auth query:response_type=code&client_id=fb3b9eda-566a-450c-b205-16fce888a2aa&redirect_uri=http%3A%2F%2F127.0.0.1%3A3001%2Fauth%2Fcallback&scope=openid&state=88613519-949f-4528-a4ae-2330f1cc42d9 remote192.168.107.121595 scheme:http] time=2025-09-18T200319Z level=error msg=An error occurred audience=application error=map[debug: message:invalid_request reason:The 'redirect_uri' parameter does not match any of the OAuth 2.0 Client's pre-registered redirect urls. stack_trace: github.com/ory/x/errorsx.WithStack /go/pkg/mod/github.com/ory/x@v0.0.612-0.20240130132700-6275e3f1ad0d/errorsx/errors.go:41 github.com/ory/fosite.MatchRedirectURIWithClientRedirectURIs /go/pkg/mod/github.com/ory/fosite@v0.44.1-0.20231218095112-ac9ae4bd99d7/authorize_helper.go:81 github.com/ory/fosite.(*Fosite).validateAuthorizeRedirectURI /go/pkg/mod/github.com/ory/fosite@v0.44.1-0.20231218095112-ac9ae4bd99d7/authorize_request_handler.go:169 github.com/ory/fosite.(*Fosite).newAuthorizeRequest /go/pkg/mod/github.com/ory/fosite@v0.44.1-0.20231218095112-ac9ae4bd99d7/authorize_request_handler.go:366 github.com/ory/fosite.(*Fosite).NewAuthorizeRequest /go/pkg/mod/github.com/ory/fosite@v0.44.1-0.20231218095112-ac9ae4bd99d7/authorize_request_handler.go:315 github.com/ory/hydra/v2/oauth2.(*Handler).oAuth2Authorize /project/oauth2/handler.go:1045 github.com/ory/x/httprouterx.(*RouterPublic).GET.NoCacheHandle.func1 /go/pkg/mod/github.com/ory/x@v0.0.612-0.20240130132700-6275e3f1ad0d/httprouterx/nocache.go:21 github.com/ory/x/httprouterx.(*RouterPublic).Handle.NoCacheHandle.func1 /go/pkg/mod/github.com/ory/x@v0.0.612-0.20240130132700-6275e3f1ad0d/httprouterx/nocache.go:21 github.com/julienschmidt/httprouter.(*Router).ServeHTTP /go/pkg/mod/github.com/julienschmidt/httprouter@v1.3.0/router.go:387 github.com/urfave/negroni.(*Negroni).UseHandler.Wrap.func1 /go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:46 github.com/urfave/negroni.HandlerFunc.ServeHTTP /go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:29 github.com/urfave/negroni.middleware.ServeHTTP /go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38 github.com/ory/hydra/v2/cmd/server.EnhanceMiddleware.func1 /project/cmd/server/handler.go:63 github.com/urfave/negroni.HandlerFunc.ServeHTTP /go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:29 github.com/urfave/negroni.middleware.ServeHTTP /go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38 net/http.HandlerFunc.ServeHTTP /usr/local/go/src/net/http/server.go:2136 github.com/ory/hydra/v2/cmd/server.EnhanceMiddleware.RejectInsecureRequests.func2 /project/x/tls_termination.go:65 github.com/urfave/negroni.HandlerFunc.ServeHTTP /go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:29 github.com/urfave/negroni.middleware.ServeHTTP /go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38 github.com/ory/x/metricsx.(*Service).ServeHTTP /go/pkg/mod/github.com/ory/x@v0.0.612-0.20240130132700-6275e3f1ad0d/metricsx/middleware.go:272 github.com/urfave/negroni.middleware.ServeHTTP /go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:38 net/http.HandlerFunc.ServeHTTP /usr/local/go/src/net/http/server.go:2136 github.com/prometheus/client_golang/prometheus/promhttp.InstrumentHandlerResponseSize.func1 /go/pkg/mod/github.com/prometheus/client_golang@v1.16.0/prometheus/promhttp/instrument_server.go:296 net/http.HandlerFunc.ServeHTTP /usr/local/go/src/net/http/server.go:2136 github.com/prometheus/client_golang/prometheus/promhttp.InstrumentHandlerCounter.func1 /go/pkg/mod/github.com/prometheus/client_golang@v1.16.0/prometheus/promhttp/instrument_server.go:147 net/http.HandlerFunc.ServeHTTP /usr/local/go/src/net/http/server.go:2136 github.com/prometheus/client_golang/prometheus/promhttp.InstrumentHandlerDuration.func1 /go/pkg/mod/github.com/prometheus/client_golang@v1.16.0/prometheus/promhttp/instrument_server.go:97 net/http.HandlerFunc.ServeHTTP /usr/local/go/src/net/http/server.go:2136 github.com/prometheus/client_golang/prometheus/promhttp.InstrumentHandlerDuration.func2 /go/pkg/mod/github.com/prometheus/client_golang@v1.16.0/prometheus/promhttp/instrument_server.go:109 net/http.HandlerFunc.ServeHTTP /usr/local/go/src/net/http/server.go:2136 github.com/prometheus/client_golang/prometheus/promhttp.InstrumentHandlerRequestSize.func1 /go/pkg/mod/github.com/prometheus/client_golang@v1.16.0/prometheus/promhttp/instrument_server.go:243 net/http.HandlerFunc.ServeHTTP /usr/local/go/src/net/http/server.go:2136 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 connection:keep-alive cookie:[csrf_token_be481debe9e1ebcf14d99f6f631d9a520ca6701ba0f3e4398508af30ebb1f509=nTCHphYwI4feXAQyml+yJq2ptMykfI1bkb9R7GguiTA=; ory_kratos_session=MTc1ODIxNjY3N3xPWHNDTFBLb2x0SVZzeEJBdGstTjVHN0V6ZUhxUGpaaWZnN2ZWeHFWdEs3VEpaZlZZZ2w1NldOdUp2Q0pERlFhNGhyNGxQc2NrZEJIY2k4TEFHT0lFOUxjYTNPNDQwN2o5clpCRVJBNzMxYjZZUWJRa2R0Q2xnUUx2YUxvYl9ub05TUjVMbjAzREdTNTVsVmt6dnN1eW9CaFNSTlBrZDVnWVBpQTFhSjdiTWo4T2pvZm0zNjh4NmRDaXVWR19jQjg3Rk0yaktFZGtkOTN1UjZkcGt3YzE2RjdNQjNsRnVOd1JQSDZWR0lod0kwNEJNZE5XRk85S2FOM2c5UDd6VXhoNFZvMHpjYzFMaXRKNjhVanVndWZ8qADXSbtBuIqu9g2caEZVWIODHNoXdlAdDvTiI71E23E=] referer:http://127.0.0.1:3001/ sec-ch-ua:"Chromium";v="140", "Not=A?Brand";v="24", "Google Chrome";v="140" 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/140.0.0.0 Safari/537.36] host127.0.0.14444 method:GET path:/oauth2/auth query:response_type=code&client_id=fb3b9eda-566a-450c-b205-16fce888a2aa&redirect_uri=http%3A%2F%2F127.0.0.1%3A3001%2Fauth%2Fcallback&scope=openid&state=88613519-949f-4528-a4ae-2330f1cc42d9 remote192.168.107.121595 scheme:http] service_name=Ory Hydra service_version=v2.2.0
c
A message from kapa.ai
The error message you are seeing:
Copy code
The 'redirect_uri' parameter does not match any of the OAuth 2.0 Client's pre-registered redirect urls.
means that the
redirect_uri
you are sending in your authorization request (`http://127.0.0.1:3001/auth/callback`) does not exactly match any of the redirect URIs registered for your OAuth2 client in Hydra. Key points from the knowledge sources: - The
redirect_uri
in your authorization request must match exactly (character for character, including trailing slashes) one of the URIs registered for your client in Hydra. Even a small difference (such as a missing or extra trailing slash) will cause this error [Resolve redirect URL mismatch errors](https://www.ory.sh/docs/getting-started/integrate-auth/auth-js#resolve-redirect-url-mismatch-errors) [Hydra debug: redirect URL isn't whitelisted](https://www.ory.sh/docs/hydra/debug#refresh-token-flow-fails). - You can check your registered client’s redirect URIs using the Hydra CLI:
Copy code
bash   hydra get oauth2-client <client-id>
and look for the
redirect_uris
field. - If you registered the URI as
<http://127.0.0.1:3001/auth/callback/>
(with a trailing slash), but your request uses
<http://127.0.0.1:3001/auth/callback>
(without the slash), or vice versa, it will not match. What you should do: 1. Check the exact value of the registered redirect URI for your client in Hydra. 2. Make sure your authorization request uses the exact same URI (including or excluding the trailing slash as registered). Example: - If registered as
<http://127.0.0.1:3001/auth/callback/>
, your request must use
<http://127.0.0.1:3001/auth/callback/>
. - If registered as
<http://127.0.0.1:3001/auth/callback>
, your request must use
<http://127.0.0.1:3001/auth/callback>
. Summary: This error is caused by a mismatch between the
redirect_uri
in your request and the one(s) registered for your client. Make them match exactly to resolve the issue [Resolve redirect URL mismatch errors](https://www.ory.sh/docs/getting-started/integrate-auth/auth-js#resolve-redirect-url-mismatch-errors) [Hydra debug: redirect URL isn't whitelisted](https://www.ory.sh/docs/hydra/debug#refresh-token-flow-fails).
w
@chilly-king-10285
Copy code
GET /auth/login?login_challenge=ARS6u_tkh0ntvTfMBpQ4PuRkcxKg6aWrVQTunNoneNGrZ_puFQvmQ-BlnxkAu1x4M6XBTgnOtODed1MI9sO_tANNHj6lNGwAKLKmajjw_Y_AZcO8xHed1ZkboLyWg1o-O1UMiYv9maG-lDJP2ZRwP7CMXjUaCFogxO2Obhh7Naqrqr7BElWOJ6aFyx4FfsrRMHN9aAzIUbHesgDXOjKbdjIItZ-7jIyRCbxrYbDPk14KH4GcII5pNsvFOMl2c5I7JgOLevIVQPO6Jln0O5Dq_t-zpvYwUQImnUkNAS0DRnI1UXeQrU-J99QS8O8g_gopAo62A2fRW3zkkB2uChmuZt_JsIkjj9D_gAmfFeGLvAlLCc1zWzzm-T3UImR1bNQzwOdtuSQR6Dptr3H4qEDMTFdAqh5m3ENk3aktnP9GeK2NTzrhbsWWyQhOAlCPd4ySrwTZPpc9glt2IHPCJwav1wTdqXP_hFfGh_ZtVdgOMJddvOSLzomRxn6r3FSti7Yx8X5RY2YIti1kvkpkCAjz1Nav0rnzRbjJkQr_arJNyVbi2a1X7lfH8qD8o-gA3PTL9BbLLRjCp7KeOSqim24rwp69Vp8EBy6_XXp21GHb8pEZl-R5jvrEvFmJBxlb6QSohwaLEsyZsw7WIj81DnSy5DE0LsSbWVuU1cufg4DzRuQHWrxxFy_whSq6G5zLekb4fhdpW2Q_wT2Q56R1FDjF12uAparG9qhu79odIzCVHa-yiKgNK_1rPWca_RxpkRCdgZigIBPDBk8ZoMZbYWUH12QdHuRiuO046YKbETKiU8xA8gEiN3ib0cel24J56Q5kbn7bHYTf6yCy1O56ek1dZA-1-rSw53AW0Id0Zd2HvHpfiqz8ZgcPtgLT4LMmhTsj7v4LuH3x8VwKgbwsWsCLKSfboGc3dK9nu7gklV2T0yYT3DF-7yuZFv0TsZY090bkGBQqPs3hKzgAqwSje9zqAofMhIWCM7iKpj_3_Gx_VIHQuZvtdslj0aDj1PIkECwoHN-0AxdxwGrskSNTZX4DH2m9NBTI_8A9scsk1hpRnJttRX0OycJoScM7XTfSBzBbeTcvPKZiE5GxJjJlcLhykTmkb8r_ROuGXBpFqDpoOrJ7g3PGoL97DafPDuHfK1LCKNlU7onkXUJ-qAmkh-396NLEv72-WmjKpsnAxBwPCI_BARF-EnrJBRSNjBIWNcKfCO7E-XTN-YyhAXE6K6gkP7rrN9RGvbsiwBV-YTqyQmywdW1tPTdUPTTB8v9awap8tjX2fBS-c06GoLHVGF_qbx2qYwEyc_ATSraV1wQ2zH2XyTnyEOxBnMgM3ZIoF9tsm7aKXK99judspkLA_wiQCBxTRArxeQRFmYKnMQGY9y-SavyaQGrIDf5qLdiCg_cU8DSMZaypR38aqB_6BApMGr4HEB7Zbui-c25e 200 in 842ms
This is the get request on the sso app, it did not redirect the user back to the redirectUri because it found a exisiting session, help me fix this:
Copy code
"use client";

import React, { useState, FormEvent, useEffect } from "react";
import { Button } from "@workspace/ui/components/button";
import { Input } from "@workspace/ui/components/input";
import {
	Card,
	CardContent,
	CardDescription,
	CardHeader,
	CardTitle,
} from "@workspace/ui/components/card";
import { Alert, AlertDescription } from "@workspace/ui/components/alert";
import { Label } from "@workspace/ui/components/label";
import { Loader2, Lock } from "lucide-react";
import {
	LoginFlow,
	UiNode,
	UiNodeAttributes,
	UiNodeInputAttributes,
	UpdateLoginFlowBody,
} from "@ory/client";
import { useSearchParams } from "next/navigation";
import { kratos } from "@/lib/ory";

type KratosErrorResponse = {
	response?: {
		status?: number;
		data?: {
			ui?: {
				messages?: Array<{ id?: number; text?: string; type?: string }>;
				nodes?: UiNode[];
			};
			error?: { message?: string };
		};
	};
	message?: string;
};

// Safe runtime check without a type predicate
function isInputNode(attrs: UiNodeAttributes): boolean {
	const t = (attrs as { node_type?: unknown }).node_type;
	// Accept both literal "input" and enum-backed values whose string value is "input"
	return t === "input" || String(t).toLowerCase() === "input";
}

function getNodeMessage(flowObj: LoginFlow | undefined, name: string) {
	const nodes = flowObj?.ui?.nodes ?? [];
	for (const n of nodes) {
		const attrs = n.attributes;
		if (isInputNode(attrs)) {
			const inputAttrs = attrs as UiNodeInputAttributes;
			if (inputAttrs.name === name) {
				const msgs = n.messages ?? [];
				if (msgs.length > 0 && msgs[0]?.text) {
					return msgs[0].text;
				}
			}
		}
	}
	return undefined;
}

function getHiddenCsrf(flowObj: LoginFlow | undefined) {
	const nodes = flowObj?.ui?.nodes ?? [];
	for (const n of nodes) {
		const attrs = n.attributes;
		if (isInputNode(attrs)) {
			const inputAttrs = attrs as UiNodeInputAttributes;
			if (inputAttrs.name === "csrf_token") {
				const value =
					typeof inputAttrs.value === "string"
						? inputAttrs.value
						: inputAttrs.value != null
							? String(inputAttrs.value)
							: "";
				return { name: inputAttrs.name, value };
			}
		}
	}
	return undefined;
}

export default function LoginPage() {
	const [loading, setLoading] = useState<boolean>(false);
	const [error, setError] = useState<string>("");

	const [flow, setFlow] = useState<LoginFlow>();
	const searchParams = useSearchParams();

	useEffect(() => {
		kratos
			.toSession()
			.then(({ data }) => {
				console.log("User is already logged in:", data);
				window.location.href = "/";
			})
			.catch(() => {
				// Not logged in, proceed to initialize or fetch the login flow
			});

		const id = searchParams.get("flow");
		if (!id) {
			kratos
				.createBrowserLoginFlow()
				.then(({ data }) => {
					setFlow(data);
					const url = new URL(window.location.href);
					url.searchParams.set("flow", data.id);
					window.history.replaceState({}, "", url.toString());
				})
				.catch((e: unknown) => {
					const err = e as Error;
					setError(err.message);
				});
		} else {
			kratos
				.getLoginFlow({ id })
				.then(({ data }) => {
					setFlow(data);
				})
				.catch((e: unknown) => {
					const err = e as Error;
					setError(err.message);
				});
		}
	}, [searchParams]);

	const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
		e.preventDefault();
		setLoading(true);
		setError("");

		try {
			if (!flow) {
				setError("Login flow is not initialized.");
				setLoading(false);
				return;
			}

			const formData = new FormData(e.currentTarget);
			const email = String(formData.get("email") ?? "");
			const password = String(formData.get("password") ?? "");
			const csrfToken = String(formData.get("csrf_token") ?? "");

			const body: UpdateLoginFlowBody = {
				method: "password",
				identifier: email,
				password,
				csrf_token: csrfToken,
			};

			const { data } = await kratos.updateLoginFlow({
				flow: flow.id,
				updateLoginFlowBody: body,
			});

			console.log("Login successful:", data);
			// Optional redirect handling:
			// const redirectTo = (data as { redirect_browser_to?: string }).redirect_browser_to;
			// if (redirectTo) window.location.href = redirectTo;
		} catch (e: unknown) {
			const err = e as KratosErrorResponse;

			let apiError: string | undefined =
				err.response?.data?.ui?.messages?.[0]?.text;

			if (!apiError) {
				const nodes = err.response?.data?.ui?.nodes ?? [];
				for (const n of nodes) {
					const msg = n.messages?.[0]?.text;
					if (msg) {
						apiError = msg;
						break;
					}
				}
			}

			if (!apiError) {
				apiError =
					err.response?.data?.error?.message ||
					err.message ||
					"An unexpected error occurred while signing in.";
			}

			const status = err.response?.status ?? 0;
			if (status === 410 || status === 400 || status === 403) {
				try {
					const { data: newFlow } = await kratos.createBrowserLoginFlow();
					setFlow(newFlow);
					const url = new URL(window.location.href);
					url.searchParams.set("flow", newFlow.id);
					window.history.replaceState({}, "", url.toString());
					setError(apiError);
				} catch (createErr: unknown) {
					const ce = createErr as KratosErrorResponse;
					setError(
						ce.response?.data?.error?.message ||
							ce.message ||
							"Failed to refresh the login flow."
					);
				} finally {
					setLoading(false);
				}
				return;
			}

			setError(apiError);
		} finally {
			setLoading(false);
		}
	};

	const identifierMessage = getNodeMessage(flow, "identifier");
	const passwordMessage = getNodeMessage(flow, "password");
	const flowMessage = flow?.ui?.messages?.[0]?.text;
	const csrfNode = getHiddenCsrf(flow);

	return (
		<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-gray-50 to-gray-100 p-4">
			<Card className="w-full max-w-md">
				<CardHeader className="space-y-1 text-center">
					<div className="mx-auto h-12 w-12 flex items-center justify-center rounded-full bg-primary/10 mb-4">
						<Lock className="h-6 w-6 text-primary" />
					</div>
					<CardTitle className="text-2xl font-bold">Welcome back</CardTitle>
					<CardDescription>Sign in to your account to continue</CardDescription>
				</CardHeader>

				<CardContent>
					{(error || flowMessage || identifierMessage || passwordMessage) && (
						<Alert variant="destructive" className="mb-6">
							<AlertDescription>
								{error || flowMessage || identifierMessage || passwordMessage}
							</AlertDescription>
						</Alert>
					)}

					<form onSubmit={handleSubmit} className="space-y-4">
						{csrfNode && (
							<input
								type="hidden"
								name={csrfNode.name}
								value={csrfNode.value}
								readOnly
							/>
						)}

						<div className="space-y-2">
							<Label htmlFor="email">Email</Label>
							<Input
								name="email"
								type="email"
								placeholder="Enter your email"
								required
								disabled={loading}
								autoComplete="username"
							/>
							{identifierMessage && (
								<p className="text-sm text-red-600">{identifierMessage}</p>
							)}
						</div>

						<div className="space-y-2">
							<Label htmlFor="password">Password</Label>
							<Input
								name="password"
								type="password"
								placeholder="Enter your password"
								required
								disabled={loading}
								autoComplete="current-password"
							/>
							{passwordMessage && (
								<p className="text-sm text-red-600">{passwordMessage}</p>
							)}
						</div>

						<Button type="submit" className="w-full" disabled={loading}>
							{loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
							Sign in
						</Button>
					</form>

					<div className="mt-6 text-center space-y-2">
						<p className="text-sm text-muted-foreground">
							Don't have an account?{" "}
							<a
								href="/register"
								className="font-medium text-primary hover:underline"
							>
								Sign up
							</a>
						</p>
						<p className="text-xs text-muted-foreground">
							<a href="/forgot-password" className="hover:underline">
								Forgot your password?
							</a>
						</p>
					</div>
				</CardContent>
			</Card>
		</div>
	);
}
c
A message from kapa.ai
The behavior you are seeing—where the user is not redirected to the
redirect_uri
after visiting the login page with a `login_challenge`—is expected if Kratos detects an existing session and your login UI does not handle the OAuth2 login challenge flow correctly. What’s happening: - When Hydra detects an existing session (the user is already authenticated in Kratos), it expects your login UI to accept the login challenge and redirect the user back to Hydra, which will then redirect to the
redirect_uri
as part of the OAuth2 flow. - If your UI simply redirects to
/
or another default page when a session exists, the OAuth2 flow is interrupted and the user never returns to the client app. How to fix: You need to detect the presence of a
login_challenge
in the query parameters and, if the user is already authenticated, call Hydra’s admin API to accept the login challenge and then redirect the user to the URL provided by Hydra. This is the documented approach for custom login UIs in the Ory ecosystem [Custom login and consent flow](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow#login-endpoint-code-example). What to do in your code: 1. Check for
login_challenge
in the query parameters.
2. If the user is already authenticated, call Hydra’s
acceptOAuth2LoginRequest
endpoint with the challenge and the user’s subject.
3. Redirect the user to the
redirect_to
URL returned by Hydra.
Example (pseudo-code, see the docs for full details):
Copy code
js import { OAuth2Api, Configuration } from "@ory/client"  const hydra = new OAuth2Api(   new Configuration({     basePath: "<http://localhost:4445>", // Hydra admin endpoint   }) )  async function handleLoginChallenge(login_challenge, subject) {   // Accept the login request   const { data } = await hydra.acceptOAuth2LoginRequest({     loginChallenge: login_challenge,     acceptOAuth2LoginRequest: {       subject,       remember: true,       remember_for: 3600,     },   })   // Redirect the user to the URL provided by Hydra   window.location.href = data.redirect_to }
In your
useEffect
, update this part:
Copy code
js kratos   .toSession()   .then(async ({ data }) => {     const login_challenge = searchParams.get("login_challenge")     if (login_challenge) {       // Accept the login challenge and redirect       await handleLoginChallenge(login_challenge, data.identity.id)     } else {       // No challenge, redirect to home       window.location.href = "/"     }   })   .catch(() => {     // Not logged in, proceed to initialize or fetch the login flow   })
Summary: - If a
login_challenge
is present and the user is already authenticated, you must accept the login challenge with Hydra and redirect the user to the URL returned by Hydra. - This ensures the OAuth2 flow completes and the user is returned to the original client app, not just your SSO app’s home page. This approach is directly described in the Ory documentation for custom login endpoints [Custom login and consent flow](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow#login-endpoint-code-example).
w
hydra update oauth2-client fb3b9eda-566a-450c-b205-16fce888a2aa --endpoint "http://127.0.0.1:4445" --grant-type authorization_token --grant-type refre sh_token --response-type code --scope openid --redirect-uri http://127.0.0.1:3001/auth/callback/ --skip-consent --skip-logout-consent why does this still take me to the consent page? @chilly-king-10285
c
A message from kapa.ai
The knowledge sources confirm that setting
--skip-consent
on an OAuth2 client should skip the consent screen for that client. However, there are a few important caveats and scenarios where you might still see the consent screen: 1. Skip Consent Only Works with Custom Consent UIs or Properly Configured Ory Network Projects: The Ory documentation and GitHub issues clarify that skipping consent is only guaranteed if your project or consent UI is configured to respect the
skip_consent
flag. If you are using the default Ory Network UI, there have been reports that it does not always skip the consent screen, and you may need to implement a custom consent UI that checks the
skip_consent
flag and auto-accepts the consent request [Skip consent and logout consent](https://www.ory.sh/docs/oauth2-oidc/skip-consent) [Skip consent flow in default Ory Network UI](https://github.com/ory/network/issues/205). 2. The Consent UI Logic Must Check the Flag: The consent skipping is managed by the
challenge.client.skip_consent
property. Your consent UI must check this property and, if true, immediately accept the consent request without showing the UI. If your consent UI does not do this, the user will still see the consent screen [Custom login and consent flow](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow#consent). 3. Ory Network Default UI Limitation: There is a known limitation in the default Ory Network UI where the skip consent flag is not always respected, and the consent screen may still appear. The recommended workaround is to implement a custom consent UI that auto-accepts consent when
skip_consent
is true [Skip consent flow in default Ory Network UI](https://github.com/ory/network/issues/205). 4. OpenID Connect Certification: The documentation also notes that skipping the consent screen will break OpenID Connect Certification, so it is not recommended for third-party clients [Custom login and consent flow](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow#previous-consent). Summary: Even if you set
--skip-consent
, you may still see the consent screen if your consent UI does not check and respect the
skip_consent
flag. If you are using the default Ory Network UI, this is a known limitation, and you may need to implement a custom consent UI to achieve the desired behavior. If you need to ensure consent is skipped, follow the guidance to implement a custom consent UI that checks
challenge.client.skip_consent
and auto-accepts the request [Custom login and consent flow](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow#consent) [Skip consent flow in default Ory Network UI](https://github.com/ory/network/issues/205).
w
@chilly-king-10285 What is the identity api for if I want to use
Copy code
async function createOAuth2ConsentRequestSession(
	grantScopes: string[],
	consentRequest: OAuth2ConsentRequest,
	identityApi: IdentityApi
): Promise<AcceptOAuth2ConsentRequestSession> {
	// The session allows us to set session data for id and access tokens

	const id_token: { [key: string]: any } = {};

	if (consentRequest.subject && grantScopes.length > 0) {
		const identity = (
			await identityApi.getIdentity({ id: consentRequest.subject })
		).data;

		if (grantScopes.indexOf("email") > -1) {
			// Client may check email of user
			id_token.email = identity.traits["email"] || "";
		}
		if (grantScopes.indexOf("phone") > -1) {
			// Client may check phone number of user
			id_token.phone = identity.traits["phone"] || "";
		}
	}

	return {
		// This data will be available when introspecting the token. Try to avoid sensitive information here,
		// unless you limit who can introspect tokens.
		access_token: {
			// foo: 'bar'
		},

		// This data will be available in the ID token.
		id_token,
	};
}
c
A message from kapa.ai
The
IdentityApi
in your function is used to fetch identity information (such as traits like email or phone) from Ory Kratos, given the subject (user ID) from the consent request. This allows you to dynamically populate claims in the ID token or access token based on the user's stored identity data and the requested OAuth2 scopes. In the example you provided,
identityApi.getIdentity({ id: consentRequest.subject })
retrieves the identity object, which contains the user's traits. You then use these traits to set claims in the ID token, such as
email
or
phone
, if those scopes were requested. This approach is shown in the Ory documentation for custom consent UIs, where the consent service uses the Identity API to fetch user data and map it to token claims according to the requested scopes [Consent endpoint code example](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow#consent-endpoint-code-example). In summary: The Identity API is used to retrieve user information from Ory Kratos so you can include relevant claims in the tokens issued by Hydra, based on the scopes requested by the OAuth2 client.
w
@chilly-king-10285 How do I get the kratos identity api?
c
A message from kapa.ai
To get the Kratos Identity API, you need to use the Ory SDK for your language of choice. For JavaScript/TypeScript, you can use the
@ory/client
library and instantiate the
IdentityApi
class. Here’s how you can do it:
Copy code
js import { Configuration, IdentityApi } from "@ory/client"  const identityApi = new IdentityApi(   new Configuration({     basePath: "https://<your-kratos-public-endpoint>", // e.g., <https://project-slug.projects.oryapis.com>     accessToken: "<ORY_API_KEY>", // if required   }) )
You can then use
identityApi.getIdentity({ id: "<identity_id>" })
to fetch an identity by its ID, as shown in the consent endpoint code example from the knowledge sources [Consent endpoint code example](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow#consent-endpoint-code-example). > Note: The API requests need to be authorized, typically with an Ory API key or a valid session, depending on your deployment and configuration [Ory Identity Management Go SDK](https://www.ory.sh/docs/kratos/sdk/go#identity-api). If you are using another language, the process is similar: instantiate the Identity API client from the relevant Ory SDK and configure it with your Kratos endpoint and credentials.
w
Copy code
serve:
  public:
    base_url: <http://127.0.0.1:4433>
    cors:
      enabled: true
      allowed_origins:
        - <http://127.0.0.1:3000>
        - <http://localhost:3000>
      allowed_methods:
        - GET
        - POST
        - PUT
        - PATCH
        - DELETE
      allowed_headers:
        - Authorization
        - Content-Type
        - X-Session-Token
        - X-XSRF-TOKEN
        - X-CSRF-TOKEN
      allow_credentials: true
      exposed_headers:
        - Content-Type
  admin:
    base_url: <http://127.0.0.1:4434>
    cors:
      enabled: true
      allowed_origins:
        - <http://127.0.0.1:3000>
        - <http://localhost:3000>
      allowed_methods:
        - GET
        - POST
        - PUT
        - PATCH
        - DELETE
      allowed_headers:
        - Authorization
        - Content-Type
        - X-Session-Token
        - X-XSRF-TOKEN
        - X-CSRF-TOKEN
      allow_credentials: true
      exposed_headers:
        - Content-Type
Why is the cors not working for the kratos admin? @chilly-king-10285
Copy code
The configuration contains values or keys which are invalid:
serve.admin: map[base_url:<http://127.0.0.1:4434> cors:map[allow_credentials:true allowed_headers:[Authorization Content-Type X-Session-Token X-XSRF-TOKEN X-CSRF-TOKEN] allowed_methods:[GET POST PUT PATCH DELETE] allowed_origins:[<http://127.0.0.1:3000> <http://localhost:3000>] enabled:true exposed_headers:[Content-Type]] host:0.0.0.0 port:4434 request_log:map[disable_for_health:false] socket:map[group: mode:493 owner:]]
             ^-- additionalProperties "cors" not allowed
c
A message from kapa.ai
The error message you are seeing:
Copy code
The configuration contains values or keys which are invalid: serve.admin: ... ^-- additionalProperties "cors" not allowed
indicates that the
cors
configuration is not allowed under the
serve.admin
section in Ory Kratos. According to the [Kratos CORS documentation](https://www.ory.sh/docs/kratos/guides/setting-up-cors), CORS settings are only supported for the
serve.public
section, not for
serve.admin
. Here is the relevant excerpt: > Ory Kratos' Public API supports CORS out of the box. A reference for this configuration can be found in the [Configuration Reference](https://www.ory.sh/docs/kratos/reference/configuration). > > For CORS to work properly, we encourage to set the following values: > >
Copy code
> serve:   >   public:   >     cors:   >       enabled: true   >       allowed_origins:   >         - <https://example.org>   >         - https://*.<http://example.org|example.org>   >       ... >
There is no mention of CORS support for the
admin
endpoint in the Kratos documentation. This is why you are getting the configuration error: the
admin
section does not accept a
cors
property. Summary: CORS is only supported on the
serve.public
endpoint in Ory Kratos, not on
serve.admin
. Remove the
cors
block from your
serve.admin
configuration to resolve the error [Cross-origin resource sharing (CORS) settings](https://www.ory.sh/docs/kratos/guides/setting-up-cors).
w
Copy code
Access to XMLHttpRequest at '<http://127.0.0.1:4434/admin/identities/53f85911-2c7c-437b-b59c-e58c66c86b22>' from origin '<http://127.0.0.1:3000>' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
@chilly-king-10285
c
A message from kapa.ai
The error you are seeing:
Copy code
Access to XMLHttpRequest at '<http://127.0.0.1:4434/admin/identities/53f85911-2c7c-437b-b59c-e58c66c86b22>' from origin '<http://127.0.0.1:3000>' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
is expected behavior in Ory Kratos. According to the official documentation, CORS is only supported on the
serve.public
endpoint, not on the
serve.admin
endpoint
. The admin API is intended for server-to-server communication and is not designed to be accessed directly from browsers or frontend applications. This is why you cannot enable CORS for the admin endpoint, and why your browser request is blocked [Cross-origin resource sharing (CORS) settings](https://www.ory.sh/docs/kratos/guides/setting-up-cors). To resolve this: - Make admin API calls from your backend server, not directly from the browser or frontend code. - Only use the public API endpoints for browser-based requests. This is a security measure to prevent exposing privileged admin operations to the browser.
w
@chilly-king-10285 How do you expect me to test that then? 127.0.0.1 is an internal ip???
Copy code
import {
	AcceptOAuth2ConsentRequestSession,
	Configuration,
	FrontendApi,
	IdentityApi,
	OAuth2Api,
	OAuth2ConsentRequest,
} from "@ory/client";

const kratos = new FrontendApi(
	new Configuration({
		basePath: process.env.NEXT_PUBLIC_ORY_SDK_URL,
		baseOptions: {
			withCredentials: true,
		},
	})
);

const identityApi = new IdentityApi(
	new Configuration({
		basePath: "<http://127.0.0.1:4434>",
		baseOptions: {
			withCredentials: true,
		},
	})
);

const hydra = new OAuth2Api(
	new Configuration({
		basePath: "<http://127.0.0.1:4445>",
	})
);

export { kratos, hydra };

async function createOAuth2ConsentRequestSession(
	grantScopes: string[],
	consentRequest: OAuth2ConsentRequest
): Promise<AcceptOAuth2ConsentRequestSession> {
	// The session allows us to set session data for id and access tokens

	const id_token: { [key: string]: any } = {};

	if (consentRequest.subject && grantScopes.length > 0) {
		const identity = (
			await identityApi.getIdentity({ id: consentRequest.subject })
		).data;

		if (grantScopes.indexOf("email") > -1) {
			// Client may check email of user
			id_token.email = identity.traits["email"] || "";
		}
		if (grantScopes.indexOf("phone") > -1) {
			// Client may check phone number of user
			id_token.phone = identity.traits["phone"] || "";
		}
	}

	return {
		// This data will be available when introspecting the token. Try to avoid sensitive information here,
		// unless you limit who can introspect tokens.
		access_token: {
			// foo: 'bar'
		},

		// This data will be available in the ID token.
		id_token,
	};
}

export { createOAuth2ConsentRequestSession };
c
A message from kapa.ai
You are correct that
127.0.0.1
is an internal IP, and the Ory Kratos Admin API is intentionally designed not to support CORS for security reasons. This means you cannot call the admin API directly from a browser or frontend app, even during local development. This is a deliberate security measure to prevent exposing sensitive admin operations to the browser [Cross-origin resource sharing (CORS) settings](https://www.ory.sh/docs/kratos/guides/setting-up-cors). How to test admin API calls locally: - Call the admin API from your backend/server-side code, not from the browser. For local development, you can run your backend on the same machine and have it communicate with Kratos Admin at
<http://127.0.0.1:4434>
. Your frontend should make requests to your backend, which then proxies or performs the admin API calls. - If you need to test from a browser, you must use a backend proxy. There is no supported way to enable CORS for the Kratos Admin API, even for
127.0.0.1
, as per the official documentation. Summary: You cannot and should not call the Kratos Admin API directly from the browser, even on
127.0.0.1
. Always use a backend server to interact with the admin API, both for security and because CORS is not supported on that endpoint [Cross-origin resource sharing (CORS) settings](https://www.ory.sh/docs/kratos/guides/setting-up-cors).
w
@chilly-king-10285 What should the callback page do with the code scope and state in the search params?
c
A message from kapa.ai
On the callback page, after the user is redirected back from the OAuth2 authorization server (Hydra), you will receive the
code
,
scope
, and
state
as query parameters. Here’s what you should do with them: 1. Validate the
state
parameter:
Compare the
state
value in the query params with the one you originally generated and stored (e.g., in local storage or a cookie) before starting the OAuth2 flow. This is a critical step to prevent CSRF attacks [OAuth2 authorization code flow](https://www.ory.sh/docs/oauth2-oidc/authorization-code-flow#step-1-get-the-users-permission). 2. Exchange the
code
for tokens:
Use the
code
to make a POST request to the token endpoint (
/oauth2/token
) of your OAuth2 server (Hydra). This request should include: -
grant_type=authorization_code
-
code
(from the query param) -
redirect_uri
(must match the one used in the initial request) -
client_id
(and
client_secret
if using a confidential client) Example (from the docs):
Copy code
js    const code = "authorization_code_received_from_auth_server"    const tokenUrl = "https://$<http://PROJECT_SLUG.projects.oryapis.com/oauth2/token|PROJECT_SLUG.projects.oryapis.com/oauth2/token>"    const requestBody = `grant_type=authorization_code&client_id=${clientId}&client_secret=${clientSecret}&code=${code}&redirect_uri=<https://myapp.com/callback>`     fetch(tokenUrl, {      method: "POST",      headers: { "Content-Type": "application/x-www-form-urlencoded" },      body: requestBody,    })      .then((response) => response.json())      .then((data) => console.log(data))      .catch((error) => console.error(error))
[Web server app and confidential client](https://www.ory.sh/docs/oauth2-oidc/authorization-code-flow#web-server-app-and-confidential-client) 3. Handle the
scope
parameter:
The
scope
parameter in the callback is informational and reflects the scopes that were granted. You typically do not need to process it further, but you may want to check it if your app needs to know which permissions were granted. Summary: - Validate
state
to prevent CSRF. - Exchange
code
for tokens at the token endpoint. - Optionally, use
scope
to check granted permissions. This flow is described in the Ory documentation for the OAuth2 authorization code flow [OAuth2 authorization code flow](https://www.ory.sh/docs/oauth2-oidc/authorization-code-flow#step-4-exchange-code-for-token).
w
@chilly-king-10285 Okay, so what would be the best way to track the state variable and verify?
c
A message from kapa.ai
The best way to track and verify the
state
variable is to generate a cryptographically random value when you initiate the OAuth2 flow, store it securely on the client (such as in a cookie or local storage), and then compare it with the
state
value returned in the callback to ensure they match. This process is essential to prevent CSRF attacks. The Ory documentation states: > The
state
parameter is a random value that is generated by the requesting application to prevent cross-site request forgery (CSRF) attacks. > — [OAuth2 authorization code flow](https://www.ory.sh/docs/oauth2-oidc/authorization-code-flow#step-1-get-the-users-permission) However, the knowledge sources do not specify a single "best" storage mechanism (e.g., cookie vs. local storage), but the key requirement is that the value must be securely stored and reliably retrievable on the callback page for comparison. If the values do not match, you should reject the flow as potentially malicious.
w
@chilly-king-10285 Write a typesafe function for exchanging the code for tokens
c
A message from kapa.ai
Certainly! Here is a type-safe TypeScript function for exchanging the authorization code for tokens, based directly on the Ory documentation for the OAuth2 authorization code flow. This function uses the
fetch
API to POST to the Hydra token endpoint, as shown in the official examples [Web server app and confidential client](https://www.ory.sh/docs/oauth2-oidc/authorization-code-flow#web-server-app-and-confidential-client):
Copy code
ts type TokenResponse = {   access_token: string   expires_in: number   id_token?: string   refresh_token?: string   scope?: string   token_type: string   [key: string]: any }  type ExchangeCodeForTokenParams = {   tokenUrl: string   clientId: string   clientSecret: string   code: string   redirectUri: string }  export async function exchangeCodeForToken({   tokenUrl,   clientId,   clientSecret,   code,   redirectUri, }: ExchangeCodeForTokenParams): Promise<TokenResponse> {   const body = new URLSearchParams({     grant_type: "authorization_code",     client_id: clientId,     client_secret: clientSecret,     code,     redirect_uri: redirectUri,   })    const response = await fetch(tokenUrl, {     method: "POST",     headers: { "Content-Type": "application/x-www-form-urlencoded" },     body: body.toString(),   })    if (!response.ok) {     throw new Error(`Token exchange failed: ${response.statusText}`)   }    return response.json() }
Usage example:
Copy code
ts const tokens = await exchangeCodeForToken({   tokenUrl: "<http://127.0.0.1:4444/oauth2/token>",   clientId: "your-client-id",   clientSecret: "your-client-secret",   code: "the-code-from-callback",   redirectUri: "<http://127.0.0.1:3001/auth/callback>", })
This function is directly modeled after the documented flow and parameters for exchanging an authorization code for tokens in Ory Hydra [Web server app and confidential client](https://www.ory.sh/docs/oauth2-oidc/authorization-code-flow#web-server-app-and-confidential-client).
w
@chilly-king-10285 where does the token url go?
c
A message from kapa.ai
The
token_url
is the URL of your OAuth2 server’s token endpoint, which is where your application sends the authorization code (received on the callback page) to exchange it for tokens (access token, ID token, etc.). In Ory Hydra, this endpoint is typically:
Copy code
http(s)://<your-hydra-public-domain>/oauth2/token
For example, if you are running Hydra locally with the default configuration, the token URL would be:
Copy code
<http://127.0.0.1:4444/oauth2/token>
You use this URL in your backend or frontend code to POST the code and other required parameters, as shown in the Ory documentation:
Copy code
js const tokenUrl = "https://$<http://PROJECT_SLUG.projects.oryapis.com/oauth2/token|PROJECT_SLUG.projects.oryapis.com/oauth2/token>"
[Web server app and confidential client](https://www.ory.sh/docs/oauth2-oidc/authorization-code-flow#web-server-app-and-confidential-client) So, the
token_url
goes in your code wherever you are making the request to exchange the authorization code for tokens.
w
@chilly-king-10285
Copy code
{
  "error": "invalid_client",
  "error_description": "Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method). The OAuth 2.0 Client supports client authentication method 'client_secret_basic', but method 'client_secret_post' was requested. You must configure the OAuth 2.0 client's 'token_endpoint_auth_method' value to accept 'client_secret_post'."
}
c
A message from kapa.ai
The error message:
Copy code
{
  "error": "invalid_client",
  "error_description": "Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method). The OAuth 2.0 Client supports client authentication method 'client_secret_basic', but method 'client_secret_post' was requested. You must configure the OAuth 2.0 client's 'token_endpoint_auth_method' value to accept 'client_secret_post'."
}
means that your OAuth2 client is currently configured to use the
client_secret_basic
authentication method, but your token request is using the
client_secret_post
method. These are two different ways of sending the client credentials to the token endpoint: - client_secret_basic: Credentials are sent in the HTTP
Authorization
header (using Basic Auth). - client_secret_post: Credentials are sent in the POST body. How to fix: You have two options: 1. Change your client to use
client_secret_basic
(the default). This means sending the
client_id
and
client_secret
in the
Authorization
header as described in the docs:
Copy code
Authorization: Basic base64(urlencoded(id):urlencoded(secret))
[See documentation](https://www.ory.sh/docs/oauth2-oidc/overview/oauth2-concepts#creating-oauth2-clients) 2. Update your OAuth2 client to accept
client_secret_post
. You can do this using the CLI:
Copy code
bash    hydra update oauth2-client <client-id> \      --endpoint <http://127.0.0.1:4445> \      --token-endpoint-auth-method client_secret_post
[See documentation](https://www.ory.sh/docs/hydra/cli/hydra-update-oauth2-client#options) Summary: Your client and your token request must use the same authentication method. Either update your client configuration to accept
client_secret_post
, or change your code to use
client_secret_basic
as required by your current client configuration.
w
@chilly-king-10285 Update this to be multi line please
Copy code
hydra update oauth2-client fb3b9eda-566a-450c-b205-16fce888a2aa --name "EAGLE SSO" --grant-type authoriz
ation_code --grant-type refresh_token --response-type code --scope openid --redirect-uri <http://127.0.0.1:30>
01/auth/callback --endpoint <http://127.0.0.1:4445> --skip-consent --skip-logout-consent --token-endpoint-auth-method none
c
A message from kapa.ai
Certainly! Here is your
hydra update oauth2-client
command, formatted as a multi-line command for better readability:
Copy code
bash hydra update oauth2-client fb3b9eda-566a-450c-b205-16fce888a2aa \   --name "EAGLE SSO" \   --grant-type authorization_code \   --grant-type refresh_token \   --response-type code \   --scope openid \   --redirect-uri <http://127.0.0.1:3001/auth/callback> \   --endpoint <http://127.0.0.1:4445> \   --skip-consent \   --skip-logout-consent \   --token-endpoint-auth-method none
This format is consistent with examples in the Ory Hydra documentation for multi-line CLI usage [hydra update oauth2-client](https://www.ory.sh/docs/hydra/cli/hydra-update-oauth2-client#hydra-update-oauth2-client).
w
@chilly-king-10285 Okay, once I exchange the code for a token, what do I do?
c
A message from kapa.ai
Once you exchange the authorization code for a token (typically an access token, and possibly an ID token and refresh token), you should: 1. Store the tokens securely: Save the access token (and refresh token, if present) in a secure location. For web applications, recommended storage options include
httpOnly
cookies (for best security), or, if necessary, local storage (with awareness of XSS risks). The Ory documentation recommends
httpOnly
and
secure
cookies, or the OS keychain for mobile apps [Refresh token rotation and security protections](https://www.ory.sh/docs/oauth2-oidc/refresh-token-grant#refresh-token-rotation-and-security-protections). 2. Use the access token to access protected resources: Include the access token in the
Authorization
header as a Bearer token when making requests to your resource server or API. For example:
Copy code
js    fetch("<https://api.example.com/resource>", {      headers: {        Authorization: "Bearer " + accessToken,      },    })
The resource server will validate the token before granting access [Accessing APIs](https://www.ory.sh/docs/oauth2-oidc/overview/oauth2-concepts#accessing-apis). 3. (Optional) Use the ID token for user information: If you received an ID token (when using OpenID Connect), you can decode it to get information about the authenticated user. This is useful for displaying user info in your app [Modifying the authorization code flow](https://www.ory.sh/docs/oauth2-oidc/authorization-code-flow#modifying-the-authorization-code-flow). 4. (Optional) Use the refresh token to obtain new access tokens: If a refresh token was issued, you can use it to get new access tokens when the current one expires, without requiring the user to log in again [OAuth2 refresh token grant](https://www.ory.sh/docs/oauth2-oidc/refresh-token-grant). Summary: After exchanging the code for tokens, store them securely, use the access token to authenticate API requests, and use the refresh token (if present) to maintain the session without re-authentication.
w
@chilly-king-10285 How do I decode the id token?
c
A message from kapa.ai
To decode the ID token, you need to parse the JSON Web Token (JWT) that is returned as the
id_token
in the token response. The ID token is a JWT, which is a base64url-encoded string with three parts: header, payload, and signature. You can decode the header and payload using any standard JWT library or by manually base64url-decoding the first two segments. The Ory documentation provides an example of the ID token payload and explains that it is a JWT containing claims about the user, such as their name and email [The ID Token](https://www.ory.sh/docs/oauth2-oidc/overview/oidc-concepts#the-id-token). Example ID token payload:
Copy code
json {   "iss": "<https://my-domain.projects.oryapis.com>",   "sub": "some-identity-id",   "aud": "some-client-id",   "exp": 1311281970,   "iat": 1311280970,   "nonce": "KxSty13b2L",   "name": "Jane Doe",   "given_name": "Jane",   "family_name": "Doe",   "email": "<mailto:jane@example.org|jane@example.org>",   "email_verified": true }
How to decode: - You can use a JWT library in your programming language (e.g., [jsonwebtoken](https://www.npmjs.com/package/jsonwebtoken) for Node.js) to decode the token. - Or, for a quick check, you can paste the token into [jwt.io](https://jwt.io/) to see the decoded contents. Note: Decoding the token does not verify its signature. For security, you should always verify the signature using the public keys from your Ory project's JWKS endpoint (`https://$PROJECT_SLUG.projects.oryapis.com/.well-known/jwks.json`) as described in the documentation [Modifying the authorization code flow](https://www.ory.sh/docs/oauth2-oidc/authorization-code-flow#modifying-the-authorization-code-flow). If you need to programmatically validate the token, use a JWT library that supports signature verification and provide it with the correct public key.
w
@chilly-king-10285 Give me a function to decode the id token
c
A message from kapa.ai
Certainly! The knowledge sources explain that the ID token is a JWT (JSON Web Token), which is a base64url-encoded string containing three parts: header, payload, and signature. You can decode the header and payload by splitting the token and base64url-decoding the first two segments. This process is described in the Ory documentation [The ID Token](https://www.ory.sh/docs/oauth2-oidc/overview/oidc-concepts#the-id-token). Here is a simple TypeScript function to decode the payload of an ID token (without verifying the signature):
Copy code
ts function decodeIdToken(idToken: string): any {   const parts = idToken.split('.')   if (parts.length !== 3) {     throw new Error("Invalid JWT format")   }   const payload = parts[1]   // Add padding if necessary   const base64 = payload.replace(/-/g, '+').replace(/_/g, '/')     + '='.repeat((4 - payload.length % 4) % 4)   const decoded = atob(base64)   return JSON.parse(decoded) }
Usage:
Copy code
ts const decoded = decodeIdToken(idToken) console.log(decoded)
> Note: This function only decodes the token and does not verify its signature. For security-sensitive operations, you should always verify the signature using the public keys from your Ory project's JWKS endpoint as described in the documentation [Modifying the authorization code flow](https://www.ory.sh/docs/oauth2-oidc/authorization-code-flow#modifying-the-authorization-code-flow).
w
@chilly-king-10285 This only returns
Copy code
{
  "at_hash": "s1y6B9emtcI4RmHHrn_18g",
  "aud": [
    "3fcb8cbb-bbe8-4a90-ad2c-6eeca275db6e"
  ],
  "auth_time": 1758230726,
  "exp": 1758236491,
  "iat": 1758232891,
  "iss": "<http://127.0.0.1:4444>",
  "jti": "639b2339-96c0-4aa5-8b88-507f5d8e5131",
  "rat": 1758232888,
  "sid": "c5430cfc-183c-4614-9d60-82edb8763d0f",
  "sub": "53f85911-2c7c-437b-b59c-e58c66c86b22"
}
Which doesn't look like readable data?
c
A message from kapa.ai
The payload you are seeing in your decoded ID token is actually correct and expected for a valid JWT issued by Ory Hydra. The fields such as
sub
,
aud
,
exp
,
iat
, and
iss
are standard OpenID Connect claims. The
sub
field is the subject identifier (the user's unique ID in Kratos), and the other fields are related to the token's audience, expiration, and issuer. If you are expecting additional user profile information (like
email
,
name
, etc.), this depends on: 1. Scopes Requested: The ID token will only include claims that correspond to the scopes you requested and that are supported by your Ory setup. For example, to get the user's email, you must request the
email
scope, and your consent app must include the email in the ID token when accepting the consent request [Customize OpenID Connect scope and claims](https://www.ory.sh/docs/oauth2-oidc/openid-connect-claims-scope-custom). 2. Consent App Logic: If you are using a custom consent app, you must explicitly add the desired claims (like
email
,
name
, etc.) to the
id_token
in the session when accepting the consent request. By default, only the standard claims are included unless you map additional traits from the identity [Consent endpoint code example](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow#consent-endpoint-code-example). 3. Identity Schema and Mapping: The claims available depend on your Kratos identity schema and how you map identity traits to OIDC claims in your consent logic [Customize OpenID Connect scope and claims](https://www.ory.sh/docs/oauth2-oidc/openid-connect-claims-scope-custom). Summary: - The decoded token you see is valid and contains standard OIDC claims. - To include more user data (like
email
), ensure you request the appropriate scopes and that your consent app maps those traits into the
id_token
when accepting the consent request. - If you are using the default Ory Network UI, only minimal claims may be included by default. For more, you need to customize the consent logic. If you need more detailed claims, review your consent app logic and the scopes you are requesting, as described in the Ory documentation [Customize OpenID Connect scope and claims](https://www.ory.sh/docs/oauth2-oidc/openid-connect-claims-scope-custom) [Consent endpoint code example](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow#consent-endpoint-code-example).
w
@chilly-king-10285 Can I access the whoami endpoint with the bearer token or should I use the users unique id?
c
A message from kapa.ai
You can access the
/sessions/whoami
endpoint using a Bearer token (Ory Session Token) in the
Authorization
header. This is the recommended way to check the current session and retrieve the user's identity information. You do not need to use the user's unique ID for this endpoint. Example using a Bearer token:
Copy code
bash curl -H "Authorization: Bearer {your-session-token}" \   "https://$<http://PROJECT_SLUG.projects.oryapis.com/sessions/whoami|PROJECT_SLUG.projects.oryapis.com/sessions/whoami>"
This will return the session payload, including the identity information, if the token is valid. If the session is not active or the token is invalid, you will receive a
401 Unauthorized
response [Check session status](https://www.ory.sh/docs/identities/sign-in/check-session-token-cookie-api) [Session-management overview](https://www.ory.sh/docs/kratos/session-management/overview#using-ory-session-token). Summary: - Use the Bearer token (session token) with the
/sessions/whoami
endpoint. - You do not need to provide the user's unique ID for this endpoint.