toeverything/AFFiNE · error · AuthenticationRequired
authentication_required
authentication_required
Error message
You must sign in first to access this resource.
What it means
Thrown by the Socket.IO server's connection middleware when the configured `canActivate` handshake guard resolves to false. The guard runs `AuthGuard.signIn` against the WS upgrade request; if no valid session cookie or JWT bearer is present (or `signIn` caught an internal error and returned null), the socket is rejected before the connection is established. This is the WebSocket equivalent of HTTP 401 Unauthorized.
Source
Thrown at packages/backend/server/src/base/websocket/adapter.ts:64
),
callback
);
},
credentials: true,
methods: CORS_ALLOWED_METHODS,
allowedHeaders: CORS_ALLOWED_HEADERS,
},
});
if (config.canActivate) {
server.use((socket, next) => {
config
.canActivate(socket)
.then(pass => {
if (pass) {
next();
} else {
throw new AuthenticationRequired();
}
})
.catch(e => {
next(e);
});
});
}
const pubClient = this.app.get(SocketIoRedis);
const subClient = pubClient.duplicate();
server.adapter(createAdapter(pubClient, subClient));
const close = server.close;
server.close = async fn => {
await close.call(server, fn);
// NOTE(@forehalo):
// the lifecycle of duplicated redis client will not be controlled by nestjs lifecycleView on GitHub (pinned to 26c515e050)
Solutions
- Ensure the WS client sends credentials: for browsers set the session cookie (same-site, `credentials: 'include'` on the polling fallback); for native clients pass `auth: { tokenType: 'jwt', token }` so the server rewrites it to an Authorization header.
- Verify the session is still valid by hitting `GET /api/auth/session` first; if it returns no user, re-authenticate before connecting the socket.
- Check that the backend Redis (`SocketIoRedis` / session cache) is reachable — an unreachable cache makes every otherwise-valid session look absent.
- Confirm the client version passes `AuthGuard.checkUserSessionClientVersion`; an unsupported version revokes/voids the session and yields no authed user.
Example fix
// before
const socket = io('/sync');
// after (browser, cookie-based)
const socket = io('/sync', { withCredentials: true });
// after (native, JWT-based)
const socket = io('/sync', {
auth: { tokenType: 'jwt', token: accessToken },
}); Defensive patterns
Strategy: validation
Validate before calling
// Before opening the socket, confirm a session exists.
async function hasSession(): Promise<boolean> {
const r = await fetch('/api/auth/session', { credentials: 'include' });
if (!r.ok) return false;
const { user } = await r.json();
return !!user;
}
if (!(await hasSession())) { location.href = '/sign-in'; }
else { io('/sync', { withCredentials: true }); } Type guard
function hasWsAuth(opts: { withCredentials?: boolean; auth?: { tokenType?: string; token?: string } }): boolean {
return opts.withCredentials === true || (!!opts.auth?.token && opts.auth.tokenType === 'jwt');
} Try / catch
// socket.io v4
socket.on('connect_error', (err) => {
if (err.message.includes('sign in first') || err.data?.code === 'authentication_required') {
redirectToLogin();
}
}); Prevention
- Always pass `withCredentials: true` (browser) or `auth: { tokenType: 'jwt', token }` (native) when connecting.
- Validate the session via `/api/auth/session` before establishing the socket.
- Refresh access tokens proactively so the WS handshake always has valid credentials.
When it happens
Trigger: Opening a WebSocket connection to the sync endpoint without an `affine_session` cookie, without an `Authorization: Bearer <jwt>` header (or `handshake.auth.token` for socket.io), with an expired/revoked session, or while the backend's session lookup throws (the guard swallows errors and treats them as unauthenticated).
Common situations: Client connects from a fresh context (no cookies persisted), cross-origin WS without `withCredentials`, JWT expired between HTTP page load and WS upgrade, native app forgot to attach the token to the socket handshake, or the Redis session store is unreachable so `getUserSessionFromRequest` returns null.
Related errors
- authentication_required
- auth_session_expired
- The refresh token is invalid. | The auth session has expired
- auth_session_temporarily_unavailable
- action_forbidden
AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12).
Data as JSON: /api/errors/283fa5b9987391db.
Report an issue: GitHub.