Authentication
Teleportal provides a flexible authentication system based on JWT tokens with IAM-like permission management. The general model is to issue a JWT token which is signed by the server, containing the user’s ID, their room/organization ID, and document access patterns and permissions.
Overview
Section titled “Overview”The authentication model:
- Token Issuance: Server issues a JWT token signed with a secret
- Token Verification: Token is checked on every message received
- Permission Checking: Token should be lightweight and fast to verify
- Flexible Implementation: You can sub-class
TokenManagerto add custom logic
Implement your own authentication logic
Section titled “Implement your own authentication logic”You can opt-out of using the built-in authentication system by implementing your own authentication logic. This is useful if you want to use a different authentication system, or if you want to add custom logic to the authentication process.
const handlers = getWebsocketHandlers({ onUpgrade: async (request) => { // Your custom auth logic const user = await verifySession(request); if (!user) throw new Response("Unauthorized", { status: 401 }); return { context: { userId: user.id } }; },});You can also implement your own authentication logic for the server
const server = new Server({ checkPermission: async ({ context, documentId, message, type }) => { // Your custom auth logic const hasPermission = await validateToken(request); if (!hasPermission) { return false; } return true; },});Built-in authentication system
Section titled “Built-in authentication system”The built-in authentication system is a JWT token-based authentication system. It is designed to be flexible and easy to use. It is also designed to be secure and easy to audit.
Token Manager
Section titled “Token Manager”The token manager is a class that is used to create and verify JWT tokens. It is designed to be flexible and easy to use. It is also designed to be secure and easy to audit.
const tokenManager = createTokenManager({ secret: "your-secret-key-here", expiresIn: 3600, issuer: "my-collaborative-app",});The token manager is used to create and verify JWT tokens. It is designed to be flexible and easy to use. It is also designed to be secure and easy to audit.
const token = await tokenManager.createToken("user-123", "org-456", [ { pattern: "*", permissions: ["admin"] },]);The token manager is used to verify JWT tokens. It is designed to be flexible and easy to use. It is also designed to be secure and easy to audit.
const result = await tokenManager.verifyToken(token);if (!result.valid || !result.payload) { return false;}return true;The token manager is used to verify JWT tokens. It is designed to be flexible and easy to use. It is also designed to be secure and easy to audit.
const result = await tokenManager.verifyToken(token);if (!result.valid || !result.payload) { return false;}return true;Token Structure
Section titled “Token Structure”The JWT token payload contains:
{ userId: string; // User identifier room: string; // Room/organization identifier documentAccess: [ // Document access patterns { pattern: string; // Document pattern permissions: Permission[]; // Array of permissions } ]; exp?: number; // Expiration time (Unix timestamp) iat?: number; // Issued at time (Unix timestamp) iss?: string; // Issuer aud: "teleportal"; // Audience}Permission Types
Section titled “Permission Types”read: Can view document content and awareness updateswrite: Can modify document contentcomment: Can add comments to documentssuggest: Can make suggestions for document changesadmin: Full access to all operations (supersedes other permissions)
Creating Tokens
Section titled “Creating Tokens”Basic Token Creation
Section titled “Basic Token Creation”import { createTokenManager } from "teleportal/token";
const tokenManager = createTokenManager({ secret: "your-secret-key-here", expiresIn: 3600, // 1 hour issuer: "my-collaborative-app",});
// Generate a token for a userconst token = await tokenManager.createToken("user-123", "org-456", [ { pattern: "*", permissions: ["admin"] },]);User Token
Section titled “User Token”Create a token for user-owned documents:
// User owns all documents starting with their userIdconst userToken = await tokenManager.createUserToken("user-123", "org-456", [ "read", "write", "comment", "suggest"]);Admin Token
Section titled “Admin Token”Create an admin token with full access:
// Admin has access to all documents in the roomconst adminToken = await tokenManager.createAdminToken("admin-789", "org-456");Custom Token
Section titled “Custom Token”Create a custom token with specific access patterns:
const customToken = await tokenManager.createDocumentToken("user-101", "org-456", [ { pattern: "shared/*", permissions: ["read", "comment"] }, { pattern: "projects/my-project/*", permissions: ["read", "write", "comment", "suggest"] }, { pattern: "user-101/*", permissions: ["read", "write", "comment", "suggest", "admin"] }]);Document Pattern Matching
Section titled “Document Pattern Matching”The token utility supports flexible document pattern matching:
Exact Match
Section titled “Exact Match”pattern: "document1"// Matches: "document1"Prefix Match
Section titled “Prefix Match”pattern: "user/*"// Matches: "user/doc1", "user/doc2", "user/project/doc3"Wildcard Match
Section titled “Wildcard Match”pattern: "*"// Matches: any document nameSuffix Match
Section titled “Suffix Match”pattern: "*.md"// Matches: "readme.md", "document.md"Document Access Builder
Section titled “Document Access Builder”For complex permission scenarios, use the DocumentAccessBuilder:
import { DocumentAccessBuilder } from "teleportal/token";
// Basic usageconst access = new DocumentAccessBuilder() .allow("user/*", ["read", "write"]) .deny("private/*") .build();
// Using convenience methodsconst access = new DocumentAccessBuilder() .readOnly("public/*") .readWrite("user/*") .fullAccess("admin/*") .admin("super-admin/*") .build();
// Domain-specific methodsconst access = new DocumentAccessBuilder() .ownDocuments("user-123") .sharedDocuments() .projectDocuments("my-project") .orgDocuments("acme-corp") .build();Verifying Tokens
Section titled “Verifying Tokens”Verify and check permissions:
// Verify a tokenconst result = await tokenManager.verifyToken(token);if (result.valid && result.payload) { // Check specific permissions const canRead = tokenManager.hasDocumentPermission( result.payload, "user-123/document1", "read" );
const canWrite = tokenManager.hasDocumentPermission( result.payload, "shared/document1", "write" );
// Get all permissions for a document const permissions = tokenManager.getDocumentPermissions( result.payload, "user-123/document1" );}Server Integration
Section titled “Server Integration”Integrate token authentication with the websocket server:
import { getWebsocketHandlers } from "teleportal/websocket-server";import { Server } from "teleportal/server";import { createTokenManager } from "teleportal/token";
const tokenManager = createTokenManager({ secret: "your-secret-key", expiresIn: 3600,});
const server = new Server({ getStorage: async ({ context }) => { // Your storage implementation return documentStorage; }, checkPermission: async ({ context, documentId, fileId, message, type }) => { // Extract token from context const token = (context as any).token; if (!token) return false;
// Verify token const result = await tokenManager.verifyToken(token); if (!result.valid || !result.payload) return false;
const payload = result.payload;
// Check room access if (payload.room !== context.room) return false;
// Handle file messages if (message.type === "file") { // File messages use fileId for permission checks return true; // Implement file-specific permission checks }
// Check document permissions if (!documentId) { throw new Error("documentId is required for doc messages"); } const requiredPermission = message.type === "awareness" ? "read" : "write"; return tokenManager.hasDocumentPermission(payload, documentId, requiredPermission); },});
const handlers = getWebsocketHandlers({ onUpgrade: async (request) => { // Extract token from request const url = new URL(request.url); const token = url.searchParams.get("token") || request.headers.get("authorization")?.replace("Bearer ", "");
if (!token) { throw new Response("No token provided", { status: 401 }); }
// Verify token const result = await tokenManager.verifyToken(token); if (!result.valid || !result.payload) { throw new Response("Invalid token", { status: 401 }); }
const payload = result.payload;
// Check expiration if (payload.exp && Date.now() / 1000 > payload.exp) { throw new Response("Token expired", { status: 401 }); }
return { context: { userId: payload.userId, room: payload.room, token, // Pass token for permission checking }, }; },});Security Considerations
Section titled “Security Considerations”- Use strong secrets: Generate cryptographically secure random secrets
- Set appropriate expiration: Don’t make tokens too long-lived
- Validate room access: Always check that the user is in the correct room
- Check permissions on every operation: Don’t cache permission results
- Use HTTPS: Always use secure connections in production
- Rotate secrets: Regularly rotate your JWT signing secrets
Custom Authentication
Section titled “Custom Authentication”You can implement your own authentication logic:
const handlers = getWebsocketHandlers({ onUpgrade: async (request) => { // Your custom auth logic const user = await verifySession(request); if (!user) throw new Response("Unauthorized", { status: 401 });
return { context: { userId: user.id } }; },});Next Steps
Section titled “Next Steps”- Server - Learn how the server uses authentication
- Integration Guide - See authentication options
- Token API Reference - Full API documentation