How to authenticate WebSocket connections
JWT in query string, Authorization header on upgrade, cookie-based auth, verifying token on connection, rejecting unauthenticated clients, per-client auth state, token refresh with WebSocket
Authenticate at Connection Time
WebSocket upgrades are regular HTTP requests. Authenticate before accepting the connection. The cleanest approach for browser clients is a JWT in the query string (not ideal for sensitive tokens, but practical):
const jwt = require('jsonwebtoken');
const SECRET = process.env.JWT_SECRET;
wss.on('connection', (ws, req) => {
const url = new URL(req.url, 'http://localhost');
const token = url.searchParams.get('token');
let user;
try {
user = jwt.verify(token, SECRET);
} catch (err) {
ws.close(4001, 'Invalid token');
return;
}
ws.userId = user.id;
ws.send(JSON.stringify({ type: 'authenticated', userId: user.id }));
});Using the HTTP Server for Pre-Auth
A more secure approach: authenticate with a REST endpoint first, receive a short-lived one-time token (OTT), pass the OTT on the WebSocket URL. The server validates and immediately marks it used. This avoids long-lived tokens in URLs appearing in server logs. Reject any connection where token validation fails - close with code 4001 immediately, before any application data is sent.
