WebSocket session verification
Authenticate WebSocket connections with SuperTokens access tokens and enforce connection-lifetime checks.
Overview
WebSocket connections begin with an HTTP upgrade request, and Socket.IO may begin with HTTP long-polling. Eligible cookies
can accompany these requests. Browser WebSocket clients cannot set arbitrary headers, although non-browser clients can.
This guide passes an access token in Socket.IO’s handshake auth payload when cookie authentication is not suitable.
Before you start
Steps
1. Expose the JWT to the frontend
Ensure that the JWT is available to the frontend.
This is already the case in header-based authentication. If you use cookie-based authentication, set the following boolean
to true in session.init on the backend:
import SuperTokens from "supertokens-node";
import Session from "supertokens-node/recipe/session";
SuperTokens.init({
supertokens: {
connectionURI: "...",
},
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "...",
},
recipeList: [
Session.init({
exposeAccessTokenToFrontendInCookieBasedAuth: true,
}),
],
});import (
"github.com/supertokens/supertokens-golang/recipe/session"
"github.com/supertokens/supertokens-golang/recipe/session/sessmodels"
"github.com/supertokens/supertokens-golang/supertokens"
)
func main() {
supertokens.Init(supertokens.TypeInput{
RecipeList: []supertokens.Recipe{
session.Init(&sessmodels.TypeInput{
ExposeAccessTokenToFrontendInCookieBasedAuth: true,
}),
},
})
}from supertokens_python import init, InputAppInfo
from supertokens_python.recipe import session
init(
app_info=InputAppInfo(
api_domain="...", app_name="...", website_domain="..."),
framework='...',
recipe_list=[
session.init(
expose_access_token_to_frontend_in_cookie_based_auth=True
)
]
)2. Send the access token when connecting
Fetch the access token before creating the socket connection. Send it in Socket.IO’s auth payload, not the query string;
query-string tokens are commonly retained in URLs, proxy logs, and monitoring systems. Always use https/wss in
production and enforce an approved origin list on the server.
import Session from "supertokens-web-js/recipe/session";
async function initSocketConnection() {
const token = await Session.getAccessToken();
if (token === undefined) {
throw new Error("User is not logged in");
}
const socket = io.connect("https://api.example.com", {
auth: { token },
});
return socket;
}
- The
Session.getAccessToken()function auto refreshes the session before returning the JWT if needed.
3. Verify the session
Use a released backend session API rather than a signature-only JWT verifier. The Node.js example below validates the complete SuperTokens access-token structure, expiry, session claims, and revocation state before accepting the connection.
import Session from "supertokens-node/recipe/session";
io.use(async (socket, next) => {
try {
const token = socket.handshake.auth.token;
if (typeof token !== "string") {
throw new Error("Missing access token");
}
const session = await Session.getSessionWithoutRequestResponse(token, undefined, {
antiCsrfCheck: false,
checkDatabase: true,
});
socket.data.accessToken = token;
socket.data.session = session;
next();
} catch {
next(new Error("Authentication error"));
}
}).on("connection", (socket) => {
const payload = socket.data.session.getAccessTokenPayload();
const expiresInMs = Math.max(0, payload.exp * 1000 - Date.now());
const expiryTimer = setTimeout(() => socket.disconnect(true), expiresInMs);
socket.on("message", async (message: string, acknowledge?: (error?: string) => void) => {
try {
// Recheck revocation and configured authorization claims before privileged events.
await Session.getSessionWithoutRequestResponse(socket.data.accessToken, undefined, {
antiCsrfCheck: false,
checkDatabase: true,
});
io.emit("message", message);
acknowledge?.();
} catch {
acknowledge?.("Authentication error");
socket.disconnect(true);
}
});
socket.on("disconnect", () => clearTimeout(expiryTimer));
});