Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

MicroHD/authflow-phantom

Repository files navigation

@auth-flow/phantom

npm version License: MIT TypeScript NestJS Bun

A secure, passwordless authentication library for NestJS applications. Built with TypeScript and designed for modern web applications.

🌟 Features

  • πŸ” 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

πŸ“¦ Installation

# Using bun (recommended)
bun add @auth-flow/phantom
# Using npm
npm install @auth-flow/phantom
# Using yarn
yarn add @auth-flow/phantom

πŸ”‘ Getting Started

  1. 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
  1. Install Dependencies
bun add @auth-flow/phantom @nestjs/common @nestjs/core
  1. 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
  1. 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 {}
  1. 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;
 }
}

πŸ”§ Configuration Options

AuthFlowModule Configuration

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;
 };
}

Using Custom Redis Configuration

You can use your own Redis configuration in three ways:

  1. Using the built-in SharedModule (default):
@Module({
 imports: [
 AuthFlowModule.forRoot({
 // ... other config
 redis: {
 host: 'localhost',
 port: 6379,
 password: 'your-password'
 },
 }),
 ],
})
export class AppModule {}
  1. 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 {}
  1. 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>

Using Custom Redis and Prisma Configuration

You can use your own Redis and Prisma configuration in several ways:

  1. Using the built-in SharedModule (default):
@Module({
 imports: [
 AuthFlowModule.forRoot({
 // ... other config
 redis: {
 host: 'localhost',
 port: 6379,
 password: 'your-password'
 },
 }),
 ],
})
export class AppModule {}
  1. 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 {}
  1. 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:

  1. Make sure your PrismaService class implements all required methods
  2. Export your PrismaService from your module
  3. Import your PrismaModule before AuthFlowModule in your app
  4. If using a custom module, pass it through the customPrismaModule option

πŸ” Security Best Practices

  1. Environment Variables

    • Use strong, unique secrets
    • Never commit .env files
    • Use different secrets for development and production
  2. Rate Limiting

    • Configure appropriate limits based on your use case
    • Monitor and adjust limits based on traffic patterns
  3. Context Verification

    • Always verify request context
    • Implement IP-based blocking for suspicious activity
  4. Token Management

    • Use short expiry times for phantom links
    • Implement proper token rotation
    • Monitor token usage patterns
  5. HTTPS

    • Always use HTTPS in production
    • Configure proper SSL/TLS settings

πŸ§ͺ Testing

# Run tests
bun test
# Run tests with coverage
bun test:cov
# Run tests in watch mode
bun test:watch

πŸ“š API Documentation

AuthFlowService

class AuthFlowService {
 sendMagicLink(email: string): Promise<void>;
 validateLogin(token: string): Promise<AuthResult>;
 refreshToken(token: string): Promise<AuthResult>;
 logout(token: string): Promise<void>;
}

Guards

@UseGuards(AuthGuard)

Decorators

@AuthUser()
@RequireAuth()

🀝 Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Please read our Contributing Guide for details on our code of conduct and the process for submitting pull requests.

πŸ“ License

This project is licensed under the MIT License - see the LICENSE file for details.

πŸ™ Acknowledgments

πŸ“ž Support

πŸ”„ Changelog

See CHANGELOG.md for a list of changes.

About

πŸ” Secure, passwordless authentication for NestJS - Phantom Links, WebAuthn, and Magic Links

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

Contributors

AltStyle γ«γ‚ˆγ£γ¦ε€‰ζ›γ•γ‚ŒγŸγƒšγƒΌγ‚Έ (->γ‚ͺγƒͺγ‚ΈγƒŠγƒ«) /