Authentication
This guide demonstrates JWT token-based authentication for securing Teleportal connections. Both WebSocket and HTTP handlers verify tokens before allowing access.
Tokens are always signed with HS256 (symmetric HMAC-SHA-256). verifyToken pins algorithms: ["HS256"], so tokens signed with any other algorithm – including unsecured alg: "none" tokens – are rejected.
What it demonstrates
Section titled “What it demonstrates”- Setting up JWT token authentication using
createTokenManager - Using token authentication for secure connections
- Configuring permission checks with token manager
- Creating and using JWT tokens on the client side
Server Setup
Section titled “Server Setup”import { serve } from "crossws/server";import { Server } from "teleportal/server";import { createTokenManager } from "teleportal/token";import { getWebsocketHandlers } from "teleportal/websocket-server";
const tokenManager = createTokenManager({ secret: "your-secret-key", expiresIn: 3600,});
const server = new Server({ storage: async (ctx) => { // Your storage implementation return documentStorage; }, checkPermission: async ({ context, documentId, message, type }) => { const token = (context as any).token; if (!token) return false;
const result = await tokenManager.verifyToken(token); if (!result.valid || !result.payload) return false;
const payload = result.payload; const requiredPermission = type === "read" ? "read" : "write"; return tokenManager.hasDocumentPermission(payload, documentId!, requiredPermission); },});
serve({ websocket: getWebsocketHandlers({ server, onUpgrade: async (request) => { // Extract token from request const url = new URL(request.url); const authHeader = request.headers.get("authorization"); const token = url.searchParams.get("token") || (authHeader && /^bearer\s+/i.test(authHeader) ? authHeader.replace(/^bearer\s+/i, "") : null);
if (!token) { throw new Response("No token provided", { status: 401 }); }
const result = await tokenManager.verifyToken(token); if (!result.valid || !result.payload) { throw new Response("Invalid token", { status: 401 }); }
return { context: { userId: result.payload.userId, room: result.payload.room, token, }, }; }, }), fetch: () => new Response("Not found", { status: 404 }),});Client Setup
Section titled “Client Setup”import { Provider } from "teleportal/providers";import { createTokenManager } from "teleportal/token";import { createEncryptionKey } from "teleportal/encryption-key";
// Create token manager (should match server secret)const tokenManager = createTokenManager({ secret: "your-secret-key",});
// Generate tokenconst token = await tokenManager.createToken("user-123", "org-456", [ { pattern: "user-123/*", permissions: ["read", "write"] },]);
// Connect with tokenconst provider = await Provider.create({ url: `wss://example.com?token=${token}`, document: "user-123/my-document", encryptionKey: createEncryptionKey(),});
await provider.synced;Document Access Patterns
Section titled “Document Access Patterns”The documentAccess list carried in each token uses literal glob matching: * is the only wildcard (it matches any run of characters, including none), and every other character – including regex metacharacters like ., [, or ( – is matched literally. A pattern such as logs[prod]* matches the literal text logs[prod]… and never behaves as a regex character class. A !-prefixed pattern is an exclusion: if any exclusion matches the document, access is denied regardless of inclusions.
Next Steps
Section titled “Next Steps”- Core Concepts: Authentication - Learn more about authentication
- Persistent Storage - Add persistent storage