Skip to content

Complete authentication and authorization package for NestJS - Monolith and Microservices ready with OAuth, JWT, Redis sessions

Notifications You must be signed in to change notification settings

UnCompa/nest-authify

Repository files navigation

πŸ” nest-authify

Complete, production-ready authentication and authorization package for NestJS applications. Supports monolithic and microservices architectures with OAuth, JWT, Redis sessions, and more.

npm version License: MIT

✨ Features

  • πŸ”‘ Multiple Authentication Strategies: Local (username/password), JWT, OAuth (Google, Facebook, GitHub)
  • 🏒 Flexible Architecture: Works in monolithic and microservices setups
  • πŸ”„ Session Management: Optional Redis-backed sessions with revocation support
  • 🎯 Easy to Use: Unified @Auth() decorator for authentication and authorization
  • πŸ›‘οΈ Type-Safe: Full TypeScript support with comprehensive types
  • πŸ”§ Extensible: Base classes for custom implementations
  • πŸ“¦ Plug & Play: Default implementations for quick setup
  • πŸš€ Production Ready: Built-in guards, decorators, and best practices
  • πŸ’Ύ Flexible Storage: Memory or Redis session storage
  • πŸ“ Swagger Integration: Automatic API documentation
  • 🎨 Customizable: Custom hash functions, repositories, and services

πŸ“¦ Installation

npm install nest-authify
# or
yarn add nest-authify
# or
pnpm add nest-authify

Required Peer Dependencies

npm install @nestjs/common @nestjs/core @nestjs/jwt @nestjs/passport @nestjs/config passport passport-jwt passport-local bcrypt joi uuid

Optional Dependencies

For Redis sessions:

npm install ioredis

For OAuth support:

# Google
npm install passport-google-oauth20

# Facebook
npm install passport-facebook

# GitHub
npm install passport-github2

πŸš€ Quick Start

1. Basic Setup (Normal Mode)

// app.module.ts
import { Module } from '@nestjs/common';
import { AuthModule } from 'nest-authify';
import { UserRepository } from './repositories/user.repository';

@Module({
  imports: [
    AuthModule.forRoot({
      mode: 'normal', // Para aplicaciones monolΓ­ticas
      jwtSecret: process.env.JWT_SECRET,
      jwtExpiresIn: '60m',
      refreshExpiresIn: '7d',
      authRepository: UserRepository,
      strategies: {
        local: true,
        jwt: true,
      },
    }),
  ],
})
export class AppModule {}

2. Implement Auth Repository

// user.repository.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { IAuthRepository } from 'nest-authify';
import { User } from './entities/user.entity';

@Injectable()
export class UserRepository implements IAuthRepository {
  constructor(
    @InjectRepository(User)
    private userRepo: Repository<User>,
  ) {}

  async findUserByUsername(username: string) {
    return this.userRepo.findOne({
      where: [{ username }, { email: username }],
    });
  }

  async findUserById(id: string) {
    return this.userRepo.findOne({ where: { id } });
  }

  async findUserByProviderId(provider: string, providerId: string) {
    return this.userRepo.findOne({ where: { provider, providerId } });
  }

  async createUser(data: any) {
    const user = this.userRepo.create(data);
    return this.userRepo.save(user);
  }

  async updateUser(id: string, data: any) {
    await this.userRepo.update(id, data);
    return this.findUserById(id);
  }
}

3. Use Built-in Controllers

The package provides ready-to-use controllers:

// No need to create controllers!
// These endpoints are automatically available:

// POST /auth/register
// POST /auth/login
// GET  /auth/profile
// POST /auth/refresh
// POST /auth/logout
// POST /auth/logout-all
// GET  /auth/verify
// POST /auth/change-password

4. Use @Auth() Decorator

// users.controller.ts
import { Controller, Get } from '@nestjs/common';
import { Auth, CurrentUser } from 'nest-authify';

@Controller('users')
export class UsersController {
  // Public route
  @Auth({ public: true })
  @Get('public')
  getPublic() {
    return 'This is public';
  }

  // Authenticated users only
  @Auth()
  @Get('profile')
  getProfile(@CurrentUser() user: any) {
    return user;
  }

  // Admin only
  @Auth({ roles: ['admin'] })
  @Get('admin')
  adminOnly() {
    return 'Admin only content';
  }

  // Multiple roles
  @Auth({ roles: ['admin', 'moderator'] })
  @Get('moderation')
  moderation() {
    return 'Moderation panel';
  }

  // Permissions-based
  @Auth({ permissions: ['posts:delete'] })
  @Delete('posts/:id')
  deletePost() {
    return 'Post deleted';
  }
}

πŸ“– Configuration Options

AuthModuleOptions

interface AuthModuleOptions {
  // Modo de operaciΓ³n
  mode: 'normal' | 'server' | 'client';

  // ConfiguraciΓ³n JWT
  jwtSecret: string;
  jwtExpiresIn?: string; // default: '60m'
  refreshExpiresIn?: string; // default: '7d'

  // Session Store (opcional)
  sessionStore?: {
    type: 'memory' | 'redis';
    redis?: {
      host: string;
      port: number;
      password?: string;
      db?: number;
      keyPrefix?: string;
    };
  };

  // Servicios personalizados
  authService?: Type<any>; // Debe extender BaseAuthService
  authRepository?: Type<any>; // Debe implementar IAuthRepository

  // Hash personalizado
  hashCallback?: (password: string) => Promise<string>;
  hashVerifyCallback?: (password: string, hash: string) => Promise<boolean>;

  // OAuth
  google?: {
    clientId: string;
    clientSecret: string;
    callbackUrl?: string;
    scope?: string[];
  };
  facebook?: {
    clientId: string;
    clientSecret: string;
    callbackUrl?: string;
    scope?: string[];
  };
  github?: {
    clientId: string;
    clientSecret: string;
    callbackUrl?: string;
    scope?: string[];
  };

  // Estrategias
  strategies?: {
    local?: boolean;
    jwt?: boolean;
    google?: boolean;
    facebook?: boolean;
    github?: boolean;
  };

  // Controladores
  enableControllers?: boolean; // default: true
  controllersPrefix?: string; // default: 'auth'
  enableSwagger?: boolean; // default: true
}

πŸ”§ Advanced Usage

Custom Hash Function

import * as argon2 from 'argon2';

AuthModule.forRoot({
  // ... otras opciones
  hashCallback: async (password: string) => {
    return argon2.hash(password);
  },
  hashVerifyCallback: async (password: string, hash: string) => {
    return argon2.verify(hash, password);
  },
}),

Redis Session Store

AuthModule.forRoot({
  mode: 'normal',
  jwtSecret: process.env.JWT_SECRET,
  authRepository: UserRepository,
  sessionStore: {
    type: 'redis',
    redis: {
      host: 'localhost',
      port: 6379,
      password: process.env.REDIS_PASSWORD,
      keyPrefix: 'auth:',
    },
  },
  strategies: {
    local: true,
    jwt: true,
  },
}),

OAuth Configuration

AuthModule.forRoot({
  mode: 'normal',
  jwtSecret: process.env.JWT_SECRET,
  authRepository: UserRepository,
  google: {
    clientId: process.env.GOOGLE_CLIENT_ID,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    callbackUrl: 'http://localhost:3000/auth/google/callback',
  },
  facebook: {
    clientId: process.env.FACEBOOK_APP_ID,
    clientSecret: process.env.FACEBOOK_APP_SECRET,
    callbackUrl: 'http://localhost:3000/auth/facebook/callback',
  },
  github: {
    clientId: process.env.GITHUB_CLIENT_ID,
    clientSecret: process.env.GITHUB_CLIENT_SECRET,
    callbackUrl: 'http://localhost:3000/auth/github/callback',
  },
  strategies: {
    local: true,
    jwt: true,
    google: true,
    facebook: true,
    github: true,
  },
}),

OAuth endpoints are automatically available:

  • GET /auth/google - Inicia flujo OAuth
  • GET /auth/google/callback - Callback de Google
  • GET /auth/facebook - Inicia flujo OAuth
  • GET /auth/facebook/callback - Callback de Facebook
  • GET /auth/github - Inicia flujo OAuth
  • GET /auth/github/callback - Callback de GitHub

Custom Auth Service

import { Injectable } from '@nestjs/common';
import { BaseAuthService } from 'nest-authify';

@Injectable()
export class CustomAuthService extends BaseAuthService {
  // Override para aΓ±adir logging
  async createSession(user: any, options?: any) {
    console.log(`User login: ${user.id}`);
    return super.createSession(user, options);
  }

  // ImplementaciΓ³n requerida
  protected async getUserById(userId: string) {
    return this.repository.findUserById(userId);
  }

  // MΓ©todos personalizados
  async sendWelcomeEmail(userId: string) {
    // Tu lΓ³gica aquΓ­
  }
}

// Usar en el mΓ³dulo
AuthModule.forRoot({
  // ... otras opciones
  authService: CustomAuthService,
}),

Extending AuthSession

// Extender la interfaz AuthSession
declare module 'nest-authify' {
  interface AuthSession {
    ipAddress?: string;
    userAgent?: string;
    deviceId?: string;
  }
}

// Usar en el servicio
const session = await this.authService.createSession(user, {
  metadata: {
    ipAddress: req.ip,
    userAgent: req.headers['user-agent'],
    deviceId: req.headers['x-device-id'],
  },
});

πŸ—οΈ Architecture Modes

Normal Mode (Monolithic)

Complete authentication in a single application.

AuthModule.forRoot({
  mode: 'normal',
  jwtSecret: process.env.JWT_SECRET,
  authRepository: UserRepository,
  strategies: { local: true, jwt: true },
})

Server Mode (Microservice Auth Server)

Dedicated authentication microservice.

// auth-service/app.module.ts
AuthModule.forRoot({
  mode: 'server',
  jwtSecret: process.env.JWT_SECRET,
  authRepository: UserRepository,
  strategies: { local: true, jwt: true },
})

Client Mode (Microservice Client)

Services that consume authentication.

// orders-service/app.module.ts
AuthModule.forRoot({
  mode: 'client',
  jwtSecret: process.env.JWT_SECRET, // Same secret as auth server
  strategies: { jwt: true }, // Only JWT validation needed
})

🎯 Decorators

@Auth()

Unified decorator for authentication and authorization.

// Public route
@Auth({ public: true })
@Get('public')
getPublic() {}

// Requires authentication
@Auth()
@Get('protected')
getProtected() {}

// Requires specific roles
@Auth({ roles: ['admin'] })
@Get('admin')
adminOnly() {}

// Requires permissions
@Auth({ permissions: ['posts:write'] })
@Post('posts')
createPost() {}

// Combined with custom guards
@Auth({ 
  roles: ['admin'], 
  permissions: ['users:delete'],
  guards: [ThrottlerGuard]
})
@Delete('users/:id')
deleteUser() {}

@CurrentUser()

Extracts user from request.

@Get('profile')
getProfile(@CurrentUser() user: any) {
  return user;
}

// Extract specific property
@Get('id')
getUserId(@CurrentUser('id') userId: string) {
  return { userId };
}

@SessionId()

Extracts session ID from JWT.

@Post('logout')
async logout(@SessionId() sessionId: string) {
  await this.authService.revokeSession(sessionId);
}

Other Decorators

@IpAddress() // Get client IP
@UserAgent() // Get User-Agent
@GetRequest() // Get full request object

πŸ›‘οΈ Guards

All guards are exported and can be used manually:

import { 
  JwtAuthGuard, 
  RolesGuard, 
  LocalAuthGuard,
  GoogleAuthGuard,
  FacebookAuthGuard,
  GithubAuthGuard,
  PermissionsGuard 
} from 'nest-authify';

@UseGuards(JwtAuthGuard, RolesGuard)
@Get('protected')
protectedRoute() {}

πŸ“ Environment Variables

Create a .env file:

# JWT Configuration
JWT_SECRET=your-super-secret-jwt-key
JWT_EXPIRES_IN=60m
REFRESH_EXPIRES_IN=7d

# Redis (optional)
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=your-redis-password
REDIS_DB=0
REDIS_KEY_PREFIX=auth:

# Google OAuth (optional)
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
GOOGLE_CALLBACK_URL=http://localhost:3000/auth/google/callback

# Facebook OAuth (optional)
FACEBOOK_APP_ID=your-facebook-app-id
FACEBOOK_APP_SECRET=your-facebook-app-secret
FACEBOOK_CALLBACK_URL=http://localhost:3000/auth/facebook/callback

# GitHub OAuth (optional)
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
GITHUB_CALLBACK_URL=http://localhost:3000/auth/github/callback

# Frontend URL (for OAuth redirects)
FRONTEND_URL=http://localhost:4200

πŸ”Œ Async Configuration

For dynamic configuration:

import { ConfigService } from '@nestjs/config';

AuthModule.forRootAsync({
  imports: [ConfigModule],
  useFactory: async (configService: ConfigService) => ({
    mode: 'normal',
    jwtSecret: configService.get('JWT_SECRET'),
    jwtExpiresIn: configService.get('JWT_EXPIRES_IN', '60m'),
    refreshExpiresIn: configService.get('REFRESH_EXPIRES_IN', '7d'),
    authRepository: UserRepository,
    sessionStore: configService.get('REDIS_HOST') ? {
      type: 'redis',
      redis: {
        host: configService.get('REDIS_HOST'),
        port: configService.get('REDIS_PORT'),
        password: configService.get('REDIS_PASSWORD'),
      },
    } : undefined,
    strategies: {
      local: true,
      jwt: true,
      google: !!configService.get('GOOGLE_CLIENT_ID'),
      facebook: !!configService.get('FACEBOOK_APP_ID'),
      github: !!configService.get('GITHUB_CLIENT_ID'),
    },
  }),
  inject: [ConfigService],
}),

πŸ“š API Reference

BaseAuthService

Base service that can be extended:

class BaseAuthService {
  // JWT Methods
  createJwt(user: any, expiresIn?: string, sessionId?: string): Promise<string>
  createRefreshToken(user: any, expiresIn?: string, sessionId?: string): Promise<string>
  verifyToken(token: string): Promise<JwtPayload>
  
  // Session Methods
  createSession(user: any, options?: CreateSessionOptions): Promise<AuthSession>
  refreshAccessToken(refreshToken: string): Promise<{ accessToken: string; expiresIn: number }>
  revokeSession(sessionId: string): Promise<void>
  revokeAllUserSessions(userId: string): Promise<void>
  
  // User Methods
  register(data: RegisterUserDto): Promise<any>
  validateUser(username: string, password: string): Promise<ValidatedUser | null>
  validateOAuthUser(provider: string, providerId: string, profile: any): Promise<any>
  changePassword(userId: string, oldPassword: string, newPassword: string): Promise<void>
  
  // Abstract Methods (must implement)
  protected abstract getUserById(userId: string): Promise<any>
}

IAuthRepository

Interface to implement for your data layer:

interface IAuthRepository {
  findUserByUsername(username: string): Promise<any>
  findUserById(id: string): Promise<any>
  findUserByProviderId(provider: string, providerId: string): Promise<any>
  createUser(data: any): Promise<any>
  updateUser(id: string, data: any): Promise<any>
  deleteUser?(id: string): Promise<void>
  findUsersByRole?(role: string): Promise<any[]>
  findActiveUsers?(): Promise<any[]>
}

ISessionStore

Interface for custom session stores:

interface ISessionStore {
  set(key: string, value: any, ttl?: number): Promise<void>
  get(key: string): Promise<any>
  delete(key: string): Promise<void>
  exists(key: string): Promise<boolean>
  keys(pattern?: string): Promise<string[]>
  clear(): Promise<void>
}

πŸ§ͺ Testing

Example test setup:

import { Test, TestingModule } from '@nestjs/testing';
import { AuthModule, AUTH_SERVICE } from 'nest-authify';

describe('AuthService', () => {
  let service: any;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      imports: [
        AuthModule.forRoot({
          mode: 'normal',
          jwtSecret: 'test-secret',
          authRepository: MockUserRepository,
          strategies: { local: true, jwt: true },
        }),
      ],
    }).compile();

    service = module.get(AUTH_SERVICE);
  });

  it('should create a session', async () => {
    const user = { id: '123', roles: ['user'] };
    const session = await service.createSession(user);
    
    expect(session).toHaveProperty('accessToken');
    expect(session).toHaveProperty('refreshToken');
    expect(session.sub).toBe('123');
  });
});

πŸ“Š Swagger Integration

The package automatically integrates with Swagger:

import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';

// In main.ts
const config = new DocumentBuilder()
  .setTitle('API Documentation')
  .setDescription('API with nest-authify')
  .setVersion('1.0')
  .addBearerAuth() // Add this for JWT
  .build();

const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);

All auth endpoints are automatically documented with:

  • Request/Response DTOs
  • Authentication requirements
  • Error responses
  • Example values

πŸ”’ Security Best Practices

  1. Strong JWT Secrets: Use long, random strings
  2. Short Token Expiration: Keep access tokens short-lived (15-60 minutes)
  3. Refresh Token Rotation: Implement refresh token rotation for better security
  4. HTTPS Only: Always use HTTPS in production
  5. Rate Limiting: Implement rate limiting on auth endpoints
  6. Password Strength: Validate password strength on registration
  7. Session Revocation: Implement session revocation for logout
  8. Audit Logging: Log authentication events

🀝 Contributing

Contributions are welcome! Please follow these steps:

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

πŸ“„ License

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

πŸ™ Acknowledgments

πŸ“§ Support

πŸ—ΊοΈ Roadmap

  • Support for more OAuth providers (Apple, LinkedIn, Twitter)
  • Permission-based authorization system (CASL integration)
  • Two-Factor Authentication (2FA)
  • Magic link authentication
  • WebSocket authentication support
  • GraphQL integration
  • Rate limiting integration
  • Audit logging
  • Admin dashboard for session management
  • Multi-tenancy support
  • Biometric authentication support

Made with ❀️ by UnCompa

About

Complete authentication and authorization package for NestJS - Monolith and Microservices ready with OAuth, JWT, Redis sessions

Resources

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published