Olympus Docs
TroubleshootingAuth issues

Login loops

Users redirected back to login immediately after submitting credentials

A login loop is when the user submits valid credentials, gets redirected to the app's callback URL, and is then immediately redirected back to login, over and over.

Symptoms

  • Browser address bar cycles through:
    /your-app/page → /your-app/login → /hydra/oauth2/auth → /hera/login → /hydra/oauth2/auth (consent) → /your-app/callback → /your-app/page → /your-app/login → ...
  • No error displayed; the user just keeps seeing the login form.
  • Network tab shows successful logins (Kratos returns 200) but the session doesn't persist.

The Kratos session cookie is scoped to a specific domain. If your app, Hera, and Hydra are on different domains (e.g. app.example.com, auth.example.com, kratos.internal), the cookie set on the auth.example.com domain isn't visible to app.example.com.

Check: in the browser DevTools → Application → Cookies, look at the ory_kratos_session cookie:

  • What's its Domain value?
  • What's the current page domain?
  • Does the page domain match (or end with) the cookie domain?

Fix: Set Kratos's cookie domain to a parent domain that covers all subdomains:

# kratos.yml
session:
  cookie:
    domain: example.com   # covers app.example.com and auth.example.com
    path: /
    same_site: Lax

Restart Kratos (or trigger a config reload). Sessions are invalidated by the change.

If same_site is Strict, the cookie is not sent on cross-origin redirects. Hydra's redirect to your app counts as cross-origin even if both are on the same parent domain.

Fix: Use same_site: Lax (the default for Olympus). Strict is for security-paranoid contexts where users don't follow links across domains, incompatible with OAuth2 flows.

Third cause: HTTPS / mixed protocol

Cookie set with Secure over HTTPS, won't be sent on HTTP. If your local dev redirects between HTTPS Olympus and HTTP app, the cookie disappears.

Fix: Use HTTPS everywhere in dev (Caddy gives you a self-signed cert for localhost.olympus.app, point your dev domain at this).

Fourth cause: clock skew

If the server's clock is off, the JWT iat/exp claims may be in the future or past, causing immediate rejection.

Check:

ssh prod date -u
date -u
# Difference should be < 5 seconds

Fix: Install ntp / systemd-timesyncd on the VPS.

Fifth cause: Hydra issuer URL mismatch

If your app validates the iss claim of the ID token against a URL, and Hydra issues with a slightly different URL (trailing slash, http vs https), the JWT verification rejects.

Check: decode your ID token; compare iss to what your app expects:

echo "$ID_TOKEN" | cut -d. -f2 | base64 -d | jq .iss

Fix:

  • Set Hydra's urls.self.issuer to exactly what your app expects.
  • Verify no trailing slash mismatch.

Sixth cause: cookies blocked

Browser privacy settings (third-party cookie blocking, Firefox ETP, Safari ITP) can block cookies set on the auth domain when the user is on the app domain.

Test: open the flow in an incognito window with strict tracking protection disabled. If it works there but not in the user's primary browser, this is the cause.

Fix:

  • Move auth and app to the same registered domain (so cookies are first-party).
  • Or use a backend session and skip the cookie entirely (your app validates the OAuth2 access token and maintains its own session).

Diagnostic procedure

  1. Capture a HAR (browser DevTools → Network → Save all as HAR with content).
  2. Look for the Set-Cookie headers on the Kratos response. Note the Domain and SameSite and Secure attributes.
  3. Look for the request that doesn't include the cookie. Compare its domain.
  4. If the cookie is set on auth.example.com (Domain=auth.example.com, no leading dot), only auth.example.com sees it. Set Domain=example.com (with leading dot in older RFCs) to make it visible to subdomains.

Where it isn't

  • It's almost never an Olympus bug. Login loops are virtually always cookie / domain / TLS configuration.
  • It's not a CSRF issue, CSRF failures return 400 with a specific error, not a redirect.

On this page