@zola_do/authorization
v0.2.8
Published
JWT auth, guards, strategies for NestJS
Maintainers
Readme
@zola_do/authorization
JWT authentication, API key validation, guards, and strategies for NestJS applications.
Overview
@zola_do/authorization provides a complete authentication and authorization solution:
- JWT Guards — Bearer token authentication
- Permission Guards — Role-based access control (RBAC)
- API Key Guard — External API authentication
- Strategies — Passport JWT strategies (access + refresh tokens)
- Decorators —
@CurrentUser(),@AllowAnonymous() - Auth Helper — Token generation, password hashing, OTP
By default, JwtGuard is registered as a global guard, protecting all routes.
Installation
# Install individually
npm install @zola_do/authorization
# Or via meta package
npm install @zola_do/nestjs-sharedDependencies
npm install @nestjs/jwt @nestjs/passport passport passport-jwt bcrypt jsonwebtokenOptional Dependencies
# For ThrottlerBehindProxyGuard
npm install @nestjs/throttlerQuick Start
1. Configure Environment
# .env
JWT_ACCESS_TOKEN_SECRET=your-access-token-secret-min-32-chars
JWT_ACCESS_TOKEN_EXPIRES=15m
JWT_REFRESH_TOKEN_SECRET=your-refresh-token-secret-min-32-chars
JWT_REFRESH_TOKEN_EXPIRES=7d2. Register Module
import { Module } from "@nestjs/common";
import { AuthorizationModule } from "@zola_do/authorization";
@Module({
imports: [AuthorizationModule],
})
export class AppModule {}3. Access User in Controller
import { Controller, Get } from "@nestjs/common";
import { CurrentUser, JwtGuard, UseGuards } from "@zola_do/authorization";
@Controller("profile")
export class ProfileController {
@Get()
@UseGuards(JwtGuard) // Optional - global guard is active by default
getProfile(@CurrentUser() user: any) {
return {
id: user.id,
email: user.email,
organization: user.organization,
};
}
}JWT Flow
┌─────────────────────────────────────────────────────────────────────┐
│ Login Flow │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Client Backend JWT Payload │
│ │ │ │ │
│ │ POST /login │ │ │
│ │ {email,pass} │ │ │
│ │───────────────>│ │ │
│ │ │ │ │
│ │ ┌─────▼─────┐ │ │
│ │ │ Validate │ │ │
│ │ │ credentials│ │ │
│ │ └─────┬─────┘ │ │
│ │ │ │ │
│ │ ┌─────▼───────────────┐ │ │
│ │ │ Generate tokens: │ │ │
│ │ │ - accessToken │─────────────┼─────────────────>│
│ │ │ - refreshToken │ │ { │
│ │ └────────────────────┘ │ sub: userId, │
│ │ │ │ email, │
│ │ │ │ organization, │
│ │ │ │ permissions │
│ │<───────────────│ │ } │
│ │ { accessToken,│ │
│ │ refreshToken│ │
│ │ } │ │
│ │ │ │
└────┼────────────────┼───────────────────────────────────────────────┘
│ │
│ │
▼ ▼
┌─────────────────────────────────────────────────────────────────────┐
│ Authenticated Request │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Client Backend JwtGuard │
│ │ │ │ │
│ │ GET /profile │ │ │
│ │ Authorization:│ │ │
│ │ Bearer <token>│ │ │
│ │───────────────>│ │ │
│ │ │ │ │
│ │ │ ┌────▼─────┐ │ │
│ │ │ │ Extract │ │ │
│ │ │ │ token │ │ │
│ │ │ └────┬─────┘ │ │
│ │ │ │ │ │
│ │ │ ┌────▼────────────┐ │ │
│ │ │ │ Verify with │ │ │
│ │ │ │ JWT_ACCESS_ │ │ │
│ │ │ │ TOKEN_SECRET │ │ │
│ │ │ └────┬────────────┘ │ │
│ │ │ │ │ │
│ │ │ │ set request.user │ │
│ │ │ ▼ │ │
│ │ │ req.user = payload │ │
│ │ │ │ │
│ │<───────────────│ Controller runs │ │
│ │ { user data } │ @CurrentUser() = user │ │
│ │ │ │ │
└────┴────────────────┴───────────────────────────────────────────────┘Guards
JwtGuard
Default global guard that validates Bearer tokens:
@UseGuards(JwtGuard)
@Controller("protected")
export class ProtectedController {}OptionalJwtGuard
Allows anonymous access but extracts user if token is present:
@UseGuards(OptionalJwtGuard)
@Controller("optional")
export class OptionalController {
@Get()
getData(@CurrentUser() user?: any) {
if (user) {
return { message: "Authenticated", user };
}
return { message: "Anonymous" };
}
}PermissionsGuard
Requires specific permissions. Supports pipe-separated OR logic:
@UseGuards(JwtGuard, PermissionsGuard("product:create"))
@Controller("products")
export class ProductsController {
@Post()
createProduct() {
// Requires 'product:create' permission
}
@Patch(":id")
@UseGuards(PermissionsGuard("product:update|product:manage"))
updateProduct() {
// Requires 'product:update' OR 'product:manage'
}
}ApiKeyGuard
Validates X-API-Key header against process.env.API_KEY:
@UseGuards(ApiKeyGuard)
@Controller("external")
export class ExternalController {}VendorGuard
Checks vendor registration status (custom implementation required):
@UseGuards(JwtGuard, VendorGuard())
@Controller("vendor")
export class VendorController {}Decorators
@CurrentUser()
Extract user from request:
@Get('profile')
getProfile(@CurrentUser() user: UserPayload) {
return user;
}
// With property extraction
@Get('profile')
getProfile(@CurrentUser('email') email: string) {
return { email };
}@AllowAnonymous()
Skip authentication for specific routes:
@Get('public')
@AllowAnonymous()
getPublicData() {
return { message: 'Public data' };
}JWT Payload
Default payload structure:
interface JwtPayload {
sub: string; // User ID
email: string; // User email
organization?: {
// Organization context
organizationId: string;
organizationName: string;
};
permissions?: string[]; // User permissions
iat?: number; // Issued at
exp?: number; // Expiration
}AuthHelper
Utility service for token operations:
import { AuthHelper } from "@zola_do/authorization";
@Injectable()
export class AuthService {
constructor(private readonly authHelper: AuthHelper) {}
async login(user: User) {
// Generate tokens
const tokens = this.authHelper.generateTokens(user);
// Hash password
const hashedPassword = await this.authHelper.hashPassword(plainPassword);
// Verify password
const isValid = await this.authHelper.verifyPassword(
plainPassword,
hashedPassword,
);
// Generate OTP
const otp = this.authHelper.generateOtp();
return { tokens, hashedPassword, isValid, otp };
}
async refreshTokens(refreshToken: string) {
return this.authHelper.refreshTokens(refreshToken);
}
}AuthHelper Methods
| Method | Description | Returns |
| -------------------------------- | ------------------------------ | ------------------------------- |
| generateTokens(user) | Create access + refresh tokens | { accessToken, refreshToken } |
| refreshTokens(refreshToken) | Refresh using refresh token | { accessToken, refreshToken } |
| hashPassword(password) | bcrypt hash | string |
| verifyPassword(password, hash) | Compare password to hash | boolean |
| generateOtp(length) | Generate numeric OTP | string |
| decodeToken(token) | Decode without verification | JwtPayload |
Token Generation Example
import { AuthHelper } from "@zola_do/authorization";
import { JwtPayload } from "@zola_do/authorization/helper";
@Controller("auth")
export class AuthController {
constructor(private readonly authHelper: AuthHelper) {}
@Post("login")
async login(@Body() dto: LoginDto) {
const user = await this.userService.validateUser(dto);
const payload: JwtPayload = {
sub: user.id,
email: user.email,
organization: {
organizationId: user.organizationId,
organizationName: user.organization.name,
},
permissions: user.permissions,
};
return this.authHelper.generateTokens(payload);
}
@Post("refresh")
async refresh(@Body("refreshToken") refreshToken: string) {
return this.authHelper.refreshTokens(refreshToken);
}
}Custom JwtGuard Override
Override the default JWT validation logic:
import { Injectable, ExecutionContext } from "@nestjs/common";
import { JwtGuard } from "@zola_do/authorization";
@Injectable()
export class AppJwtGuard extends JwtGuard {
async canActivate(context: ExecutionContext): Promise<boolean> {
// Run default validation
const canActivate = await super.canActivate(context);
if (!canActivate) return false;
// Add custom logic
const request = context.switchToHttp().getRequest();
const user = request.user;
// Check additional conditions
if (user.status === "suspended") {
throw new ForbiddenException("Account suspended");
}
return true;
}
}Register in module:
import { APP_GUARD } from "@nestjs/core";
import { AuthorizationModule, JwtGuard } from "@zola_do/authorization";
@Module({
imports: [AuthorizationModule],
providers: [
AppJwtGuard,
{ provide: JwtGuard, useExisting: AppJwtGuard },
{ provide: APP_GUARD, useExisting: AppJwtGuard },
],
})
export class AppModule {}Strategies
JwtStrategy
Passport strategy for access token validation:
import { JwtStrategy } from "@zola_do/authorization";
// Already registered by AuthorizationModule
// Customization via override (see above)JwtRefreshTokenStrategy
Passport strategy for refresh token validation:
import { JwtRefreshTokenStrategy } from "@zola_do/authorization";
// Used for token refresh endpoints
// Validates against JWT_REFRESH_TOKEN_SECRETEnvironment Variables
| Variable | Description | Default |
| --------------------------- | ---------------------------- | -------- |
| JWT_ACCESS_TOKEN_SECRET | Access token signing secret | Required |
| JWT_ACCESS_TOKEN_EXPIRES | Access token TTL | 15m |
| JWT_REFRESH_TOKEN_SECRET | Refresh token signing secret | Required |
| JWT_REFRESH_TOKEN_EXPIRES | Refresh token TTL | 7d |
| API_KEY | API key for ApiKeyGuard | Optional |
Token Expiration Format
15m - 15 minutes
1h - 1 hour
7d - 7 days
30d - 30 daysPermission System
Permissions use pipe-separated OR logic:
// Require any of these permissions
@UseGuards(PermissionsGuard('admin:write|manager:write|product:create'))Common Permission Patterns
| Pattern | Description |
| ----------------- | ----------------------------------------- |
| resource:action | Basic CRUD (product:create, product:read) |
| resource:* | All actions on resource |
| *:read | Read all resources |
| admin:* | Admin access |
Recommended Imports
Subpath imports are recommended for tree-shaking:
import { AuthorizationModule } from "@zola_do/authorization/module";
import {
JwtGuard,
PermissionsGuard,
ApiKeyGuard,
} from "@zola_do/authorization/guards";
import { AllowAnonymous, CurrentUser } from "@zola_do/authorization/decorators";
import { AuthHelper } from "@zola_do/authorization/helper";
import {
JwtStrategy,
JwtRefreshTokenStrategy,
} from "@zola_do/authorization/strategy";Root import is supported for backward compatibility:
import {
AuthorizationModule,
JwtGuard,
AllowAnonymous,
AuthHelper,
} from "@zola_do/authorization";Optional Features
ThrottlerBehindProxyGuard
Rate limiting guard for applications behind a reverse proxy:
import { ThrottlerBehindProxyGuard } from "@zola_do/authorization/optional/throttler";
@UseGuards(ThrottlerBehindProxyGuard)
@Controller("api")
export class ApiController {}Requires @nestjs/throttler peer dependency.
API Reference
Guards
| Guard | Description | Auth Required |
| ------------------------------- | ----------------------- | -------------------- |
| JwtGuard | Bearer token validation | Yes (global default) |
| OptionalJwtGuard | Bearer token if present | No |
| PermissionsGuard(permissions) | Permission check | Yes |
| ApiKeyGuard | API key validation | Yes |
| VendorGuard() | Vendor status check | Yes |
| ThrottlerBehindProxyGuard | Rate limiting | Configurable |
Decorators
| Decorator | Description |
| ------------------------ | ----------------------------- |
| @CurrentUser() | Get current user from request |
| @CurrentUser(property) | Get specific property |
| @AllowAnonymous() | Skip authentication |
Strategies
| Strategy | Token Type | Secret |
| ------------------------- | ---------- | -------------------------- |
| JwtStrategy | Access | JWT_ACCESS_TOKEN_SECRET |
| JwtRefreshTokenStrategy | Refresh | JWT_REFRESH_TOKEN_SECRET |
AuthHelper
class AuthHelper {
generateTokens(payload: JwtPayload): {
accessToken: string;
refreshToken: string;
};
refreshTokens(
refreshToken: string,
): Promise<{ accessToken: string; refreshToken: string }>;
hashPassword(password: string): Promise<string>;
verifyPassword(password: string, hashedPassword: string): Promise<boolean>;
generateOtp(length?: number): string;
decodeToken(token: string): JwtPayload;
}Troubleshooting
Q: Token expired errors?
Check JWT_ACCESS_TOKEN_EXPIRES environment variable and ensure clocks are synchronized.
Q: User is undefined in @CurrentUser()?
Ensure JwtGuard is active and token is valid:
@UseGuards(JwtGuard) // Add explicit guard if global is not configuredQ: Custom token payload?
Override JwtStrategy and JwtGuard to implement custom validation.
Q: Skip guard for specific routes?
@AllowAnonymous() // Works even with global JwtGuardRelated Packages
- @zola_do/crud — Uses JwtGuard and PermissionsGuard
License
ISC
