npm version License: MIT TypeScript NestJS Bun
A secure, passwordless authentication library for NestJS applications. Built with TypeScript and designed for modern web applications.
- π Phantom Links: Secure, one-time, short-lived links tied to user/device context
- πΎ Device Memory Tokens: Local device tokens for seamless re-login
- π§ Email Magic Links: Passwordless login via email
- π― WebAuthn Support: FIDO2/WebAuthn authentication
- π‘οΈ Security Features:
- Rate limiting
- Context verification
- IP-based blocking
- Challenge expiration
- Secure token generation
- Redis-backed session management
- JWT token support
# Using bun (recommended) bun add @auth-flow/phantom # Using npm npm install @auth-flow/phantom # Using yarn yarn add @auth-flow/phantom
- Generate API Key
You can generate your API key in two ways:
# Method 1: Using the CLI tool bunx @auth-flow/phantom # or npx @auth-flow/phantom # Method 2: Programmatically import { ApiKeyService } from '@auth-flow/phantom'; const apiKeyService = new ApiKeyService(); const { apiKey, projectId } = apiKeyService.generateApiKey();
This will generate your API key and Project ID. Add them to your .env file:
AUTHFLOW_API_KEY=your_api_key_here AUTHFLOW_PROJECT_ID=your_project_id_here
- Install Dependencies
bun add @auth-flow/phantom @nestjs/common @nestjs/core
- Environment Setup
Add these to your .env file:
# Database Configuration DATABASE_URL=postgresql://user:password@localhost:5432/dbname # Redis Configuration REDIS_HOST=localhost REDIS_PORT=6379 REDIS_PASSWORD=your-redis-password # Email Configuration SMTP_HOST=smtp.example.com SMTP_PORT=587 SMTP_USER=your-smtp-user SMTP_PASS=your-smtp-password SMTP_FROM=noreply@example.com # JWT Configuration JWT_SECRET=your-secret-key-here # WebAuthn Configuration (Optional) WEBAUTHN_RP_NAME=Your App Name WEBAUTHN_RP_ID=yourdomain.com WEBAUTHN_ORIGIN=https://yourdomain.com
- Module Integration
import { Module } from '@nestjs/common'; import { AuthFlowModule } from '@auth-flow/phantom'; @Module({ imports: [ AuthFlowModule.forRoot({ database: { url: process.env.DATABASE_URL, }, redis: { host: process.env.REDIS_HOST, port: parseInt(process.env.REDIS_PORT), password: process.env.REDIS_PASSWORD, }, email: { host: process.env.SMTP_HOST, port: parseInt(process.env.SMTP_PORT), secure: true, auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS, }, from: process.env.SMTP_FROM, }, jwt: { secret: process.env.JWT_SECRET, expiresIn: '7d', }, webauthn: { rpName: process.env.WEBAUTHN_RP_NAME, rpID: process.env.WEBAUTHN_RP_ID, origin: process.env.WEBAUTHN_ORIGIN, }, }), ], }) export class AppModule {}
- Controller Implementation
import { Controller, Post, Body, Req, UseGuards } from '@nestjs/common'; import { AuthFlowService, AuthGuard } from '@auth-flow/phantom'; import { Request } from 'express'; @Controller('auth') export class AuthController { constructor(private readonly authFlow: AuthFlowService) {} @Post('login') async login(@Body('email') email: string) { await this.authFlow.sendMagicLink(email); return { message: 'Login link sent' }; } @Post('verify') async verify(@Body('token') token: string) { const result = await this.authFlow.validateLogin(token); return result; } @UseGuards(AuthGuard) @Get('profile') async getProfile(@Req() req: Request) { return req.user; } }
interface AuthFlowConfig { database: { url: string; }; redis: { host: string; port: number; password?: string; }; email: { host: string; port: number; secure: boolean; auth: { user: string; pass: string; }; from: string; }; jwt: { secret: string; expiresIn: string; }; webauthn?: { rpName: string; rpID: string; origin: string; }; }
You can use your own Redis configuration in three ways:
- Using the built-in SharedModule (default):
@Module({ imports: [ AuthFlowModule.forRoot({ // ... other config redis: { host: 'localhost', port: 6379, password: 'your-password' }, }), ], }) export class AppModule {}
- Using your own Redis module:
@Module({ imports: [ AuthFlowModule.forRoot({ // ... other config redis: { host: 'localhost', port: 6379, password: 'your-password' }, }, { useCustomRedis: true, customRedisModule: YourRedisModule, // Your custom Redis module }), ], }) export class AppModule {}
- Providing RedisService globally:
@Module({ imports: [ AuthFlowModule.forRoot({ // ... other config redis: { host: 'localhost', port: 6379, password: 'your-password' }, }, { useCustomRedis: true }), // No custom module provided, app must provide RedisService ], }) export class AppModule {}
Note: When using a custom Redis module or providing RedisService globally, make sure it implements the same interface as the built-in RedisService. The RedisService should provide these methods:
get<T>(key: string): Promise<T | null>set(key: string, value: any, ttl?: number): Promise<void>del(key: string): Promise<void>setWithExpiry(key: string, value: any, ttl: number): Promise<void>
You can use your own Redis and Prisma configuration in several ways:
- Using the built-in SharedModule (default):
@Module({ imports: [ AuthFlowModule.forRoot({ // ... other config redis: { host: 'localhost', port: 6379, password: 'your-password' }, }), ], }) export class AppModule {}
- Using custom Redis and Prisma modules:
@Module({ imports: [ AuthFlowModule.forRoot({ // ... other config redis: { host: 'localhost', port: 6379, password: 'your-password' }, }, { useCustomRedis: true, customRedisModule: YourRedisModule, useCustomPrisma: true, customPrismaModule: YourPrismaModule, }), ], }) export class AppModule {}
- Providing PrismaService globally:
// In your app's PrismaModule: @Module({ providers: [YourPrismaService], exports: [YourPrismaService], }) export class PrismaModule {} // In your AppModule: @Module({ imports: [ PrismaModule, // Import your PrismaModule first AuthFlowModule.forRoot({ // ... other config redis: { host: 'localhost', port: 6379, password: 'your-password' }, }, { useCustomPrisma: true, // No custom module provided, app must provide PrismaService }), ], }) export class AppModule {}
Note: When using custom modules or providing services globally, make sure they implement the correct interfaces:
RedisService Interface:
interface RedisService { get<T>(key: string): Promise<T | null>; set(key: string, value: any, ttl?: number): Promise<void>; del(key: string): Promise<void>; setWithExpiry(key: string, value: any, ttl: number): Promise<void>; }
PrismaService Interface:
interface PrismaService { // Your Prisma client methods user: { findUnique: (args: any) => Promise<any>; create: (args: any) => Promise<any>; update: (args: any) => Promise<any>; delete: (args: any) => Promise<any>; }; // ... other Prisma models }
Important: When providing your own PrismaService:
- Make sure your PrismaService class implements all required methods
- Export your PrismaService from your module
- Import your PrismaModule before AuthFlowModule in your app
- If using a custom module, pass it through the
customPrismaModuleoption
-
Environment Variables
- Use strong, unique secrets
- Never commit
.envfiles - Use different secrets for development and production
-
Rate Limiting
- Configure appropriate limits based on your use case
- Monitor and adjust limits based on traffic patterns
-
Context Verification
- Always verify request context
- Implement IP-based blocking for suspicious activity
-
Token Management
- Use short expiry times for phantom links
- Implement proper token rotation
- Monitor token usage patterns
-
HTTPS
- Always use HTTPS in production
- Configure proper SSL/TLS settings
# Run tests bun test # Run tests with coverage bun test:cov # Run tests in watch mode bun test:watch
class AuthFlowService { sendMagicLink(email: string): Promise<void>; validateLogin(token: string): Promise<AuthResult>; refreshToken(token: string): Promise<AuthResult>; logout(token: string): Promise<void>; }
@UseGuards(AuthGuard)
@AuthUser() @RequireAuth()
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Please read our Contributing Guide for details on our code of conduct and the process for submitting pull requests.
This project is licensed under the MIT License - see the LICENSE file for details.
See CHANGELOG.md for a list of changes.