Skip to content

Resigned/disabled accounts keep working sessions: nothing revokes a live token #8071

Description

@renemadsen

Follow-up to the "refuse login for resigned accounts" work (design: eform-backendconfiguration-plugin/docs/superpowers/specs/2026-09-17-resigned-account-login-refusal-design.md).

That change stops a resigned worker starting a session. It does not end one they already have.

Current behaviour

  • Tokens are valid for 24h — AuthService.GenerateToken, AuthService.cs:256 (DateTime.Now.AddHours(24)).
  • GET /api/auth/token/refresh (AuthService.RefreshToken, AuthService.cs:173-204) mints a fresh 24h token from any still-valid token, indefinitely. The Angular client calls it automatically on any 403 (http-error.interceptor.ts:78-113).
  • Nothing re-reads the user row after login. The only per-request hook, ClaimsTransformer.TransformAsync (ClaimsTransformer.cs:49-100), never rejects — on a cache miss or stale updated_at it sets an advisory response header, and the client-side handler for that header is commented out (user-claims.interceptor.ts:22-28).
  • LogOut (AuthService.cs:297-311) only evicts an in-memory cache entry; the JWT stays valid afterwards.

So a worker resigned at 09:00 keeps full access until they stop using the app. The same is true for any account an admin wants to cut off immediately.

Why it needs its own design

ClaimsTransformer is the natural rejection point — it already runs on every authenticated request and already has the user id. But making it reject means either a per-request user lookup or a revocation cache, on every authenticated request, so it touches:

  • performance — an extra DB round trip per request unless cached;
  • multi-replica correctness — the existing AuthCacheService is a per-process IMemoryCache (AuthCacheService.cs:30-85), so a token minted on one pod has no cache entry on another;
  • the gRPC surface — the host calls app.UseAuthentication() but there is no app.UseAuthorization() and no fallback policy, and no TimePlanning/BackendConfiguration gRPC service carries [Authorize].

Refusing the two refresh paths (AuthService.RefreshToken, TimePlanningAuthGrpcService.RefreshToken:202-253) is part of the login change and caps exposure at the remaining token lifetime — this issue is about closing the rest of that window.

Related, not covered here

  • The four [AllowAnonymous] kiosk endpoints authenticate with a RegistrationDevices.Token: plaintext, generated with System.Random, no expiry and no revocation field (RegistrationDevice.cs:35-56).
  • AccountService.ForgotPassword (AccountService.cs:284-291) returns User with <email> not found, an account-enumeration leak of the same family as the login messages.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions