fix(auth): keep the permission list out of access tokens (11.5 KB → <2 KB)

Merged
EV
11d ago
+163-1

Why

Every request carries the access token. It embedded the name of every enabled permission of the user's role — 380 for a super admin after the platform-extensions merge (#10254) → an 11.5 KB token (11,722 B for every permission in PermissionsEnum), growing ~30 B with each new permission. On the way:

  • the web image's nginx refuses header lines over 8 KB → ./assets/i18n/en.json answered 400 for signed-in users (worked around in #10634 by not sending the token to static assets);
  • Node caps all request headers together at 16 KB (no --max-http-header-size override in the repo) → ~3 KB of headroom for every API call, shrinking with every plugin.

Nobody reads the claim

  • API: JwtStrategy.validate() → RoleAuthorizationService.attachAuthorizationState() pins the role's current permissions from the database on each request; RequestContext.hasPermission(s) reads those ("never the permissions claim"). Field visibility and GraphQL subscriptions read the request user / a live evaluator, never the token.
  • Web: permissions come from /user/me (role.rolePermissions).
  • Desktop timer/agent, desktop-ui-lib, MCP server: decode exp / employeeId / tenantId only.
  • Ever Teams web (jwt-utils.ts, ever-id/session.ts) and mobile (tokenHandler.ts): the payload type lists permissions? but nothing reads it — only exp and ids.

Change

  • getJwtAccessToken() leaves permissions out of new access tokens.
  • Gated, not deleted: JWT_ACCESS_TOKEN_INCLUDE_PERMISSIONS=true puts it back for an integration that still decodes it (access-token-claims.ts, documented in .env.sample). Tokens issued before the deploy keep working (the API never read the claim).

Evidence

  • New auth.service.access-token-size.spec.ts drives the real getJwtAccessToken() and signs the payload with HS256 as the scoped token service does. Red on develop 77606f6199: 2 of 4 fail — claim present, Bearer header 11,722 bytes. Green: 4/4 (no claim, < 2 KB, id/tenant/org/employee/role/exp kept, flag restores the list).
  • auth + request-context + role + access-token + token suites: 460/460; tsc -p packages/core/tsconfig.lib.json --noEmit 0 errors; all 35 static gates pass; cspell clean.
  • Boot proof (fresh SQLite, demo seed, login, permission-guarded REST + GraphQL, e2e): running — results will be posted here.

Tracked in the homelab handover HANDOVER-2026-10-15-gauzy-followups.md (Q2).

🤖 Generated with Claude Code


Summary by cubic

Shrinks access tokens by leaving the role's permission list out of new tokens — a super admin's token drops from 11.5 KB to under 2 KB.

  • Each token previously embedded every enabled permission (380 for a super admin), blowing past nginx's 8 KB header-line cap and eating into Node's 16 KB total request-header budget on every API call.
  • Nothing reads the claim: requests are authorized against the role's current permissions from the database, and clients load them from /user/me.
  • Set JWT_ACCESS_TOKEN_INCLUDE_PERMISSIONS=true to restore the list for an integration that still decodes it (documented in .env.sample); already-issued tokens keep working.
  • Specs drive the real getJwtAccessToken(), sign the payload with the exact claims CreateTokenHandler adds, and verify the token stays under 2 KB while keeping the id, tenantId, organizationId, employeeId, role, and exp claims.