fix(auth): self-heal + auto re-login on refresh failure

Three-layer fix for the "session keeps dying with invalid_grant" pain:

1. Authentik provider config (separate change via API):
   access_token_validity bumped 5min → 60min so refreshes are 12x less
   frequent. Refresh-token rotation collisions only happen during the
   refresh, so a longer access_token TTL means far fewer windows.

2. jwt callback (auth-options.ts): when Authentik responds 400
   invalid_grant on refresh, the stored refresh_token is permanently
   dead — Authentik rotated it on a previous successful refresh and the
   old value can't be reused. Clear it (and the access_token) from the
   JWT so subsequent session checks see a clean RefreshAccessTokenError
   instead of looping into the same 400 every 5 minutes.

3. SessionErrorWatcher (new client component, mounted in providers
   tree): listens for session.error === "RefreshAccessTokenError" and
   calls signIn("authentik") with the current URL as callback. The
   cleared JWT cookie means Authentik runs a full OIDC flow, mints fresh
   tokens, and the user lands back where they were. No manual logout.

Net effect: refresh storms become invisible — at worst there's a single
redirect to Authentik (silent if the user is still SSO'd) instead of a
broken session that 401s every API call.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude VM
2026-05-19 16:23:50 +03:00
parent 1786c254d5
commit 8ff67d19fb
3 changed files with 51 additions and 1 deletions
+2
View File
@@ -6,6 +6,7 @@ import { StorageProvider } from '@/core/storage';
import { FeatureFlagProvider } from '@/core/feature-flags';
import { AuthProvider } from '@/core/auth';
import { VersionWatcher } from '@/core/version/version-watcher';
import { SessionErrorWatcher } from '@/core/auth/session-error-watcher';
import { DEFAULT_FLAGS } from '@/config/flags';
// Ensure module registry is populated
@@ -24,6 +25,7 @@ export function Providers({ children }: ProvidersProps) {
<AuthProvider>
{children}
<VersionWatcher />
<SessionErrorWatcher />
</AuthProvider>
</FeatureFlagProvider>
</StorageProvider>