1.0.5 • Published 5 months ago

@develop-x/nest-auth-context v1.0.5

Weekly downloads
-
License
-
Repository
-
Last release
5 months ago

@develop-x/nest-auth-context

Overview

@develop-x/nest-auth-context is a NestJS package that provides user authentication context management for your applications. It extracts user information from request headers and makes it available throughout the request lifecycle via decorators and middleware.

Installation

npm install @develop-x/nest-auth-context

Features

  • User Context Middleware: Automatically extracts user information from request headers
  • Parameter Decorators: Easy access to current user data in controllers
  • Request Scoped: User context is available throughout the entire request lifecycle
  • Type Safe: Full TypeScript support with proper typing
  • Flexible: Works with various authentication schemes and user data formats

Usage

Module Import

Import the AuthContextModule in your application module:

import { Module } from '@nestjs/common';
import { AuthContextModule } from '@develop-x/nest-auth-context';

@Module({
  imports: [AuthContextModule],
})
export class AppModule {}

Middleware Configuration

Apply the user context middleware to extract user information from requests:

import { Module, MiddlewareConsumer } from '@nestjs/common';
import { UserContextMiddleware } from '@develop-x/nest-auth-context';

@Module({
  imports: [AuthContextModule],
})
export class AppModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(UserContextMiddleware)
      .forRoutes('*'); // Apply to all routes
  }
}

Using Decorators in Controllers

Use the provided decorators to access current user information:

import { Controller, Get, Post, Body } from '@nestjs/common';
import { CurrentUser, CurrentUserId } from '@develop-x/nest-auth-context';

@Controller('users')
export class UsersController {
  @Get('profile')
  getProfile(@CurrentUser() user: any) {
    return {
      message: 'User profile',
      user: user,
    };
  }

  @Get('dashboard')
  getDashboard(@CurrentUserId() userId: string) {
    return {
      message: `Dashboard for user ${userId}`,
      userId: userId,
    };
  }

  @Post('update')
  updateProfile(
    @CurrentUser() user: any,
    @CurrentUserId() userId: string,
    @Body() updateData: any
  ) {
    return {
      message: 'Profile updated',
      userId: userId,
      user: user,
      updateData: updateData,
    };
  }
}

API Reference

Decorators

@CurrentUser()

Extracts the complete user object from the request context.

Returns: any - The complete user object

Example:

@Get('profile')
getProfile(@CurrentUser() user: any) {
  console.log(user); // { id: '123', email: 'user@example.com', roles: ['user'] }
  return user;
}

@CurrentUserId()

Extracts only the user ID from the request context.

Returns: string | undefined - The user ID or undefined if not available

Example:

@Get('orders')
getUserOrders(@CurrentUserId() userId: string) {
  if (!userId) {
    throw new UnauthorizedException('User not authenticated');
  }
  return this.orderService.getOrdersByUserId(userId);
}

Middleware

UserContextMiddleware

Middleware that extracts user information from request headers and attaches it to the request object.

Header Processing:

  • Looks for user information in request headers
  • Parses and validates user data
  • Attaches user object to request.user

Request Header Format

The middleware expects user information to be provided in request headers. The exact format depends on your authentication system, but typically includes:

// Example headers from an authentication gateway or proxy
{
  'x-user-id': '123',
  'x-user-email': 'user@example.com',
  'x-user-roles': 'user,admin',
  // or a single header with JSON data
  'x-user-data': '{"id":"123","email":"user@example.com","roles":["user","admin"]}'
}

Advanced Usage

Custom User Type

Define a custom user interface for better type safety:

// types/user.interface.ts
export interface User {
  id: string;
  email: string;
  roles: string[];
  permissions: string[];
  organizationId?: string;
}

// controllers/users.controller.ts
import { Controller, Get } from '@nestjs/common';
import { CurrentUser, CurrentUserId } from '@develop-x/nest-auth-context';
import { User } from '../types/user.interface';

@Controller('users')
export class UsersController {
  @Get('profile')
  getProfile(@CurrentUser() user: User) {
    return {
      id: user.id,
      email: user.email,
      roles: user.roles,
    };
  }
}

Role-Based Access Control

Combine with guards for role-based access control:

import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const requiredRoles = this.reflector.get<string[]>('roles', context.getHandler());
    if (!requiredRoles) {
      return true;
    }

    const request = context.switchToHttp().getRequest();
    const user = request.user;

    return requiredRoles.some(role => user?.roles?.includes(role));
  }
}

// Usage in controller
import { UseGuards, SetMetadata } from '@nestjs/common';

@Controller('admin')
@UseGuards(RolesGuard)
export class AdminController {
  @Get('users')
  @SetMetadata('roles', ['admin'])
  getUsers(@CurrentUser() user: User) {
    return this.userService.getAllUsers();
  }
}

Service Integration

Access user context in services:

import { Injectable, Scope, Inject } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { Request } from 'express';

@Injectable({ scope: Scope.REQUEST })
export class UserAwareService {
  constructor(@Inject(REQUEST) private request: Request) {}

  getCurrentUser() {
    return this.request.user;
  }

  getCurrentUserId(): string | undefined {
    return this.request.user?.id;
  }

  async getUserData() {
    const userId = this.getCurrentUserId();
    if (!userId) {
      throw new Error('User not authenticated');
    }
    
    return this.userRepository.findById(userId);
  }
}

Audit Logging

Automatically log user actions:

import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { LoggerService } from '@develop-x/nest-logger';

@Injectable()
export class AuditInterceptor implements NestInterceptor {
  constructor(private readonly logger: LoggerService) {}

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const request = context.switchToHttp().getRequest();
    const user = request.user;
    const method = request.method;
    const url = request.url;

    return next.handle().pipe(
      tap(() => {
        this.logger.info('User action', {
          userId: user?.id,
          userEmail: user?.email,
          method,
          url,
          timestamp: new Date().toISOString(),
        });
      })
    );
  }
}

// Apply globally or to specific controllers
@UseInterceptors(AuditInterceptor)
@Controller('api')
export class ApiController {
  // ... controller methods
}

Integration Examples

With Authentication Guard

import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();
    const user = request.user;

    if (!user || !user.id) {
      throw new UnauthorizedException('User not authenticated');
    }

    return true;
  }
}

// Usage
@Controller('protected')
@UseGuards(AuthGuard)
export class ProtectedController {
  @Get('data')
  getProtectedData(@CurrentUser() user: User) {
    return { message: 'Protected data', user };
  }
}

With API Gateway Integration

// When using with an API Gateway that forwards user information
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class GatewayUserMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    // Extract user info from gateway headers
    const userId = req.headers['x-gateway-user-id'] as string;
    const userEmail = req.headers['x-gateway-user-email'] as string;
    const userRoles = req.headers['x-gateway-user-roles'] as string;

    if (userId) {
      req.user = {
        id: userId,
        email: userEmail,
        roles: userRoles ? userRoles.split(',') : [],
      };
    }

    next();
  }
}

Multi-tenant Support

import { Injectable } from '@nestjs/common';
import { CurrentUser } from '@develop-x/nest-auth-context';

interface TenantUser {
  id: string;
  email: string;
  tenantId: string;
  roles: string[];
}

@Injectable()
export class TenantService {
  async getTenantData(@CurrentUser() user: TenantUser) {
    return this.dataService.getDataByTenant(user.tenantId);
  }

  async createResource(
    @CurrentUser() user: TenantUser,
    resourceData: any
  ) {
    return this.resourceService.create({
      ...resourceData,
      tenantId: user.tenantId,
      createdBy: user.id,
    });
  }
}

Testing

Unit Testing with User Context

import { Test, TestingModule } from '@nestjs/testing';
import { UsersController } from './users.controller';

describe('UsersController', () => {
  let controller: UsersController;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      controllers: [UsersController],
    }).compile();

    controller = module.get<UsersController>(UsersController);
  });

  it('should return user profile', () => {
    const mockUser = {
      id: '123',
      email: 'test@example.com',
      roles: ['user'],
    };

    const result = controller.getProfile(mockUser);

    expect(result).toEqual({
      message: 'User profile',
      user: mockUser,
    });
  });

  it('should return dashboard for user', () => {
    const userId = '123';
    const result = controller.getDashboard(userId);

    expect(result).toEqual({
      message: 'Dashboard for user 123',
      userId: '123',
    });
  });
});

Integration Testing

import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import { AuthContextModule, UserContextMiddleware } from '@develop-x/nest-auth-context';
import * as request from 'supertest';

describe('Auth Context (e2e)', () => {
  let app: INestApplication;

  beforeEach(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AuthContextModule, AppModule],
    }).compile();

    app = moduleFixture.createNestApplication();
    app.use(new UserContextMiddleware().use);
    await app.init();
  });

  it('should extract user from headers', () => {
    return request(app.getHttpServer())
      .get('/users/profile')
      .set('x-user-id', '123')
      .set('x-user-email', 'test@example.com')
      .expect(200)
      .expect((res) => {
        expect(res.body.user).toBeDefined();
        expect(res.body.user.id).toBe('123');
      });
  });
});

Best Practices

  1. Type Safety: Define proper TypeScript interfaces for your user objects
  2. Validation: Validate user data extracted from headers
  3. Security: Never trust header data completely; validate against your user store
  4. Performance: Use request-scoped services when you need user context in services
  5. Error Handling: Handle cases where user information is missing or invalid
  6. Testing: Mock user context properly in your tests

Security Considerations

  1. Header Validation: Always validate user information from headers
  2. Authorization: Use additional guards for authorization checks
  3. Sensitive Data: Don't include sensitive information in user context
  4. Token Verification: If using tokens, verify them before trusting user data

Dependencies

  • @nestjs/common: NestJS common utilities
  • express: Express.js types for request/response handling

License

ISC

Support

For issues and questions, please refer to the project repository or contact the development team.

1.0.5

5 months ago

1.0.4

5 months ago

1.0.3

5 months ago

1.0.2

5 months ago

1.0.1

5 months ago

1.0.0

5 months ago