My App
BoardKitGuides

Custom Auth Guard

Step-by-step guide to implementing BoardAuthGuard

This guide walks through implementing a custom BoardAuthGuard for different authentication strategies.

The Interface

interface AuthenticatedUser {
    userId: string;
    displayName: string;
}

abstract class BoardAuthGuard {
    abstract validateConnection(token: string): Promise<AuthenticatedUser | null>;
    abstract validateRequest(token: string): Promise<AuthenticatedUser | null>;
}
  • validateConnection — Called when a WebSocket client sends a join message. The token comes from the JoinMessage.token field.
  • validateRequest — Called for HTTP requests. The token is extracted from the Authorization: Bearer <token> header.

Return null to reject the request, or return an AuthenticatedUser object to allow it.

Example: JWT Authentication

import { BoardAuthGuard, type AuthenticatedUser } from '@hfu.digital/boardkit-nestjs';
import { verify } from 'jsonwebtoken';

interface JwtPayload {
    sub: string;
    name: string;
    exp: number;
}

export class JwtAuthGuard extends BoardAuthGuard {
    constructor(private readonly secret: string) {
        super();
    }

    async validateConnection(token: string): Promise<AuthenticatedUser | null> {
        return this.validate(token);
    }

    async validateRequest(token: string): Promise<AuthenticatedUser | null> {
        return this.validate(token);
    }

    private validate(token: string): AuthenticatedUser | null {
        try {
            const payload = verify(token, this.secret) as JwtPayload;
            return {
                userId: payload.sub,
                displayName: payload.name,
            };
        } catch {
            return null;
        }
    }
}

Example: API Key Authentication

export class ApiKeyAuthGuard extends BoardAuthGuard {
    constructor(private readonly apiKeys: Map<string, { userId: string; displayName: string }>) {
        super();
    }

    async validateConnection(token: string): Promise<AuthenticatedUser | null> {
        return this.apiKeys.get(token) ?? null;
    }

    async validateRequest(token: string): Promise<AuthenticatedUser | null> {
        return this.apiKeys.get(token) ?? null;
    }
}

Example: OAuth / External Auth Service

export class OAuthAuthGuard extends BoardAuthGuard {
    constructor(private readonly authServiceUrl: string) {
        super();
    }

    async validateConnection(token: string): Promise<AuthenticatedUser | null> {
        return this.validate(token);
    }

    async validateRequest(token: string): Promise<AuthenticatedUser | null> {
        return this.validate(token);
    }

    private async validate(token: string): Promise<AuthenticatedUser | null> {
        const res = await fetch(`${this.authServiceUrl}/verify`, {
            headers: { Authorization: `Bearer ${token}` },
        });

        if (!res.ok) return null;

        const user = await res.json();
        return {
            userId: user.id,
            displayName: user.name,
        };
    }
}

Registration

BoardModule.register({
    // ...
    authGuard: new JwtAuthGuard(process.env.JWT_SECRET!),
})

Frontend Integration

The authToken passed to BoardKitProvider is sent:

  • As the Authorization: Bearer <token> header on all HTTP requests
  • As the token field in WebSocket join messages
<BoardKitProvider config={{ apiUrl: '/api', wsUrl: 'ws://localhost:3000/board', authToken: jwtToken }}>
    <App />
</BoardKitProvider>

Testing

Use MockAuthGuard to skip authentication in tests:

import { MockAuthGuard } from '@hfu.digital/boardkit-nestjs';

BoardModule.register({
    // ...
    authGuard: new MockAuthGuard(),
    // Accepts any token, returns { userId: 'test-user', displayName: 'Test User' }
})

On this page