fix(auth): silent session renewal + 401 auto-recovery
ci / changes (push) Successful in 4s
ci / tc_booking (push) Has been skipped
ci / tc_operator (push) Successful in 20s
ci / tc_website (push) Has been skipped
ci / tc_platform_api (push) Has been skipped
ci / test_platform_api (push) Has been skipped
ci / build_booking (push) Has been skipped
ci / tc_portal (push) Successful in 26s
ci / build_platform_api (push) Has been skipped
ci / build_operator (push) Successful in 31s
ci / build_portal (push) Successful in 39s
ci / deploy (push) Successful in 41s

Idle sessions died and left a broken page: when the access token expired,
nuxt-oidc-auth's automatic refresh had no refresh token to use — neither
Authentik provider carried the offline_access scope mapping (and the
operator never requested the scope), so the module cleared the session
and every /api call 401'd until a manual F5 happened to re-auth through
Authentik's still-alive SSO session.

Fix 1: offline_access end to end — scope mapping attached to both live
providers (and blueprints, prod + dev), operator now requests the scope.
Sessions renew server-side for up to 30 days of activity (Redis store +
pinned token key from earlier make the refresh tokens durable).

Fix 2: client plugin in both apps — a 401 from /api sends the browser
through /auth/oidc/login instead of leaving dead buttons; invisible when
Authentik's session is alive, a clean sign-in screen when it isn't.
Loop-guarded. Full sign-out behavior unchanged.
This commit is contained in:
Ronni Baslund
2026-06-11 09:21:15 +02:00
parent 33d6c23733
commit 901cc69ba3
6 changed files with 54 additions and 1 deletions
@@ -94,6 +94,11 @@ entries:
authentik_providers_oauth2.scopemapping,
[managed, "goauthentik.io/providers/oauth2/scope-profile"],
]
# offline_access -> refresh tokens for the apps' silent session renewal.
- !Find [
authentik_providers_oauth2.scopemapping,
[managed, "goauthentik.io/providers/oauth2/scope-offline_access"],
]
sub_mode: hashed_user_id
issuer_mode: per_provider
@@ -42,6 +42,9 @@ entries:
- !Find [authentik_providers_oauth2.scopemapping, [managed, "goauthentik.io/providers/oauth2/scope-openid"]]
- !Find [authentik_providers_oauth2.scopemapping, [managed, "goauthentik.io/providers/oauth2/scope-email"]]
- !Find [authentik_providers_oauth2.scopemapping, [managed, "goauthentik.io/providers/oauth2/scope-profile"]]
# offline_access -> Authentik issues refresh tokens, enabling the
# apps' silent session renewal (idle sessions died without it).
- !Find [authentik_providers_oauth2.scopemapping, [managed, "goauthentik.io/providers/oauth2/scope-offline_access"]]
sub_mode: hashed_user_id
issuer_mode: per_provider
# Authentik 2026.5+ enforces a per-provider grant_types allowlist; an empty
@@ -37,6 +37,9 @@ entries:
- !Find [authentik_providers_oauth2.scopemapping, [managed, "goauthentik.io/providers/oauth2/scope-openid"]]
- !Find [authentik_providers_oauth2.scopemapping, [managed, "goauthentik.io/providers/oauth2/scope-email"]]
- !Find [authentik_providers_oauth2.scopemapping, [managed, "goauthentik.io/providers/oauth2/scope-profile"]]
# offline_access -> Authentik issues refresh tokens, enabling the
# apps' silent session renewal (idle sessions died without it).
- !Find [authentik_providers_oauth2.scopemapping, [managed, "goauthentik.io/providers/oauth2/scope-offline_access"]]
sub_mode: hashed_user_id
issuer_mode: per_provider
# Authentik 2026.5+ enforces a per-provider grant_types allowlist; an empty