---
title: WebSocket session verification
description: Authenticate WebSocket connections with SuperTokens access tokens and enforce connection-lifetime checks.
sidebar:
  order: 4
---

## 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

:::info[Access token guidance]
This guide applies to scenarios involving **SuperTokens Session Access Tokens**.
:::

## 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:

<CodeGroup group="backend-language">
<Tab title="Node.js" value="nodejs">
```tsx
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,
    }),
  ],
});
```
</Tab>
<Tab title="Go" value="go">
```go
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,
			}),
		},
	})
}
```
</Tab>
<Tab title="Python" value="python">
```python check=false reason="initialization excerpt omits deployment connection config"
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
        )
    ]
)
```
</Tab>
</CodeGroup>

### 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.

```tsx check=false reason="Socket.IO server instance is created in the surrounding framework setup"
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.


```tsx check=false reason="Socket.IO server instance and application authorization validators are defined by the application"
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));
});
```

:::warning[Define a connection-lifetime policy]
Authentication at connection time is not enough: a connection can outlive token expiry, session revocation, or an
authorization change. Disconnect no later than the access token's `exp`, and revalidate before privileged events or on a
short application-defined interval. Use database checking when immediate session revocation matters. After disconnecting,
the client must refresh its session and reconnect with a new access token.
:::

---

## See also

<CardGroup cols={3}>
  <Card title="Protect backend routes" href="/additional-verification/session-verification/protect-api-routes" />
  <Card title="Protect frontend routes" href="/additional-verification/session-verification/protect-frontend-routes" />
  <Card title="Claim validation" href="/additional-verification/session-verification/claim-validation" />
  <Card title="Access session data" href="/post-authentication/session-management/access-session-data" />
</CardGroup>
