Skip to content

fix(jwt): /jwt/refresh 401s for apps with custom user_model (v4.5.3)#92

Merged
kennethphough merged 2 commits into
masterfrom
fix/jwt-refresh-custom-user-model
May 28, 2026
Merged

fix(jwt): /jwt/refresh 401s for apps with custom user_model (v4.5.3)#92
kennethphough merged 2 commits into
masterfrom
fix/jwt-refresh-custom-user-model

Conversation

@kennethphough

Copy link
Copy Markdown
Member

Summary

JwtEndpoint::refresh() hardcoded new ModelObject(KyteUser) to reload the principal. Apps with an app-scoped user_model store the user in the app DB → KyteUser->retrieve('id', $userId) fails → 401 "Refresh token principal not found" on the first refresh (~15 min after login). login() was fixed for this in v4.4.3–4.4.5; refresh() was missed.

Fix: refresh() resolves the app from refresh_token.app_id, calls resolveAuthContext() for the correct user_model + DB context, and retrieves the principal from that model — mirroring login().

Also backfills migrations/4.5.3_mcp_tokens.sql (Phase 2 MCP token table).

Impact

Critical for any JWT deployment on apps with custom user models. ETOM doctor app (post HMAC→JWT migration) was hitting this on every session.

Test plan

  • Deploy to ETOM; login on doctor app, let access token expire (~15 min) → refresh succeeds (was 401)
  • Default KyteUser path unchanged

Target v4.5.3.

🤖 Generated with Claude Code

kennethphough and others added 2 commits May 28, 2026 05:33
JwtEndpoint::refresh() reloaded the principal with a hardcoded
new ModelObject(KyteUser). Apps authenticating against an app-scoped
user_model store the user in the app DB, so KyteUser->retrieve('id',
$userId) finds nothing -> 401 "Refresh token principal not found."

Impact: every JWT session on such an app dies on its FIRST refresh —
~15 min (access TTL) after login — presenting as "JWT fails after a few
minutes of inactivity." login() already handled custom user models
(v4.4.3-4.4.5 via resolveAuthContext); refresh() was never given the
same treatment, so it was latent until an app with a custom user model
was migrated HMAC->JWT (ETOM doctor app).

Fix: refresh() resolves the app from refresh_token.app_id, calls
resolveAuthContext($appIdentifier) to load the app models + DB context
and get the correct user_model, and retrieves the principal from that
model — mirroring login(). Default KyteUser path unchanged.

Also adds migrations/4.5.3_mcp_tokens.sql (Phase 2 MCP token table
backfill; CREATE TABLE IF NOT EXISTS).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…set)

The actual code fix: refresh() resolves the app from refresh_token.app_id,
calls resolveAuthContext() to load the app models + DB context and get the
correct user_model, and retrieves the principal from that model — mirroring
login(). Fixes 401 "Refresh token principal not found" on first refresh for
apps with a custom user_model. Plus the v4.5.3 CHANGELOG entry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@kennethphough
kennethphough merged commit 0ab20be into master May 28, 2026
4 checks passed
@kennethphough
kennethphough deleted the fix/jwt-refresh-custom-user-model branch May 28, 2026 10:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant