Authentication (JWT)
Authenticated - JWT token verification required
This example shows how to properly verify user identity using JWT tokens.
import { defineRoom } from "verani";
// npm install @tsndr/cloudflare-worker-jwt
import jwt from "@tsndr/cloudflare-worker-jwt";
interface AuthMeta extends ConnectionMeta {
username: string;
role: "user" | "moderator" | "admin";
}
export const secureRoom = defineRoom<AuthMeta>({
async extractMeta(req) {
// Get token from query parameter (or Authorization header)
const url = new URL(req.url);
const token = url.searchParams.get("token");
if (!token) {
throw new Error("Unauthorized: No token provided");
}
// Verify JWT signature and expiration
// Replace SECRET_KEY with your actual secret from environment
const isValid = await jwt.verify(token, SECRET_KEY);
if (!isValid) {
throw new Error("Unauthorized: Invalid token");
}
// Decode verified token
const payload = jwt.decode(token);
// Validate required claims
if (!payload.sub) {
throw new Error("Unauthorized: Missing user ID");
}
// Extract verified user data
return {
userId: payload.sub,
clientId: crypto.randomUUID(),
channels: ["default"],
username: payload.name || payload.sub,
role: payload.role || "user"
};
},
onConnect(ctx) {
// ctx.meta.userId is now VERIFIED and TRUSTED
console.log(`Verified user ${ctx.meta.username} connected`);
}
});
// Register event handlers (socket.io-like)
secureRoom.on("mod.kick", (ctx, data) => {
// Authorization check: Moderator-only actions
if (ctx.meta.role !== "moderator" && ctx.meta.role !== "admin") {
ctx.emit.emit("error", { message: "Insufficient permissions" });
return;
}
// Perform kick action
const { targetUserId } = data;
// Close target user's connections
const sessions = ctx.actor.getUserSessions(targetUserId);
sessions.forEach(ws => ws.close(1008, "Kicked by moderator"));
// Notify room using emit API
ctx.actor.emit.to("default").emit("user.kicked", {
userId: targetUserId,
by: ctx.meta.userId
});
});
onError(error, ctx) {
console.error(`Auth error for ${ctx.meta.userId}:`, error);
// Don't expose error details to client
ctx.emit.emit("error", { message: "An error occurred" });
}
});
Client:
// Step 1: Get JWT token from your auth service
async function login(username, password) {
const response = await fetch("https://your-api.com/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password })
});
const { token } = await response.json();
return token;
}
// Step 2: Connect with verified token
const token = await login("alice", "password123");
const client = new VeraniClient(
`wss://app.example.com/ws?token=${token}`
);
// Step 3: Handle auth errors
client.onClose((event) => {
if (event.reason === "Unauthorized") {
// Token invalid or expired - redirect to login
window.location.href = "/login";
}
});
client.on("error", (error) => {
console.error("Connection error:", error);
});
Important Notes:
- Never put tokens in localStorage if they contain sensitive data
- Use short-lived tokens (15-60 minutes)
- Implement token refresh mechanism
- Always use WSS (not WS) in production
- Consider using HttpOnly cookies instead of query params for better security
See Security Guide - Authentication for comprehensive authentication guide.
Related Examples
- Basic Chat - Simple chat room
- Rate Limiting - Prevent spam
- RPC - Remote Procedure Calls
Related Documentation
- Security Guide - Authentication - Complete authentication guide
- Security Guide - Authorization - Role-based access control