toeverything/AFFiNE · error · AuthenticationRequired
authentication_required
authentication_required
Error message
You must sign in first to access this resource.
What it means
The global AuthGuard rejects any route that is not decorated @Public() when no authenticated user could be resolved. signIn() tries the Authorization: Bearer JWT path first (only if the bearer looks like a JWT) and then the affine_session cookie; if neither yields a session, AuthenticationRequired (authentication_required, HTTP 401) is thrown before the handler runs.
Solutions
- Sign in first, or attach Authorization: Bearer <auth-session access token> to the request
- For cookie auth always send credentials (fetch credentials: 'include' / axios withCredentials: true)
- Handle 401 in an interceptor: refresh the session, then retry once or redirect to sign-in
- If sessions died after a server config change, check that the auth secret / cookie settings are stable across restarts
Example fix
// before
await fetch('/api/auth/sessions'); // no credentials
// after
const res = await fetch('/api/auth/sessions', {
credentials: 'include',
headers: accessToken ? { authorization: `Bearer ${accessToken}` } : {},
});
if (res.status === 401) location.href = '/sign-in'; Defensive patterns
Strategy: try-catch
Type guard
function isAuthenticationRequired(e: unknown): boolean {
return (
typeof e === 'object' && e !== null &&
(e as { code?: string }).code === 'authentication_required'
);
} Try / catch
try {
return await api.get('/auth/sessions');
} catch (e) {
if (isAuthenticationRequired(e)) {
redirectToSignIn(); // or refresh session then retry once
return null;
}
throw e;
} Prevention
- Install a global 401 interceptor instead of catching per call
- Send credentials on every authenticated request from one shared client
- Check session presence (user object) before rendering authenticated UI
When it happens
Trigger: Calling a protected REST endpoint or GraphQL mutation with no cookies and no bearer token; an expired or revoked affine_session cookie; a bearer string that is not JWT-shaped (falls through to the cookie path); cross-origin fetch that did not send cookies; WebSocket/SSE handshake without credentials.
Common situations: Session expired while the tab was open; server restart with a new auth secret invalidating cookies; frontend calling the API before sign-in finishes; axios/fetch missing withCredentials; devtools 'copy as fetch' losing the cookie header.
Related errors
AI-assisted analysis of toeverything/AFFiNE@591f874dad (2026-08-18).
Data as JSON: /api/errors/4a03edc4a6d9032e.
Report an issue: GitHub.
Appendix: source
Thrown at packages/backend/server/src/core/auth/guard.ts:75
async canActivate(context: ExecutionContext) {
const { req, res } = getRequestResponseFromContext(context);
const clazz = context.getClass();
const handler = context.getHandler();
// api is public
const isPublic = this.reflector.getAllAndOverride<boolean>(
PUBLIC_ENTRYPOINT_SYMBOL,
[clazz, handler]
);
const authedUser = await this.signIn(req, res, isPublic);
if (isPublic) {
return true;
}
if (!authedUser) {
throw new AuthenticationRequired();
}
return true;
}
async signIn(
req: Request,
res?: Response,
isPublic = false
): Promise<Session | null> {
const result = await this.resolveRequestSession(req, res, isPublic);
return result?.session ?? null;
}
private async resolveRequestSession(
req: Request,
res?: Response,
isPublic = falseView on GitHub (pinned to 591f874dad)