fix(jwt): /jwt/refresh 401s for apps with custom user_model (v4.5.3)#92
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
JwtEndpoint::refresh()hardcodednew ModelObject(KyteUser)to reload the principal. Apps with an app-scopeduser_modelstore 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 fromrefresh_token.app_id, callsresolveAuthContext()for the correctuser_model+ DB context, and retrieves the principal from that model — mirroringlogin().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
Target v4.5.3.
🤖 Generated with Claude Code