Overview
The Auth module provides a powerful, flexible authentication system for Vercube applications. Built around a provider-based architecture, it allows you to implement various authentication strategies such as JWT, sessions, OAuth, or custom solutions.
Installation
$ pnpm add @vercube/auth
$ npm install @vercube/auth
$ bun install @vercube/auth
Quick Start
Create an Auth Provider
Create a custom authentication provider by extending the AuthProvider class:
import { AuthProvider, type AuthTypes } from '@vercube/auth';
interface User {
id: number;
username: string;
roles: string[];
}
export class JWTAuthProvider extends AuthProvider<User> {
public validate(request: Request, params?: AuthTypes.MiddlewareOptions): string | null {
const token = request.headers.get('Authorization')?.replace('Bearer ', '');
if (!token) {
return 'No token provided';
}
try {
const user = this.verifyToken(token);
// Check roles if specified
if (params?.roles && params.roles.length > 0) {
const hasRequiredRole = params.roles.some(role => user.roles.includes(role));
if (!hasRequiredRole) {
return 'Insufficient permissions';
}
}
return null; // Authentication successful
} catch {
return 'Invalid token';
}
}
public getCurrentUser(request: Request): User | null {
const token = request.headers.get('Authorization')?.replace('Bearer ', '');
if (!token) {
return null;
}
try {
return this.verifyToken(token);
} catch {
return null;
}
}
private verifyToken(token: string): User {
// Your JWT verification logic here
// This is a simplified example
return { id: 1, username: 'john', roles: ['user'] };
}
}
Register Provider in Container
Register your auth provider in the DI container during application setup:
import { type App } from '@vercube/core';
import { AuthProvider } from '@vercube/auth';
import { JWTAuthProvider } from './providers/JWTAuthProvider';
export function setup(app: App): void {
app.container.bind(AuthProvider, JWTAuthProvider);
}
Protect Your Endpoints
Use the @Auth decorator to protect controller methods:
import { Controller, Get } from '@vercube/core';
import { Auth, User } from '@vercube/auth';
interface User {
id: number;
username: string;
roles: string[];
}
@Controller('/profile')
export class ProfileController {
@Get('/')
@Auth()
public getProfile(@User() user: User) {
return { profile: user };
}
@Get('/admin')
@Auth({ roles: ['admin'] })
public getAdminPanel(@User() user: User) {
return { admin: true, user };
}
}
Core Concepts
AuthProvider
The AuthProvider is an abstract class that defines the interface for authentication implementations. All authentication providers must extend this class and implement two methods:
validate()- Validates incoming requests and returnsnullon success or an error message string on failuregetCurrentUser()- Returns the authenticated user object ornullif not authenticated
Decorators
The Auth module provides two decorators for easy integration with controllers:
| Decorator | Description |
|---|---|
@Auth() | Protects a method, requiring authentication before execution |
@User() | Injects the current authenticated user as a method parameter |
Role-Based Access Control
You can restrict access based on user roles by passing options to the @Auth decorator:
@Auth({ roles: ['admin', 'moderator'] })
public adminOnly(@User() user: User) {
// Only accessible by admins and moderators
}
The validate() method in your provider receives these options and should check if the user has the required roles.
Authentication Flow
When a request hits a protected endpoint:
- The
@Authdecorator triggers the authentication middleware - Your
AuthProvider.validate()method is called with the request - If
validate()returnsnull, authentication succeeds - If
validate()returns a string, authentication fails with that error message - The
@Userdecorator callsgetCurrentUser()to inject the user object
Common Patterns
import { AuthProvider, type AuthTypes } from '@vercube/auth';
import jwt from 'jsonwebtoken';
export class JWTAuthProvider extends AuthProvider<User> {
private secret = process.env.JWT_SECRET!;
public validate(request: Request, params?: AuthTypes.MiddlewareOptions): string | null {
const token = request.headers.get('Authorization')?.replace('Bearer ', '');
if (!token) {
return 'Authorization header required';
}
try {
const decoded = jwt.verify(token, this.secret) as User;
if (params?.roles?.length) {
if (!params.roles.some(role => decoded.roles.includes(role))) {
return 'Insufficient permissions';
}
}
return null;
} catch (error) {
if (error instanceof jwt.TokenExpiredError) {
return 'Token expired';
}
return 'Invalid token';
}
}
public getCurrentUser(request: Request): User | null {
const token = request.headers.get('Authorization')?.replace('Bearer ', '');
if (!token) return null;
try {
return jwt.verify(token, this.secret) as User;
} catch {
return null;
}
}
}
import { AuthProvider, type AuthTypes } from '@vercube/auth';
interface ApiKeyUser {
apiKeyId: string;
permissions: string[];
}
export class ApiKeyAuthProvider extends AuthProvider<ApiKeyUser> {
private validKeys = new Map<string, ApiKeyUser>();
public validate(request: Request): string | null {
const apiKey = request.headers.get('X-API-Key');
if (!apiKey) {
return 'API key required';
}
if (!this.validKeys.has(apiKey)) {
return 'Invalid API key';
}
return null;
}
public getCurrentUser(request: Request): ApiKeyUser | null {
const apiKey = request.headers.get('X-API-Key');
return apiKey ? this.validKeys.get(apiKey) || null : null;
}
}
import { AuthProvider } from '@vercube/auth';
import { Inject } from '@vercube/di';
export class SessionAuthProvider extends AuthProvider<User> {
@Inject(SessionStore)
private sessions!: SessionStore;
public async validate(request: Request): Promise<string | null> {
const sessionId = this.getSessionCookie(request);
if (!sessionId) {
return 'Session required';
}
const session = await this.sessions.get(sessionId);
if (!session) {
return 'Invalid or expired session';
}
return null;
}
public async getCurrentUser(request: Request): Promise<User | null> {
const sessionId = this.getSessionCookie(request);
if (!sessionId) return null;
const session = await this.sessions.get(sessionId);
return session?.user || null;
}
private getSessionCookie(request: Request): string | null {
const cookies = request.headers.get('Cookie');
// Parse session cookie...
return null;
}
}