Mintplex-Labs/anything-llm · warning
Forbidden
Error message
Forbidden
What it means
This 403 'Forbidden' is a deliberate status, not an exception, sent by GET /system/check-token (server/endpoints/system.js:134) in multi-user mode. After validatedRequest passes, the handler loads the session user via userFromSession(); it sends 403 when the lookup returns null (no Authorization header, JWT invalid/expired because JWT_SECRET changed, or the user row was deleted) or when user.suspended is true (admin suspended the account). Single-user mode never returns 403 here - it always answers 200.
Source
Thrown at server/endpoints/system.js:136
app.get("/setup-complete", async (_, response) => {
try {
const results = await SystemSettings.currentSettings();
response.status(200).json({ results });
} catch (e) {
console.error(e.message, e);
response.sendStatus(500).end();
}
});
app.get(
"/system/check-token",
[validatedRequest],
async (request, response) => {
try {
if (multiUserMode(response)) {
const user = await userFromSession(request, response);
if (!user || user.suspended) {
response.sendStatus(403).end();
return;
}
response.sendStatus(200).end();
return;
}
response.sendStatus(200).end();
} catch (e) {
console.error(e.message, e);
response.sendStatus(500).end();
}
}
);
/**
* Refreshes the user object from the session from a provided token.
* This does not refresh the token itself - if that is expired or invalid, the user will be logged out.View on GitHub (pinned to 20f6d3546c)
Solutions
- Treat 403 from check-token as 'log the user out': clear the stored session token and redirect to /login
- If all users are logged out at once, check whether JWT_SECRET was rotated (update-password regenerates it) - users simply sign in again
- If a single user is affected, verify their account is not suspended in the admin users panel
- Ensure the client sends Authorization: Bearer <token> on the request
Example fix
// before (frontend)
const res = await fetch("/api/system/check-token");
if (!res.ok) throw new Error("unexpected");
// after
const res = await fetch("/api/system/check-token", {
headers: { Authorization: `Bearer ${window.storage.getItem("token")}` },
});
if (res.status === 403) {
window.storage.removeItem("token");
window.location = "/login"; // session invalid or user suspended
} Defensive patterns
Strategy: validation
Validate before calling
const token = window.localStorage.getItem("token");
if (!token) {
window.location = "/login"; // no session to check - skip the request entirely
}
const res = await fetch("/api/system/check-token", {
headers: { Authorization: `Bearer ${token}` },
});
if (res.status === 403) {
window.localStorage.removeItem("token");
window.location = "/login";
} Prevention
- Always send the Authorization: Bearer header on authenticated routes
- Handle 403 from check-token as 'logout' - never as a retryable error
- After any JWT_SECRET rotation expect all sessions to invalidate; have users re-login rather than treating it as a bug
- Admins: prefer suspending users over deletion when sessions may still be active, so the UI gets a clean 403
When it happens
Trigger: Frontend polling /system/check-token with a stale session token after the admin rotated JWT_SECRET (e.g. via /system/update-password which regenerates the secret with v4()); a suspended user whose browser still holds a valid-signed JWT; a deleted user with an unexpired token; missing Authorization: Bearer header.
Common situations: Password update in single-user-to-multi-user transitions invalidating old sessions; admin suspending a user mid-session; clock skew or JWT_EXPIRY misconfiguration making tokens expire immediately; frontend kept a token from a previous deployment whose JWT_SECRET was regenerated.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Unauthorized
- User is suspended.
- Public token is required to validate a temporary auth token.
- Invalid token.
- Token expired.
AI-assisted analysis of Mintplex-Labs/anything-llm@20f6d3546c (2026-08-18).
Data as JSON: /api/errors/bc9d1776e4806223.
Report an issue: GitHub.