Skip to content

feat(auth): return to DevLake after OIDC logout and show a login error page - #9351

Merged
klesh merged 1 commit into
apache:mainfrom
DoDiODev:pr/oidc-logout-error-page
Nov 9, 2026
Merged

klesh merged 1 commit into
apache:mainfrom
DoDiODev:pr/oidc-logout-error-page

Conversation

@DoDiODev

@DoDiODev DoDiODev commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

⚠️ Pre Checklist

Please complete ALL items in this checklist, and remove before submitting

  • I have read through the Contributing Documentation.
  • I have added relevant tests.
  • I have added relevant documentation. (env.example)
  • I will add labels to the PR, such as pr-type/bug-fix, pr-type/feature-development, etc.
    (I can't set labels myself; suggested: pr-type/feature-development, component/config-ui)

Summary

Two gaps in the OIDC login added in #8854:

  1. Logout does not return to DevLake. With OIDC_LOGOUT_REDIRECT=true,
    /auth/logout returns the bare end_session_endpoint. Keycloak 18+ then
    shows "Do you want to log out?" and leaves the user on a Keycloak page.
  2. Login errors show raw JSON. For example, a user whose email is not in
    OIDC_ALLOW_DOMAINS/OIDC_ALLOW_EMAILS gets 403 {"message":"user is not allowed"} in the browser. Their IdP session stays alive, so clicking
    "Sign in" again ends in the same error without a way out.

RP-initiated logout

  • The logoutUrl now carries client_id, post_logout_redirect_uri and
    id_token_hint (OpenID Connect RP-Initiated Logout 1.0). Keycloak skips the
    confirmation prompt and sends the user back to DevLake.
  • New optional OIDC_<NAME>_POST_LOGOUT_REDIRECT_URL. Default: origin of
    OIDC_<NAME>_REDIRECT_URL + /.
  • The raw id_token is kept in a new auth_sessions.id_token column (migration
    20261107000001). It is only stored when OIDC_LOGOUT_REDIRECT=true,
    never exposed via JSON, and cleared when the session is revoked.

Login error page

  • Browser-facing failures in /auth/login and /auth/callback now redirect
    to a new config-ui route /auth-error?code=<code> instead of returning
    JSON. Codes: forbidden, expired (state/cookie problems), idp_error
    (IdP returned an error, discovery/exchange/verification failed),
    server_error. Details only go to the server log; rejected logins are logged
    as a warning with sub and email.
  • The page uses the existing TipLayout (same look as the other error pages).
  • For forbidden:
    • Request access — shown only if the new optional OIDC_ACCESS_REQUEST_URL
      is set (exposed via /auth/methods as accessRequestUrl).
    • Sign in with a different account — calls the new public
      GET /auth/switch-account, which ends the IdP session and returns to
      DevLake. The id_token for the hint is passed in a short-lived (10 min),
      encrypted, HttpOnly cookie set during the rejected callback, since no
      DevLake session exists for that user.
  • Other codes show Back to sign in.

Does this close any open issues?

N/A (follow-up to #8854)

Screenshots

Rejected user (forbidden, with OIDC_ACCESS_REQUEST_URL set):

auth-error-forbidden

Expired or tampered state (expired):

auth-error-expired

Other Information

Upgrade note: deployments that already run with OIDC_LOGOUT_REDIRECT=true
now send post_logout_redirect_uri by default. It must be registered at the
IdP (Keycloak: Valid post logout redirect URIs; Entra ID: add it as a
redirect URI of the app registration). Otherwise the IdP rejects the logout request.

Known limits

  • After a successful login the discovery document is cached. If the IdP
    becomes unreachable later, /auth/login still redirects to it and the
    browser shows its own error; DevLake cannot catch that.
  • The hint cookie for "Sign in with a different account" can exceed 4 KB for
    very large id_tokens (many group claims). The browser then drops it and the
    logout falls back to no id_token_hint, so the IdP shows its confirmation
    prompt.
  • Redirect targets are root-relative (/auth-error, /), like the existing
    post-login redirect.

Testing

  • Unit tests: go test ./server/api/... ./helpers/oidchelper/ ./core/models/migrationscripts/...
    (backend) and npx vitest run (config-ui, new auth-error.test.tsx) — passed.
    golangci-lint run on the changed packages and make migration-script-lint
    — passed.
  • Manual test against Keycloak 26.8.1 (realm import, users with allowed and
    rejected domains), PostgreSQL 18.6, backend + Vite dev proxy: login,
    logout without prompt and return to DevLake, IdP session ended, forbidden
    page with both buttons, switch account, expired/idp_error/server_error
    pages, OIDC_LOGOUT_REDIRECT=false (no id_token stored) — all as expected.
  • Fork CI on the same change (golangci-lint, unit-test, e2e on MySQL and
    PostgreSQL, config-ui, license header, Grafana check):
    https://github.com/DoDiODev/devlake/actions/runs/42589488952 (16/16 jobs green)

…r page

RP-initiated logout:
- /auth/logout now sends client_id, post_logout_redirect_uri and
  id_token_hint to the IdP's end_session_endpoint, so IdPs such as
  Keycloak >= 18 skip the logout prompt and return the user to DevLake.
- The id_token is stored in auth_sessions (new column, migration
  20261107000001) only when OIDC_LOGOUT_REDIRECT=true, and is cleared
  on revocation.
- New OIDC_<NAME>_POST_LOGOUT_REDIRECT_URL; defaults to the origin of
  OIDC_<NAME>_REDIRECT_URL plus "/".

Login error page:
- Browser-facing failures in /auth/login and /auth/callback redirect to
  the new config-ui route /auth-error?code=<code> instead of returning
  raw JSON. Codes: forbidden, expired, idp_error, server_error. Details
  are only logged server-side.
- For rejected users (OIDC_ALLOW_EMAILS/OIDC_ALLOW_DOMAINS) the page
  offers "Request access" (new OIDC_ACCESS_REQUEST_URL, exposed via
  /auth/methods) and "Sign in with a different account", which calls
  the new /auth/switch-account endpoint to end the IdP session using
  an encrypted, short-lived id_token hint cookie.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

@klesh klesh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM
Thanks for your contribution.

@klesh
klesh merged commit 103498e into apache:main Nov 9, 2026
11 checks passed
@DoDiODev
DoDiODev deleted the pr/oidc-logout-error-page branch November 9, 2026 12:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants