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.