@simple-cinema/passport
v1.0.0
Published
Lightweight SimpleCinema authentication library
Readme
@simple-cinema/passport
A lightweight, stateless authentication library for NestJS applications. It generates and verifies HMAC-signed tokens that encode a user ID and expiry time — no database or session store required.
How it works
A token has four dot-separated parts:
<userPart>.<iatPart>.<expPart>.<mac>| Part | Content |
|------------|-------------------------------------------------|
| userPart | Base64URL-encoded user ID |
| iatPart | Base64URL-encoded issued-at timestamp (seconds) |
| expPart | Base64URL-encoded expiry timestamp (seconds) |
| mac | HMAC-SHA256 hex digest of the above three parts |
The HMAC is computed with a domain prefix (PassportTokenAuth/v1) to prevent cross-context reuse. MAC comparison uses timingSafeEqual to prevent timing attacks.
Installation
npm install @simple-cinema/passport
# or
yarn add @simple-cinema/passportRegistration
Synchronous
Use when the secret key is available at module load time (e.g. from a static config).
import { PassportModule } from '@simple-cinema/passport';
@Module({
imports: [
PassportModule.register({
secretKey: 'your-secret-key',
}),
],
})
export class AppModule {}Asynchronous
Use when the secret key must be resolved from another provider (e.g. a config service).
import { PassportModule } from '@simple-cinema/passport';
import { ConfigModule, ConfigService } from '@nestjs/config';
@Module({
imports: [
PassportModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
secretKey: config.get<string>('AUTH_SECRET'),
}),
}),
],
})
export class AppModule {}PassportModule is decorated with @Global(), so you only need to register it once in your root module. PassportService is automatically available everywhere.
API
PassportService
Inject it anywhere in your application:
import { PassportService } from '@simple-cinema/passport';
@Injectable()
export class AuthService {
constructor(private readonly passport: PassportService) {}
}generate(userId: string, ttl: number): string
Creates a signed token for the given user.
| Parameter | Type | Description |
|-----------|----------|------------------------------------------|
| userId | string | The user identifier to embed in the token |
| ttl | number | Time-to-live in seconds |
// Generate a token valid for 1 hour
const token = this.passport.generate('user-123', 3600);verify(token: string): VerifyResult
Validates a token's signature and expiry.
const result = this.passport.verify(token);
if (result.valid) {
console.log(result.userId); // 'user-123'
} else {
console.log(result.reason); // 'Expired' | 'Invalid signature' | 'Invalid format' | 'Error'
}Return type — VerifyResult:
interface VerifyResult {
valid: boolean;
reason?: string; // present when valid is false
userId?: string; // present when valid is true
}Interfaces
PassportOptions
interface PassportOptions {
secretKey: string; // HMAC signing key — keep this secret
}PassportAsyncOptions
interface PassportAsyncOptions {
imports?: ModuleMetadata['imports'];
useFactory: (...args: any[]) => PassportOptions | Promise<PassportOptions>;
inject?: FactoryProvider['inject'];
}Full example
// auth.module.ts
@Module({
imports: [
PassportModule.register({ secretKey: process.env.AUTH_SECRET! }),
],
providers: [AuthService],
})
export class AuthModule {}
// auth.service.ts
@Injectable()
export class AuthService {
constructor(private readonly passport: PassportService) {}
login(userId: string) {
return { token: this.passport.generate(userId, 86400) }; // 24 h
}
authenticate(token: string) {
const result = this.passport.verify(token);
if (!result.valid) throw new UnauthorizedException(result.reason);
return result.userId;
}
}