@codecanva/nest-auth
v0.1.0
Published
Reusable NestJS authentication module with JWT access + refresh-token rotation, multi-device sessions, and pluggable persistence.
Maintainers
Readme
@codecanva/nest-auth
Reusable NestJS authentication module. JWT access + refresh-token rotation, multi-device sessions, replay detection, and pluggable persistence (no DB lock-in).
Features
- Short-lived access JWT + long-lived refresh JWT
- Refresh-token rotation on every refresh (with replay detection →
revokeAllForUser) - Multi-device sessions (one row per session, hashed at rest)
- Pluggable
RefreshTokenStoreandUserValidator— bring your own DB @CurrentUser(),@Public()decoratorsJwtAuthGuard(honours@Public()) andRefreshAuthGuard- Configurable issuer, audience, clock tolerance, optional hash pepper
Install
npm install @codecanva/nest-auth \
@nestjs/jwt @nestjs/passport passport passport-jwt \
class-validator class-transformerUsage
1. Implement the two interfaces against your data layer
// users/user.validator.ts
import { Injectable } from '@nestjs/common';
import { AuthUser, UserValidator } from '@codecanva/nest-auth';
@Injectable()
export class MyUserValidator implements UserValidator {
async validateCredentials(email: string, password: string): Promise<AuthUser | null> {
// load user, bcrypt.compare, return { id, email, roles } or null
}
async findById(userId: string | number): Promise<AuthUser | null> {
// load by id, return AuthUser or null
}
}// auth/refresh-token.store.ts — example with TypeORM
import { Injectable } from '@nestjs/common';
import {
CreateRefreshTokenInput,
RefreshTokenStore,
StoredRefreshToken,
} from '@codecanva/nest-auth';
@Injectable()
export class MyRefreshTokenStore implements RefreshTokenStore {
// create / findById / consume / revokeById / revokeAllForUser
// IMPORTANT: `consume` must be atomic (single UPDATE ... WHERE revoked_at IS NULL
// AND token_hash = $hash RETURNING *). Non-atomic impls split sessions under load.
}2. Register the module
import { AuthModule } from '@codecanva/nest-auth';
@Module({
imports: [
AuthModule.forRootAsync({
imports: [ConfigModule],
useFactory: (cfg: ConfigService) => ({
accessSecret: cfg.getOrThrow('JWT_ACCESS_SECRET'),
refreshSecret: cfg.getOrThrow('JWT_REFRESH_SECRET'),
accessTtl: '15m',
refreshTtl: '30d',
tokenHashPepper: cfg.get('TOKEN_HASH_PEPPER'),
issuer: 'my-app',
}),
inject: [ConfigService],
store: { useClass: MyRefreshTokenStore },
validator: { useClass: MyUserValidator },
}),
],
})
export class AppModule {}3. Apply the guard globally (optional)
import { APP_GUARD } from '@nestjs/core';
import { JwtAuthGuard } from '@codecanva/nest-auth';
providers: [{ provide: APP_GUARD, useClass: JwtAuthGuard }],4. Use in controllers
import {
AuthService, CurrentUser, LoginDto, Public, RefreshTokenDto,
} from '@codecanva/nest-auth';
@Controller('auth')
export class AuthController {
constructor(private readonly auth: AuthService) {}
@Public() @Post('login')
login(@Body() dto: LoginDto) { return this.auth.login(dto.email, dto.password); }
@Public() @Post('refresh')
refresh(@Body() dto: RefreshTokenDto) { return this.auth.refresh(dto.refreshToken); }
@Post('logout')
logout(@Body() dto: RefreshTokenDto) { return this.auth.logout(dto.refreshToken); }
}
@Controller('me')
export class MeController {
@Get() me(@CurrentUser() user: AuthUser) { return user; }
}API surface
AuthModule.forRootAsync(opts)— only entry pointAuthService—login/refresh/logout/logoutAll- Guards —
JwtAuthGuard,RefreshAuthGuard - Decorators —
@Public(),@CurrentUser() - DTOs —
LoginDto,RefreshTokenDto - Errors —
AuthError,InvalidCredentialsError,InvalidRefreshTokenError,RefreshTokenExpiredError,RefreshTokenReuseDetectedError,UserNotFoundError - Interfaces —
AuthModuleOptions,AuthModuleAsyncOptions,AuthUser,JwtPayload,RefreshTokenStore,StoredRefreshToken,CreateRefreshTokenInput,SessionMetadata,UserValidator
Local development / publishing
npm install # install deps
npm run start:dev # run the demo app at :3000 (uses in-memory store + validator)
npm run build:lib # compile lib → dist/
npm publish # publish (runs build:lib via prepublishOnly)To consume from a sibling project before publishing:
# in this repo
npm run build:lib && npm pack
# in the consumer
npm install /path/to/codecanva-nest-auth-0.1.0.tgzDemo endpoints (when running start:dev)
POST /auth/login { "email": "[email protected]", "password": "password123" }
POST /auth/refresh { "refreshToken": "..." }
POST /auth/logout { "refreshToken": "..." } # 204
GET /me # bearer access token requiredSecurity notes
- Refresh tokens are JWTs; the hash of the JWT (sha256 + optional pepper) is stored, never the raw token.
consumemust be atomic — non-atomic impls split sessions under concurrent refresh.- On replay (token hash mismatch or already-revoked row), every active session for that user is revoked.
- Always serve auth over HTTPS. Store the refresh token in an httpOnly cookie when possible.
