<@U04UQ68083H> When using ory tunnel to develop cu...
# ory-copilot
w
@chilly-king-10285 When using ory tunnel to develop custom login pages locally, I'm suddenly getting 403 errors on the consent endpoint with the message - 'Access credentials are not sufficient to access this resource'. Logging in works fine, as does the settings page etc., just when local apps attempt token exchange and get to the code challenge stage they always get a 403 back from the local ory instance. I'm not sure what could be causing this.
c
A message from kapa.ai
It sounds like the login and settings flows work through Ory Tunnel, but the OAuth2 consent step (token exchange/code flow) fails with 403 “Access credentials are not sufficient…”. That typically points to either: - The OAuth2 login/consent URLs not pointing to your custom endpoints, so Hydra can’t call back correctly; or - The tunnel/cookie/CORS setup causing requests to come from a different “site” (127.0.0.1 vs localhost), breaking cookies/CSRF/session at the moment Hydra calls your consent endpoint; or - The consent POST is missing CSRF/session context. Here’s a tight checklist to resolve: 1) Ensure Hydra is configured to use your consent URL Verify your project’s OAuth2 configuration has the consent and login URLs pointing to your local app endpoints (e.g., http://localhost:3000/consent and /login), then update the config. [[Configure Network](https://www.ory.sh/docs/hydra/guides/custom-ui-oauth2#configure-ory-network)] - Get current config: ory get oauth2-config --project <project-id> --workspace <workspace-id> --format yaml > config.yaml - Ensure: oauth2: urls: consent: http://localhost:3000/consent login: http://localhost:3000/login - Apply: ory update oauth2-config --project <project-id> --workspace <workspace-id> --file config.yaml 2) Run Ory Tunnel correctly and use the tunnel domain consistently - Start tunnel against your app URL and use the tunnel base for Ory frontend calls: ory tunnel --project <project-or-slug> http://localhost:3000 - Send frontend SDK calls to http://localhost:4000 (not the cloud slug). [[Local dev](https://www.ory.sh/docs/getting-started/local-development#local-development); [Proxy/Tunnel guide](https://www.ory.sh/docs/guides/cli/proxy-and-tunnel)] - Avoid mixing 127.0.0.1 and localhost. Cookies are domain-bound; mixing them can drop sessions/CSRF and cause 403s. Use localhost everywhere. [[Local dev](https://www.ory.sh/docs/getting-started/local-development#local-development)] 3) Verify the consent endpoint flow and CSRF handling - Your consent GET should fetch the consent request using the consent_challenge, and either auto-accept or render a form with a csrf token and hidden consent_challenge. [[Consent code](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow#consent-endpoint-code-example)] - On POST: - If user denied, call rejectOAuth2ConsentRequest with error info, then redirect to body.redirect_to. [[Consent code](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow#consent-endpoint-code-example); [API reject](https://raw.githubusercontent.com/ory/docs/master/docs/reference/api.json)] - If accepted, fetch the consent request again, build session claims if needed, call acceptOAuth2ConsentRequest with grant_scope, grant_access_token_audience, and session, then redirect to body.redirect_to. [[Consent code](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow#consent-endpoint-code-example)] - Ensure your consent POST includes the CSRF token and the browser sends cookies. When rendering the consent UI, include req.csrfToken() and post to your own action URL. [[Consent code](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow#consent-endpoint-code-example); [Kratos CSRF debug](https://www.ory.sh/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#debug); [CSRF troubleshooting](https://www.ory.sh/docs/troubleshooting/csrf)] 4) Double-check cookies/CORS via the tunnel - The tunnel places Ory and your app on the same site for cookies/CSRF. Use the tunnel URL for all Ory browser endpoints. [[Proxy/Tunnel guide](https://www.ory.sh/docs/guides/cli/proxy-and-tunnel); [ory tunnel CLI](https://www.ory.sh/docs/cli/ory-tunnel#ory-tunnel); [Security model](https://www.ory.sh/docs/security-model)] 5) If you use OAuth2 webhooks (claims at refresh/introspection), confirm they aren’t denying the exchange - A webhook replying 403 will abort the token exchange toward the client with an error. If you have such hooks, make sure they return 200/204 or correct JSON when appropriate. [[Webhook response](https://www.ory.sh/docs/hydra/guides/claims-at-refresh#responding-to-the-webhook)] Common pitfalls to look for in your setup: - Mixing domains: starting login on localhost but your app, redirects, or consent POST hit 127.0.0.1. Use localhost consistently. [[Local dev](https://www.ory.sh/docs/getting-started/local-development#local-development)] - Missing/invalid CSRF token on consent POST. Ensure the csrf token is rendered and posted back, and the browser sends cookies. [[CSRF debug](https://www.ory.sh/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#debug); [Troubleshooting](https://www.ory.sh/docs/troubleshooting/csrf)] - OAuth2 URLs still pointing to Ory Managed screens instead of your local consent page. [[Configure Network](https://www.ory.sh/docs/hydra/guides/custom-ui-oauth2#configure-ory-network)] If you’ve checked the above and still see 403 at the consent step, please share: - The exact consent GET/POST paths and whether they run on localhost vs 127.0.0.1 - Whether your consent POST includes csrf_token and consent_challenge - Whether you’re using any OAuth2 webhooks - Snippets of the failing request/response headers (Cookie, Set-Cookie, Origin) from the browser’s Network tab That will help pinpoint whether it’s a cookie/CSRF domain issue or an OAuth2 policy/webhook denial.
w
@chilly-king-10285 why would this have been working the other day and, as far as I know with no changes, not be working today? What could have caused this? Have there been any default settings updates or anything like that?
c
A message from kapa.ai
Short answer: there isn’t a documented global default change that would suddenly break consent via Ory Tunnel, but a few known culprits can make something “work yesterday, fail today” without obvious code/config edits. Likely causes, with pointers: - Localhost vs 127.0.0.1 drift. Cookies are domain-bound; if any step of the OAuth2 flow uses 127.0.0.1 while the rest uses localhost (or vice‑versa), session/CSRF context is lost and you can hit 403/insufficient credentials. This often changes “silently” when a URL is copied or a callback uses a different host. [[Local development](https://www.ory.sh/docs/getting-started/local-development#local-development); [CSRF debug](https://www.ory.sh/docs/kratos/debug/csrf)] - Tunnel vs direct project URL. For browser flows, all Ory Frontend API calls must go through the tunnel base (http://localhost:4000). Mixing in the cloud slug even once can drop cookies and break the consent step. [[Local development](https://www.ory.sh/docs/getting-started/local-development#local-development); [Tunnel guide](https://www.ory.sh/docs/guides/cli/proxy-and-tunnel)] - CSRF/session context on consent POST. If the consent page stopped including the hidden csrf_token or the browser stopped sending cookies (e.g., due to the domain mismatch above), the consent acceptance can fail with 403. This may appear after a local dependency/browser update. [[BYO UI CSRF](https://www.ory.sh/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#debug); [CSRF troubleshooting](https://www.ory.sh/docs/troubleshooting/csrf)] - Incognito/anti-tracking modes. Running one part of the flow in a private window or a privacy browser mode can discard cookies between redirects, leading to 403 at consent/token exchange. [[CSRF OAuth2 notes](https://www.ory.sh/docs/troubleshooting/csrf#ory-oauth2)] - Ory Tunnel hiccup. There was a recent, short-lived issue where tunnel auth “froze” and was then fixed; if you hit that window or an outdated CLI, behavior could have flipped between days. Users reported it working again after a fix. Try updating the CLI and re-running. [[Tunnel issue 407](https://github.com/ory/network/issues/407); [Tunnel issue 408](https://github.com/ory/network/issues/408)] - Subtle cookie policy constraints. SameSite/Secure behavior and Chrome’s handling can trip flows if any endpoint shifts between HTTP/HTTPS or different origins. Keeping everything on the same origin via tunnel avoids this. [[Security model](https://www.ory.sh/docs/security-model); [Hydra CSRF pitfalls](https://www.ory.sh/docs/self-hosted/hydra/debug/csrf)] What I’d check first: - Ensure all URLs (your app, consent GET/POST, callback, and Ory endpoints) consistently use localhost (not 127.0.0.1) and go through the tunnel for browser calls. [[Local development](https://www.ory.sh/docs/getting-started/local-development#local-development); [Tunnel guide](https://www.ory.sh/docs/guides/cli/proxy-and-tunnel)] - In DevTools, verify the consent POST includes cookies and a csrf_token, and that Set-Cookie/Cookie domains are localhost. [[CSRF troubleshooting](https://www.ory.sh/docs/troubleshooting/csrf); [BYO UI CSRF](https://www.ory.sh/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#debug)] - Update to the latest Ory CLI and restart the tunnel. [[Tunnel issue 408](https://github.com/ory/network/issues/408)] If the issue persists, please share a failing network trace (hostnames, Cookie/Set‑Cookie headers on the consent GET/POST) so we can pinpoint whether it’s a domain/cookie or tunnel mismatch.
w
@chilly-king-10285 I'm trying again in the vague hope of hearing something helpful that I missed the first time. I can login fine but when I attempt to do the code challenge, I get a 403 forbidden with the message 'Access credentials are not sufficient to access this resource'. As far as I can tell, all of the cookies that should be included have the right settings (Lax, secure = false, httpOnly). Talk me through it again, how can I debug this, how can I continue?
c
A message from kapa.ai
Thanks for sticking with it — let’s walk through a focused, reproducible debug path for 403 at the OAuth2 consent/token step when using Ory Tunnel. What “403: Access credentials are not sufficient” usually means here - The browser didn’t send the expected cookies/CSRF context at the consent accept/reject step, often because of an origin/host mismatch (localhost vs 127.0.0.1, tunnel vs slug), so Hydra can’t validate the challenge and denies. [[Local development](https://www.ory.sh/docs/getting-started/local-development#local-development); [CSRF troubleshooting](https://www.ory.sh/docs/troubleshooting/csrf); [OAuth2 CSRF notes](https://www.ory.sh/docs/troubleshooting/csrf#ory-oauth2)] - The consent UI POST missed the CSRF token or wasn’t sent with credentials, so the request gets rejected. [[BYO UI CSRF debug](https://www.ory.sh/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#debug); [CSRF troubleshooting](https://www.ory.sh/docs/troubleshooting/csrf)] - The flow was mixed across different origins (cloud slug + tunnel), which invalidates cookies. [[Security model](https://www.ory.sh/docs/security-model); [Local development](https://www.ory.sh/docs/getting-started/local-development#local-development)] Step-by-step debugging checklist 1) Verify you’re using the tunnel for all browser-facing calls - Start tunnel and keep its URL consistent: - ory tunnel --project <slug-or-id> http://localhost:3000 [[ory tunnel](https://www.ory.sh/docs/cli/ory-tunnel#ory-tunnel)] - Ensure your frontend SDK points to http://localhost:4000 (not the cloud slug) for all browser flows. [[Local development](https://www.ory.sh/docs/getting-started/local-development#local-development)] - Do not mix direct calls to https://<slug>.projects.oryapis.com from the browser. [[Security model](https://www.ory.sh/docs/security-model)] 2) Keep the host consistent: never mix localhost and 127.0.0.1 - Cookies are domain-bound. Use localhost everywhere (your app URLs, consent/login endpoints, callback, and the tunnel). [[Local development](https://www.ory.sh/docs/getting-started/local-development#local-development); [Hydra CSRF pitfalls](https://www.ory.sh/docs/self-hosted/hydra/debug/csrf)] 3) Inspect the failing consent request in DevTools - In the Network tab, select the consent POST (the one that calls your consent endpoint or the accept/reject handler) and confirm: - Cookie header is present and includes the relevant cookies (set by Ory via the tunnel). [[CSRF troubleshooting](https://www.ory.sh/docs/troubleshooting/csrf)] - Origin/Host are localhost, and the request is not cross-site. - The form body includes the expected CSRF token field (csrf_token) and the consent_challenge. [[BYO UI CSRF debug](https://www.ory.sh/docs/kratos/bring-your-own-ui/custom-ui-basic-integration#debug)] - If cookies are missing, you are likely hitting a different origin or the request isn’t sent with credentials. 4) Re-run the OAuth2 flow in a single, non-incognito window - Don’t run parallel OAuth2 flows in the same browser at once and avoid incognito/privacy modes that drop cookies. [[OAuth2 CSRF notes](https://www.ory.sh/docs/troubleshooting/csrf#ory-oauth2)] 5) Re-confirm your custom login/consent endpoints are wired correctly - Hydra redirects to the login and consent URLs you configured. Ensure those point to your local app (localhost) and handle the challenges as in the docs: - GET: fetch login/consent request using the challenge. - POST: accept or reject, then redirect to data.redirect_to. [[Custom login/consent flow](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow); [Login endpoint example](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow#login-endpoint-code-example)] - Important: The accept/reject calls must happen from the same browser context that received the challenge (so cookies/CSRF align). [[OAuth2 CSRF notes](https://www.ory.sh/docs/troubleshooting/csrf#ory-oauth2)] 6) Confirm you’re not calling /oauth2/auth via AJAX - That endpoint must be navigated/redirected in the browser (not XHR/fetch). [[OAuth2 CSRF notes](https://www.ory.sh/docs/troubleshooting/csrf#ory-oauth2)] 7) If you switched ports/domains recently, restart the tunnel and clear cookies - Chrome’s SameSite/Secure and cookie scoping can leave stale cookies behind. Restart tunnel, clear cookies for localhost, and retry. [[Hydra CSRF pitfalls](https://www.ory.sh/docs/self-hosted/hydra/debug/csrf); [Security model](https://www.ory.sh/docs/security-model)] What to capture if it still fails - The exact hosts for: - Your app (e.g., http://localhost:3000) - Tunnel (http://localhost:4000) - Login and consent endpoints (must be localhost) - Browser Network tab for: - GET /consent?consent_challenge=... - POST your consent form (show Request Headers: Cookie/Origin; and Form Data with csrf_token and consent_challenge) - The subsequent redirect back to Ory (Location) - Confirmation that all Ory Frontend API calls used http://localhost:4000. [[Local development](https://www.ory.sh/docs/getting-started/local-development#local-development)] Why this often “worked yesterday, fails today” - A subtle drift (copy-pasted 127.0.0.1 in one place, or a cloud slug URL sneaks into one step) breaks cookies/CSRF at the consent step. This is the most common cause in tunnel-based local dev and matches the 403 you’re seeing. [[Local development](https://www.ory.sh/docs/getting-started/local-development#local-development); [Security model](https://www.ory.sh/docs/security-model); [OAuth2 CSRF notes](https://www.ory.sh/docs/troubleshooting/csrf#ory-oauth2)] If you want, paste the request/response snippets from DevTools (with hostnames and Cookie/Set-Cookie headers) and I’ll help pinpoint exactly where the context is getting lost.
w
@chilly-king-10285 the failing call is a GET, not a POST
c
A message from kapa.ai
Thanks for the clarification — a failing GET (not POST) at the consent stage narrows it down. Where a GET can legitimately 403 in this flow - The initial redirect to your consent endpoint is a GET: /oauth2-consent?consent_challenge=... Your handler typically does a GET to fetch the consent request using the consent_challenge and either: - immediately accepts (skip/trusted client), or - renders the consent UI. [[Consent](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow#consent); [Code example](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow#consent-endpoint-code-example)] - If that GET is missing the browser cookies or reaches the wrong origin, Hydra/Ory can’t correlate the challenge/session and you can get a 403 “Access credentials are not sufficient”. Mixing origins (localhost vs 127.0.0.1, or tunnel vs cloud slug) is the most common cause. [[Local development](https://www.ory.sh/docs/getting-started/local-development#local-development); [OAuth2 CSRF notes](https://www.ory.sh/docs/troubleshooting/csrf#ory-oauth2); [Security model](https://www.ory.sh/docs/security-model)] Targeted debug steps for a failing GET 1) Verify the exact URL and host - In DevTools, open the failing GET (to your consent endpoint or to Ory’s accept URL if you skip UI). - Confirm: - Host is localhost everywhere (not 127.0.0.1). [[Local development](https://www.ory.sh/docs/getting-started/local-development#local-development)] - You are not mixing the tunnel (http://localhost:4000) and the cloud slug in the same browser flow. All browser-facing Ory calls should go through the tunnel during local dev. [[Local development](https://www.ory.sh/docs/getting-started/local-development#local-development); [ory tunnel](https://www.ory.sh/docs/cli/ory-tunnel#ory-tunnel)] 2) Check the Cookie header on that GET - In the Network tab, the GET must include cookies set earlier in the flow (session/anti-CSRF). If Cookie is empty/missing, the request likely hit a different origin or wasn’t routed through the tunnel as required for cookies to work. [[CSRF troubleshooting](https://www.ory.sh/docs/troubleshooting/csrf); [Security model](https://www.ory.sh/docs/security-model)] 3) Confirm your consent handler uses the challenge correctly - On GET, fetch the consent request using consent_challenge and decide whether to skip or render UI. If you skip, immediately call accept and then redirect to the provided redirect_to. [[Consent](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow#consent); [Code example](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow#consent-endpoint-code-example)] - If the GET lacks the consent_challenge query param, or if your handler calls Ory APIs from a different origin than the browser flow, it can fail early. 4) Avoid parallel/isolated flows or incognito - Running two OAuth2 flows at once or using privacy modes can drop cookies, causing a 403 on the GET before you ever POST. [[OAuth2 CSRF notes](https://www.ory.sh/docs/troubleshooting/csrf#ory-oauth2)] 5) Restart the tunnel and clear localhost cookies - Stale cookies with mismatched flags/domains can linger. Restart ory tunnel, clear cookies for localhost, and try again. [[Hydra CSRF pitfalls](https://www.ory.sh/docs/self-hosted/hydra/debug/csrf); [ory tunnel](https://www.ory.sh/docs/cli/ory-tunnel#ory-tunnel)] Quick self-check - Are your OAuth2 URLs configured to your local consent/login endpoints? [[Consent](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow#consent)] - Are all browser calls to Ory made via http://localhost:4000, and your app/pages on http://localhost:3000, both using localhost (not 127.0.0.1)? [[Local development](https://www.ory.sh/docs/getting-started/local-development#local-development)] - Does the failing GET carry Cookie headers? If not, it’s almost certainly an origin/domain mix-up. [[CSRF troubleshooting](https://www.ory.sh/docs/troubleshooting/csrf); [Security model](https://www.ory.sh/docs/security-model)] If you can share the exact failing GET line from DevTools (URL, Host, and the Request Headers showing Cookie and Origin), I can help pinpoint precisely where the context is being lost.
w
@chilly-king-10285 how many cookies would we expect to send with a consent request?
c
A message from kapa.ai
Short answer: there isn’t a fixed number documented. It depends on your setup and which parts of Ory are involved in the browser flow. What you should typically see in a browser-based consent GET/POST: - Ory session cookie(s): used to maintain the authenticated user session. In Ory Network these are identifiable by the ory_session_ prefix. [[Token formats](https://www.ory.sh/docs/security-compliance/token-formats)] - Ory anti-CSRF cookie(s): set when using browser endpoints, required so Hydra/Kratos can validate browser-submitted forms. [[Browser vs native](https://www.ory.sh/docs/identities/native-browser); [CSRF troubleshooting](https://www.ory.sh/docs/troubleshooting/csrf); [Security model](https://www.ory.sh/docs/security-model#http-cookies)] - Potential additional Ory cookies: internal cookies for load balancing or other protections, prefixed with ory_. If you’re on Ory Network, you may also see Cloudflare cookies (__cf, _cf, cf). [[Token formats](https://www.ory.sh/docs/security-compliance/token-formats)] Important points while debugging: - Cookies are HttpOnly and SameSite-based, so you won’t read them in JS, but they must be present on the request. Use the browser Network tab to confirm Cookie headers. [[CSRF troubleshooting](https://www.ory.sh/docs/troubleshooting/csrf)] - For server-side handlers that fetch consent/login flows, you must forward the Cookie header you received from the browser; otherwise, fetching the flow will fail. [[CSRF troubleshooting](https://www.ory.sh/docs/troubleshooting/csrf)] - When rendering a consent form, include the CSRF token field (csrf_token) and submit it back to your POST handler. [[Consent flow](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow#consent); [Consent component](https://www.ory.sh/docs/elements/reference/@ory/elements-react/theme/functions/Consent)] So, in practice during consent you commonly see: - One session cookie - One or more anti-CSRF/ory_ cookies - Possibly Cloudflare cookies (on Ory Network) There’s no single “expected count,” but missing the session and anti-CSRF cookies will typically cause 403/CSRF errors in consent flows. [[Security model](https://www.ory.sh/docs/security-model#http-cookies); [CSRF troubleshooting](https://www.ory.sh/docs/troubleshooting/csrf)]
w
@chilly-king-10285 there are indeed cloudflare cookies. The browser gives a specific warning about the cloudflare cookie in fact. Would the lack of a cloudflare cookie cause the 403?
c
A message from kapa.ai
Short answer: Unlikely. The Cloudflare cookies are managed by Cloudflare’s edge and aren’t part of Ory’s auth/session model. Missing a Cloudflare cookie shouldn’t, by itself, cause a 403 in the OAuth2 consent flow. What does matter for consent are the Ory cookies (session and CSRF-related) and that they’re sent on the correct origin/domain. Ory documents that session/CSRF rely on its own cookies and strict domain/origin rules; Cloudflare cookies are listed as “other” cookies that may appear on Ory Network but aren’t used for your app’s auth decisions. [[Security model](https://www.ory.sh/docs/security-model#http-cookies); [Token formats](https://www.ory.sh/docs/security-compliance/token-formats)] If you’re seeing 403 “Access credentials are not sufficient,” focus on: - Ensuring the browser sends Ory session/CSRF cookies on the consent GET/POST and that the flow isn’t mixed across origins (localhost vs 127.0.0.1, tunnel vs project slug). [[Security model](https://www.ory.sh/docs/security-model#http-cookies); [CSRF troubleshooting](https://www.ory.sh/docs/troubleshooting/csrf); [OAuth2 notes](https://www.ory.sh/docs/troubleshooting/csrf#ory-oauth2)] - Verifying you don’t call /oauth2/auth via AJAX and that all browser-facing calls go through the tunnel during local dev. [[OAuth2 notes](https://www.ory.sh/docs/troubleshooting/csrf#ory-oauth2); [Security model](https://www.ory.sh/docs/security-model)] So, a Cloudflare warning in DevTools is not the likely root cause; missing/mismatched Ory cookies or origin drift is.
w
@chilly-king-10285 all cookies are listed in the request cookie header on the network tab of my browser so are presumably being sent (?). As for origin drift, I really don't think that's the issue. I never use 127.0.0.1 nor the network slug url so unless it is redirecting to a network slug url because of something returned from ory tunnel itself, I don't think that's the issue
c
A message from kapa.ai
Thanks for the extra detail. If the failing call is a GET to your consent endpoint and the Network tab shows a Cookie header present, the next most common causes (with tunnel/local dev) are: 1) Mixed origins within the same browser flow, but not the obvious 127.0.0.1 vs localhost - Even if you never type 127.0.0.1 or the project slug, a single redirect or SDK call to the cloud slug can break cookies because browsers won’t send cookies across different top-level domains. Double-check that every browser-facing Ory call (self-service, /oauth2/auth redirects, toSession, etc.) goes via the tunnel base (http://localhost:4000) and none hit https://$PROJECT_SLUG.projects.oryapis.com. [[Local development](https://www.ory.sh/docs/getting-started/local-development#local-development); [Security model](https://www.ory.sh/docs/security-model)] How to verify quickly: - In DevTools > Network, filter by “ory” and inspect all requests around the failure. If any request URL is the cloud slug instead of http://localhost:4000, that’s the drift. [[Local development](https://www.ory.sh/docs/getting-started/local-development#local-development)] 2) Consent/login URLs not pointing to your local endpoints anymore - Re-download the OAuth2 config and confirm oauth2.urls.login and oauth2.urls.consent are your localhost pages (not defaults). Then update if needed. [[Configure Network](https://www.ory.sh/docs/hydra/guides/custom-ui-oauth2#configure-ory-network)] 3) CSRF/session validation on the consent GET path - Even on GET, the server expects the browser context (cookies) to match the challenge. If your consent handler fetches the consent request from the server side, be sure you forward the browser’s Cookie header when you call Ory (otherwise Ory can’t correlate the session). This is the same pattern as with self-service flows: include the Cookie header from the incoming request when fetching the flow/request. [[CSRF troubleshooting](https://www.ory.sh/docs/troubleshooting/csrf)] - Also make sure you’re not attempting to initiate or resume the OAuth2 authorization at /oauth2/auth via AJAX—this must be a top-level navigation. [[OAuth2 CSRF notes](https://www.ory.sh/docs/troubleshooting/csrf#ory-oauth2)] 4) Parallel/isolated flows or privacy modes - Running two OAuth2 flows in the same browser, or incognito/tracking-prevention modes, can drop or isolate cookies and produce 403s on the consent GET. Try a clean, single normal window and clear localhost cookies. [[OAuth2 CSRF notes](https://www.ory.sh/docs/troubleshooting/csrf#ory-oauth2); [Hydra CSRF pitfalls](https://www.ory.sh/docs/self-hosted/hydra/debug/csrf)] Focused next steps: - Restart the tunnel and ensure your frontend SDK/basePath points to http://localhost:4000. [[ory tunnel](https://www.ory.sh/docs/cli/ory-tunnel#ory-tunnel); [Local development](https://www.ory.sh/docs/getting-started/local-development#local-development)] - In the failing GET, confirm: - Request URL host is your localhost app. - Request headers include Cookie. - If your app then calls Ory to fetch/accept the consent request, that server-side call must include the incoming Cookie header. [[CSRF troubleshooting](https://www.ory.sh/docs/troubleshooting/csrf)] - Re-check your project’s oauth2.urls.login/consent still point to your local pages. [[Configure Network](https://www.ory.sh/docs/hydra/guides/custom-ui-oauth2#configure-ory-network)] If you can share: - The failing GET URL/host and its Request Headers (Cookie present). - The subsequent server-to-Ory request you make (host used and whether you forward Cookie). We can pinpoint whether it’s a tunnel/origin mismatch or missing Cookie forwarding on the server call.
w
@chilly-king-10285 I just noticed at the point of login there is a login?login_challenge=... GET request that gets blocked but then it redirects to the custom login page I have set up. Is that possibly where the issue lies?
c
A message from kapa.ai
Yes — that’s a strong clue. If the initial GET to your login endpoint with login_challenge is being blocked, the OAuth2 login flow starts in a “broken” state and later consent/token steps can fail with 403, even if the browser eventually lands on your custom login UI. Why it matters - Ory first redirects the browser to your configured Login URL with a login_challenge. Your login endpoint must read that challenge, call getOAuth2LoginRequest, and then either render UI or accept/reject and redirect back. If that initial GET is blocked or mishandled, the flow context isn’t established correctly, which will surface later as “Access credentials are not sufficient.” [[Flow steps](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow#flow-steps); [Login endpoint example](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow#login-endpoint-code-example)] What to check next 1) Confirm your OAuth2 URLs point to your local pages - Verify oauth2.urls.login and oauth2.urls.consent in the project config are your endpoints (e.g., http://localhost:3000/login and /consent) and re-apply if needed. [[Configure Network](https://www.ory.sh/docs/hydra/guides/custom-ui-oauth2#configure-ory-network)] 2) Inspect why the initial login?login_challenge=… GET is blocked - In DevTools, open the blocked request and check: - URL/Host: ensure it’s your localhost login page (not 127.0.0.1 or the project slug). [[Local development](https://www.ory.sh/docs/getting-started/local-development#local-development)] - Request Headers: Cookie present. - Response: who blocked it (browser extension, CSP, mixed content, ad/tracking filter, or a 4xx from the app)? - If you’re using Ory Tunnel, make sure every browser-facing call goes via the tunnel for Ory endpoints (http://localhost:4000) and your app stays on localhost:3000. Avoid mixing the cloud slug anywhere in the browser flow. [[Security model](https://www.ory.sh/docs/security-model); [Local development](https://www.ory.sh/docs/getting-started/local-development#local-development)] 3) Ensure the login endpoint handles the challenge exactly once - On the first GET: - Read login_challenge from the query. - Call getOAuth2LoginRequest(challenge). - If skip is true, accept immediately; otherwise render your login UI. [[Login endpoint example](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow#login-endpoint-code-example)] - After user auth, call acceptOAuth2LoginRequest and redirect to data.redirect_to. [[Login endpoint example](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow#login-endpoint-code-example)] 4) Verify consent follows only after a successful login accept - Only after the login accept redirect should the browser be sent to your consent endpoint with consent_challenge. [[Flow steps](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow#flow-steps); [Consent](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow#consent)] Common reasons the initial login GET is blocked - Origin mismatch (127.0.0.1 vs localhost; tunnel vs slug) causing cookies/CSRF to be considered cross-site. [[Local development](https://www.ory.sh/docs/getting-started/local-development#local-development); [Security model](https://www.ory.sh/docs/security-model)] - An extension or privacy setting intercepting the first redirect containing a long login_challenge parameter. Hydra challenges can be long by design. [[Hydra issue](https://github.com/ory/hydra/issues/3955)] - The app rejecting the GET because it doesn’t forward cookies or doesn’t expect the query parameter, then doing a secondary redirect to your login route without the original challenge. Actionable next step - Re-run the flow and capture: - The blocked login GET (full URL, host, status, and who blocked it). - Your server logs for the login route to confirm whether getOAuth2LoginRequest was called with that challenge. If that first redirect is fixed (allowed and correctly processed), the downstream 403 at consent typically disappears. [[Flow steps](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow#flow-steps); [Login endpoint example](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow#login-endpoint-code-example)]
w
@chilly-king-10285 the UI URLs in the ory network portal all point to my custom auth flow screens but should the oauth2 URLs also point to the custom endpoints? For example the login one? I don't have a custom consent endpoint created, should that still point to my custom URL?
c
A message from kapa.ai
Short answer: Yes for login (if you use custom OAuth2 login), and no for consent only if you rely on Ory’s built‑in Account Experience. If you haven’t built a custom consent page, keep consent on the default. If you do build one, point both login and consent to your endpoints. Details: - UI URLs in Branding control the self-service UIs (login, registration, recovery, settings, verification) for Ory Identities. These are separate from the OAuth2 login/consent endpoints used by Ory OAuth2. [[Configure UI URLs](https://www.ory.sh/docs/kratos/bring-your-own-ui/configure-ory-to-use-your-ui)] - OAuth2 login/consent URLs are configured in the OAuth2 config and are used when running the custom login and consent flow. If you want Ory OAuth2 to use your own pages, set: - urls.login = your custom login page - urls.consent = your custom consent page [[Custom flow](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow); [Configure Network](https://www.ory.sh/docs/hydra/guides/custom-ui-oauth2#configure-ory-network)] Example config: oauth2: urls: login: http://localhost:3000/login consent: http://localhost:3000/consent [[Configure Network](https://www.ory.sh/docs/hydra/guides/custom-ui-oauth2#configure-ory-network)] - If you don’t have a custom consent UI: - Leave urls.consent at the default (Ory Account Experience) and do not point it to a non-existent custom page. Ory will then render its built-in consent screen. [[Custom flow](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow)] - If you do set custom URLs, your endpoints must implement the flow: - Login endpoint: handle login_challenge, call getOAuth2LoginRequest, accept/reject, redirect to redirect_to. [[Login example](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow#login-endpoint-code-example)] - Consent endpoint: handle consent_challenge, call getOAuth2ConsentRequest, optionally skip or show UI, accept/reject, redirect to redirect_to. [[Consent flow](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow#consent)] Summary: - Branding UI URLs → your self-service screens (already set). - OAuth2 urls.login/urls.consent → only point to your custom endpoints if you’ve implemented them; otherwise keep defaults (especially for consent) to use Ory’s built-in pages. [[Configure Network](https://www.ory.sh/docs/hydra/guides/custom-ui-oauth2#configure-ory-network); [Custom flow](https://www.ory.sh/docs/oauth2-oidc/custom-login-consent/flow)]
w
@chilly-king-10285 why would ory network return a 403 from a consent GET request? I'm certain the browser is doing nothing wrong now
c
A message from kapa.ai
A 403 on the consent GET usually means Ory can’t validate the request context (challenge/session/CSRF) for that browser flow. The most common, documented causes are: - Cookies/session context not available to Ory for that request. If the consent request is fetched or accepted server‑side without forwarding the browser’s Cookie header, Ory can’t correlate the challenge and will reject it. This is explicitly required for browser/self‑service flows: when your app calls Ory to fetch a flow/request, include the incoming Cookie header. [[CSRF troubleshooting](https://www.ory.sh/docs/troubleshooting/csrf)] - Running parts of the flow on different origins. Even one step going to a different host (e.g., project slug vs tunnel, localhost vs 127.0.0.1) breaks cookies and causes CSRF/session failures. For local dev, all browser-facing Ory calls must go through the tunnel (http://localhost:4000) and your app should consistently use localhost. [[Local development](https://www.ory.sh/docs/getting-started/local-development#local-development); [Security model](https://www.ory.sh/docs/security-model); [OAuth2 CSRF notes](https://www.ory.sh/docs/troubleshooting/csrf#ory-oauth2)] - Attempting to drive /oauth2/auth or consent via AJAX/XHR instead of top-level browser navigation. Ory explicitly warns that /oauth2/auth must not be called via AJAX; such attempts lead to security errors. [[OAuth2 CSRF notes](https://www.ory.sh/docs/troubleshooting/csrf#ory-oauth2)] - Privacy/incognito/parallel flows. Running multiple OAuth2 flows simultaneously or using privacy modes that discard cookies can result in missing context and a 403. [[OAuth2 CSRF notes](https://www.ory.sh/docs/troubleshooting/csrf#ory-oauth2)] Concrete checks to isolate the cause: - In your consent endpoint, when you call Ory to read the consent request (GET /admin/oauth2/auth/requests/consent with consent_challenge), make sure you forward the exact Cookie header from the user’s browser request. If you don’t, Ory can’t validate the challenge in the context of that session. [[CSRF troubleshooting](https://www.ory.sh/docs/troubleshooting/csrf); [getOAuth2ConsentRequest](https://raw.githubusercontent.com/ory/docs/master/docs/reference/api.json)] - Verify all browser-visible Ory endpoints in the flow are under http://localhost:4000 (tunnel) during local development, and do not mix in https://$PROJECT_SLUG.projects.oryapis.com. Mixing origins breaks cookie-based auth. [[Local development](https://www.ory.sh/docs/getting-started/local-development#local-development); [Security model](https://www.ory.sh/docs/security-model)] - Ensure the consent GET is a normal browser navigation, not XHR/fetch. [[OAuth2 CSRF notes](https://www.ory.sh/docs/troubleshooting/csrf#ory-oauth2)] If you’ve confirmed all of the above and still see 403, please capture for one failing attempt: - The consent GET request URL/host and its Request Headers (especially Cookie and Origin) from DevTools. - Whether your server-side call to fetch the consent request forwarded that Cookie header. These two points typically reveal the missing context that leads to the 403. [[CSRF troubleshooting](https://www.ory.sh/docs/troubleshooting/csrf); [Local development](https://www.ory.sh/docs/getting-started/local-development#local-development)]