gitshark

Clone repository

git clone https://gitshark.ha1nz.de/git/workaround/Gitshark.git
git clone ssh://git@gitshark.ha1nz.de:22/workaround/Gitshark.git

← Issues

Auth: silently refresh expired sessions and return users to the page they were on #6

Problem

The SSO (kanidm) issues short-lived tokens, so the login session expires quickly. Two user-visible pains:

  1. Lost location. After re-authenticating, the user lands on the dashboard instead of the page they were on.
    • Before: https://gitshark.ha1nz.de/repos/workaround/Gitshark/issues
    • After: https://gitshark.ha1nz.de/
  2. Visible re-auth. Every ID-token expiry forces a round-trip through the IdP (or a manual "Log in" click). This should be invisible.

Research / current state

  • quarkus.oidc.application-type=web-app; quarkus.oidc.token.refresh-expired is not set (default false). When the ID token expires, Quarkus invalidates the session cookie instead of using the refresh token. Default quarkus.oidc.authentication.session-age-extension is only 5 minutes.
  • Most pages are anonymous-permitted (quarkus.http.auth.permission.authenticated.paths only protects /login, /settings/*, /repos/new, /onboarding, /following*). So after expiry the user silently becomes logged out on public pages, clicks Log in (layout.html:28/login), and HomeResource.login() hardcodes Response.seeOther("/") (HomeResource.java:97-104) — that is where the original page is lost.
  • The UI is fully server-rendered (no htmx/fetch/XHR in any template), so a server-side silent refresh is sufficient; no JavaScript refresh machinery and no java-script-auto-redirect handling needed.
  • kanidm facts:
    • Access/ID tokens are short-lived by design (~15 min in the refresh-token design doc); extending them meaningfully on the SSO side is not the intended path.
    • OAuth2 refresh token expiry is currently hard-coded to 16 h (kanidm#3040 tracks making it configurable; offline_access is discussed in kanidm#4034). So the practical ceiling for a fully silent session is ~16 h; beyond that the code flow runs again (still silent while the kanidm SSO session is alive).
    • No end_session_endpoint (already documented in application.properties), irrelevant here.
    • kanidm validates redirect_uri strictly, which matters for Part A below.

Proposal

Part A — return to the original page after login

  • Point the header Log in link at /login?redirect=<current path + query> (URL-encoded), and make HomeResource.login() redirect there instead of the hardcoded /.
    • Open-redirect guard: accept only local paths — must start with /, must not start with // or contain a scheme; otherwise fall back to /.
  • For challenges triggered on protected paths (e.g. deep link to /settings/... while logged out), rely on Quarkus restoring the original request URI. If a fixed callback is registered in kanidm (strict redirect_uri matching), set:
    quarkus.oidc.authentication.redirect-path=/login
    quarkus.oidc.authentication.restore-path-after-redirect=true
    
    With restore-path-after-redirect=true Quarkus sends the user back to the originally requested URL after the code flow; only direct visits to /login hit the redirect-param logic.

Part B — silent (invisible) session refresh

quarkus.oidc.token.refresh-expired=true
# keep the session cookie alive past ID-token expiry so the refresh can run;
# align with kanidm's 16 h refresh-token lifetime
quarkus.oidc.authentication.session-age-extension=PT12H
# refresh proactively shortly before expiry instead of on the first failing request
quarkus.oidc.token.refresh-token-time-skew=PT60S
  • The default token-state-manager strategy (keep-all-tokens) already stores the refresh token in the encrypted session cookie, and token-state-manager.encryption-secret is already set for multi-pod deployments — no change needed there.
  • Because refresh happens inline during request processing (no redirect), it is invisible to the user and form POSTs survive an expired-but-refreshable token.
  • If the refresh itself fails (refresh token older than 16 h / revoked), Quarkus falls back to the code-flow redirect; while the kanidm SSO session is still valid that is also silent, otherwise the user sees the login screen once and Part A returns them to their page.
  • Verify kanidm actually issues a refresh token to this client (scopes/basic client config); if not, that is the first fix.

Part C — SSO side (kanidm)

  • Extending access-token lifetime in kanidm is not really available/intended; the refresh-token path above is the supported mechanism. Document the ~16 h silent-session ceiling and revisit when kanidm#3040 lands.

Acceptance criteria

  • [ ] Session expiring while browsing does not visibly log the user out; the next request refreshes tokens silently (test with a short-lived dev token)
  • [ ] After a full re-login, the user lands on the exact page (path + query) they were on
  • [ ] redirect parameter on /login rejects external/absolute URLs (open-redirect test)
  • [ ] Form POST submitted with an expired-but-refreshable session succeeds without data loss
  • [ ] Failing tests first, green after implementation (repo convention)
  • [ ] Docs updated in the same PR: docs/admins/getting-started.md (new OIDC properties incl. any new env vars), docs/users/ if the login behavior description changes

References

  • Quarkus OIDC code-flow session management (refresh-expired, session-age-extension): https://quarkus.io/guides/security-oidc-code-flow-authentication
  • restore-path-after-redirect / redirect-path: https://quarkus.io/guides/security-oidc-expanded-configuration
  • kanidm refresh-token design (short access tokens, 16 h refresh expiry): https://kanidm.github.io/kanidm/stable/developers/designs/oauth2_refresh_tokens.html
  • kanidm configurable refresh expiry: https://github.com/kanidm/kanidm/issues/3040
  • kanidm offline_access: https://github.com/kanidm/kanidm/issues/4034

Keyboard shortcuts

?Show this help
g hGo home
EscClose dialog