wooden-addition-60810
09/18/2025, 7:43 PMchilly-king-10285
09/18/2025, 7:43 PMchilly-king-10285
09/18/2025, 7:44 PM/auth
endpoint, including the required parameters (client_id
, redirect_uri
, response_type
, scope
, etc.). This is the standard OAuth2 authorization code flow. Example: 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: 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): 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).wooden-addition-60810
09/18/2025, 7:44 PMchilly-king-10285
09/18/2025, 7:44 PMchilly-king-10285
09/18/2025, 7:45 PM@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 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. 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).wooden-addition-60810
09/18/2025, 7:46 PMchilly-king-10285
09/18/2025, 7:46 PMchilly-king-10285
09/18/2025, 7:46 PM@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.wooden-addition-60810
09/18/2025, 7:47 PMchilly-king-10285
09/18/2025, 7:47 PMchilly-king-10285
09/18/2025, 7:47 PM@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: 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: 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.wooden-addition-60810
09/18/2025, 7:51 PMchilly-king-10285
09/18/2025, 7:51 PMchilly-king-10285
09/18/2025, 7:51 PM@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: 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: 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.wooden-addition-60810
09/18/2025, 7:53 PMchilly-king-10285
09/18/2025, 7:53 PMchilly-king-10285
09/18/2025, 7:53 PMhydra 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: 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).wooden-addition-60810
09/18/2025, 7:57 PMhydra 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-10285chilly-king-10285
09/18/2025, 7:57 PMchilly-king-10285
09/18/2025, 7:57 PM--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: 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).wooden-addition-60810
09/18/2025, 8:00 PMchilly-king-10285
09/18/2025, 8:00 PMchilly-king-10285
09/18/2025, 8:00 PM/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 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.wooden-addition-60810
09/18/2025, 8:05 PMchilly-king-10285
09/18/2025, 8:05 PMchilly-king-10285
09/18/2025, 8:06 PMThe '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: 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).wooden-addition-60810
09/18/2025, 8:08 PMGET /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:
"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>
);
}
chilly-king-10285
09/18/2025, 8:08 PMchilly-king-10285
09/18/2025, 8:08 PMredirect_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): 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: 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).wooden-addition-60810
09/18/2025, 8:21 PMchilly-king-10285
09/18/2025, 8:21 PMchilly-king-10285
09/18/2025, 8:21 PM--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).wooden-addition-60810
09/18/2025, 8:34 PMasync 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,
};
}
chilly-king-10285
09/18/2025, 8:34 PMchilly-king-10285
09/18/2025, 8:35 PMIdentityApi
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.wooden-addition-60810
09/18/2025, 8:35 PMchilly-king-10285
09/18/2025, 8:35 PMchilly-king-10285
09/18/2025, 8:35 PM@ory/client
library and instantiate the IdentityApi
class. Here’s how you can do it: 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.wooden-addition-60810
09/18/2025, 8:40 PMserve:
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
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
chilly-king-10285
09/18/2025, 8:40 PMchilly-king-10285
09/18/2025, 8:40 PMThe 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: > > > 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).wooden-addition-60810
09/18/2025, 8:42 PMAccess 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-10285chilly-king-10285
09/18/2025, 8:42 PMchilly-king-10285
09/18/2025, 8:42 PMAccess 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.wooden-addition-60810
09/18/2025, 8:43 PMimport {
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 };
chilly-king-10285
09/18/2025, 8:43 PMchilly-king-10285
09/18/2025, 8:43 PM127.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).wooden-addition-60810
09/18/2025, 8:46 PMchilly-king-10285
09/18/2025, 8:46 PMchilly-king-10285
09/18/2025, 8:46 PMcode
, 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): 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).wooden-addition-60810
09/18/2025, 8:53 PMchilly-king-10285
09/18/2025, 8:53 PMchilly-king-10285
09/18/2025, 8:53 PMstate
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.wooden-addition-60810
09/18/2025, 8:58 PMchilly-king-10285
09/18/2025, 8:58 PMchilly-king-10285
09/18/2025, 8:58 PMfetch
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): 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: 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).wooden-addition-60810
09/18/2025, 9:26 PMchilly-king-10285
09/18/2025, 9:26 PMchilly-king-10285
09/18/2025, 9:26 PMtoken_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: 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: <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: 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.wooden-addition-60810
09/18/2025, 9:36 PM{
"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'."
}
chilly-king-10285
09/18/2025, 9:36 PMchilly-king-10285
09/18/2025, 9:36 PM{
"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: 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: 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.wooden-addition-60810
09/18/2025, 9:49 PMhydra 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
chilly-king-10285
09/18/2025, 9:49 PMchilly-king-10285
09/18/2025, 9:49 PMhydra update oauth2-client
command, formatted as a multi-line command for better readability: 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).wooden-addition-60810
09/18/2025, 9:54 PMchilly-king-10285
09/18/2025, 9:54 PMchilly-king-10285
09/18/2025, 9:54 PMhttpOnly
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: 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.wooden-addition-60810
09/18/2025, 9:56 PMchilly-king-10285
09/18/2025, 9:56 PMchilly-king-10285
09/18/2025, 9:56 PMid_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: 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.wooden-addition-60810
09/18/2025, 10:00 PMchilly-king-10285
09/18/2025, 10:00 PMchilly-king-10285
09/18/2025, 10:00 PMts 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: 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).wooden-addition-60810
09/18/2025, 10:02 PM{
"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?chilly-king-10285
09/18/2025, 10:02 PMchilly-king-10285
09/18/2025, 10:02 PMsub
, 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).wooden-addition-60810
09/18/2025, 10:03 PMchilly-king-10285
09/18/2025, 10:03 PMchilly-king-10285
09/18/2025, 10:03 PM/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: 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.