mem0ai/mem0 · error · HTTPException
Refresh token is no longer valid.
Error message
Refresh token is no longer valid.
What it means
Raised by POST /auth/refresh when the decoded refresh token carries no `jti` (JWT ID) claim. Refresh tokens are one-shot: each is minted with a unique jti that is tracked and consumed on use, so a token without a jti cannot participate in rotation. This typically means the token was created by a code path that skips jti assignment, or the payload was stripped/mangled.
Source
Thrown at server/routers/auth.py:153
user.last_login_at = datetime.now(timezone.utc)
db.commit()
return TokenResponse(
access_token=create_access_token(str(user.id), user.role),
refresh_token=create_refresh_token(str(user.id), db),
)
@router.post("/refresh", response_model=TokenResponse)
@limiter.limit("20/minute")
def refresh(request: Request, body: RefreshRequest, db: Session = Depends(get_db)):
payload = decode_token(body.refresh_token)
if payload.get("type") != "refresh":
raise HTTPException(status_code=401, detail="Invalid token type.")
jti = payload.get("jti")
if not jti:
raise HTTPException(status_code=401, detail="Refresh token is no longer valid.")
user = db.get(User, payload["sub"])
if user is None:
raise HTTPException(status_code=401, detail="User not found.")
consume_refresh_jti(jti, db)
return TokenResponse(
access_token=create_access_token(str(user.id), user.role),
refresh_token=create_refresh_token(str(user.id), db),
)
@router.get("/me", response_model=UserResponse)
def me(user: User = Depends(require_auth)):
return user
View on GitHub (pinned to 001c235229)
Solutions
- Have the user log in again to obtain a refresh token minted by the current create_refresh_token (which embeds a jti)
- Audit any code or fixture that constructs refresh tokens and ensure it includes a unique jti claim
- If upgrading the auth server, plan for old refresh tokens: let them fail here and require re-login, or issue a migration window
Example fix
// before (fixture/helper omitting jti)
def make_refresh_token(user_id):
return jwt.encode({"sub": user_id, "type": "refresh"}, SECRET, algorithm="HS256")
// after
import uuid
def make_refresh_token(user_id):
return jwt.encode({"sub": user_id, "type": "refresh", "jti": str(uuid.uuid4())}, SECRET, algorithm="HS256") Defensive patterns
Strategy: validation
Validate before calling
function hasJti(token) {
const payload = JSON.parse(atob(token.split(".")[1].replace(/-/g, "+").replace(/_/g, "/")));
return Boolean(payload.jti);
}
if (!hasJti(stored.refreshToken)) { clearTokens(); goToLogin(); } Type guard
function isRotatableRefreshToken(token: string): boolean {
const payload = JSON.parse(atob(token.split(".")[1].replace(/-/g, "+").replace(/_/g, "/")));
return payload.type === "refresh" && typeof payload.jti === "string" && payload.jti.length > 0;
} Try / catch
catch (e) { if (e.status === 401 && /no longer valid/.test(e.detail)) { clearTokens(); goToLogin(); /* token cannot be reused */ } throw e; } Prevention
- Treat this 401 as terminal: do not retry with the same token, clear session state and re-authenticate
- Rotate stored tokens atomically: persist the new refresh_token from each TokenResponse before the next request
- When building tokens in tests, always include a unique jti claim
When it happens
Trigger: Calling /auth/refresh with a refresh token minted without a jti (older issuer version or custom create_refresh_token path); decoding/re-encoding the token client-side and dropping claims; sending a refresh token signed with the right secret but constructed manually.
Common situations: Server upgrade changed create_refresh_token to include jti and rotation, and old clients still hold pre-rotation tokens; a test fixture builds refresh tokens with a helper that omits jti; multiple services share the JWT secret but only one adds jti.
Related errors
- Invalid token type.
- Authentication failed. Your API key may be invalid or expire
- Resource not found: ${path}
- Bad request to ${path}: ${detail}
- Either memoryId or --all is required
AI-assisted analysis of mem0ai/mem0@001c235229 (2026-08-15).
Data as JSON: /api/errors/290de38083ec8d90.
Report an issue: GitHub.