usememos/memos · error
Unauthenticated
Unauthenticated
Error message
refresh token revoked
What it means
During refresh-token authentication, the JWT signature and claims validated, but no row with the token's TokenID exists in the database for that user — meaning the token was revoked (user logged out, sessions invalidated, or admin revoked sessions) after it was issued. Returned as Unauthenticated.
Source
Thrown at server/auth/authenticator.go:78
// AuthenticateByRefreshToken validates a refresh token against the database.
func (a *Authenticator) AuthenticateByRefreshToken(ctx context.Context, refreshToken string) (*store.User, string, error) {
claims, err := ParseRefreshToken(refreshToken, []byte(a.secret))
if err != nil {
return nil, "", errors.Wrap(err, "invalid refresh token")
}
userID, err := util.ConvertStringToInt32(claims.Subject)
if err != nil {
return nil, "", errors.Wrap(err, "invalid user ID in token")
}
// Check token exists in database (revocation check)
token, err := a.store.GetUserRefreshTokenByID(ctx, userID, claims.TokenID)
if err != nil {
return nil, "", errors.Wrap(err, "failed to get refresh token")
}
if token == nil {
return nil, "", errors.New("refresh token revoked")
}
// Check token not expired
if token.ExpiresAt != nil && token.ExpiresAt.AsTime().Before(time.Now()) {
return nil, "", errors.New("refresh token expired")
}
// Get user
user, err := a.store.GetUser(ctx, &store.FindUser{ID: &userID})
if err != nil {
return nil, "", errors.Wrap(err, "failed to get user")
}
if user == nil {
return nil, "", errors.New("user not found")
}
if user.RowStatus == store.Archived {
return nil, "", errors.New("user is archived")
}View on GitHub (pinned to 14d757ce1f)
Solutions
- Discard the stored refresh token and re-authenticate (redirect to login)
- On the client, clear token state on receiving Unauthenticated from the refresh endpoint instead of retrying
- If you administer the instance, confirm whether a session-wide revocation or DB restore explains it
Example fix
// before onRefreshError: retryRefreshWithSameToken() // after onUnauthenticated: clearTokens(); redirectToLogin()
Defensive patterns
Strategy: try-catch
Validate before calling
// Client-side: before refreshing, you cannot query revocation; instead ensure you
// clear tokens atomically on logout in all tabs:
// auth-state.ts
broadcastChannel.postMessage({ type: "logout" });
localStorage.removeItem("refreshToken"); Try / catch
// Treat Unauthenticated from refresh as terminal: purge credentials, do not retry
if _, _, err := authn.AuthenticateByRefreshToken(ctx, token); err != nil {
if status.Code(err) == codes.Unauthenticated {
session.Clear() // remove stored tokens
return http.Redirect(w, r, "/login", http.StatusSeeOther)
}
return err
} Prevention
- Never retry a refresh after an Unauthenticated response
- Clear tokens on logout across all tabs (BroadcastChannel pattern)
- Handle revocation as expected behavior after admin session resets, not as a bug
When it happens
Trigger: Client retries a refresh with a token whose row was deleted: after logout on another device, after 'sign out all sessions', after an admin deactivates sessions, or after a DB restore/rotation that dropped the refresh-token table rows.
Common situations: Multiple tabs/devices where one logged out and a stale tab attempts refresh; iOS app resuming with an old token; database restored from backup without the refresh-token rows; race between logout and an in-flight token refresh.
Related errors
- Unauthenticated
- Failed to link account. Please sign in to Memos again and re
- missing access token from authorization response
- deployment configuration disables password authentication fo
AI-assisted analysis of usememos/memos@14d757ce1f (2026-08-15).
Data as JSON: /api/errors/2f9443355c4dc2d3.
Report an issue: GitHub.