Skip to main content

Authentication Infrastructure

Walle provides JWT (JSON Web Token) authentication infrastructure using Passport.js. The authentication system includes strategy implementation, guards, and decorators ready to be used in your application.
The authentication infrastructure is implemented, but you’ll need to create your own auth controller with login/signup endpoints. The core components (JwtStrategy, JwtAuthguard, @Auth decorator) are ready to use.

Core Components

JwtStrategy

The JwtStrategy class validates JWT tokens and retrieves user data from MongoDB:
// From src/app/auth/strategies/jwt.strategy.ts
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(
    private readonly config: ConfigService,
    private readonly userService: UserService,
  ) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: config.get<string>(JWT_SECRET)
    });
  }

  async validate(payload: any) {
    if ('user_dni' in payload) {
      return await this.userService.findOneDni(payload.user_dni);
    }
    return null;
  }
}
Key features:
  • Extracts JWT from Authorization header (Bearer token)
  • Validates token signature using JWT_SECRET
  • Rejects expired tokens
  • Looks up user by user_dni from token payload
  • Returns user document or null

JwtAuthguard

The JwtAuthguard extends Passport’s AuthGuard and throws exceptions for unauthorized requests:
// From src/common/guards/jwt.guard.ts
@Injectable()
export class JwtAuthguard extends AuthGuard('jwt') {
  handleRequest(err, user, info) {
    if (err || !user) {
      throw err || new UnauthorizedException('Unathorized!');
    }
    return user;
  }
}
Behavior:
  • Returns 401 Unauthorized if token is missing, invalid, or expired
  • Returns 401 if user lookup fails
  • Populates request.user with the User document on success

@Auth() Decorator

The @Auth() decorator provides a convenient way to protect endpoints:
// From src/common/decorators/auth.decorator.ts
export function Auth() {
  return applyDecorators(
    UseGuards(JwtAuthguard)
  )
}
Usage in controllers:
import { Controller, Get } from '@nestjs/common';
import { Auth } from 'src/common/decorators';
import { User as UserDecorator } from 'src/common/decorators';

@Controller('protected')
export class ProtectedController {
  
  @Get()
  @Auth()  // Requires valid JWT token
  getProtectedData(@UserDecorator() user) {
    // user is the User document from MongoDB
    return { 
      message: 'Authenticated', 
      userId: user._id,
      dni: user.user_dni 
    };
  }
}

JWT Configuration

JWT tokens are configured in src/app/auth/auth.module.ts:
JwtModule.registerAsync({
  inject: [ConfigService],
  useFactory: (config: ConfigService) => ({
    secret: config.get<string>(JWT_SECRET),
    signOptions: {
      expiresIn: '8h'
    }
  })
})
Configuration:
  • Secret: Loaded from JWT_SECRET environment variable
  • Expiration: 8 hours
  • Algorithm: HS256 (default)

Required Environment Variable

JWT_SECRET
string
required
Secret key used to sign and verify JWT tokens. Use a strong random string in production.
Example:
JWT_SECRET=your-super-secret-key-min-32-characters-long

Implementing Login Endpoint

To use the authentication infrastructure, you’ll need to create a login controller:
import { Controller, Post, Body } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UserService } from './app/user/user.service';
import * as bcrypt from 'bcrypt';

@Controller('auth')
export class AuthController {
  constructor(
    private readonly userService: UserService,
    private readonly jwtService: JwtService,
  ) {}

  @Post('login')
  async login(@Body() loginDto: { user_dni: number; password: string }) {
    // Find user by DNI
    const user = await this.userService.findOneDni(loginDto.user_dni);
    
    if (!user) {
      throw new UnauthorizedException('Invalid credentials');
    }
    
    // Verify password (user_password field from User schema)
    const isPasswordValid = await bcrypt.compare(
      loginDto.password, 
      user.user_password
    );
    
    if (!isPasswordValid) {
      throw new UnauthorizedException('Invalid credentials');
    }
    
    // Generate JWT token with user_dni in payload
    const payload = { user_dni: user.user_dni };
    const access_token = this.jwtService.sign(payload);
    
    return {
      access_token,
      expires_in: '8h',
      user: {
        dni: user.user_dni,
        name: user.user_name,
        lastname: user.user_lastname,
        email: user.user_email,
        role: user.user_role
      }
    };
  }
}
Remember to hash passwords using bcrypt before storing them in the database. The User schema stores passwords in the user_password field.

Token Usage

Include JWT tokens in requests using the Authorization header:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Example request:
curl -X GET http://localhost:3700/your-endpoint \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

Error Handling

401 Unauthorized

The JwtAuthguard throws UnauthorizedException when:
  • Authorization header is missing
  • Token is malformed or invalid signature
  • Token has expired (>8 hours)
  • User lookup by user_dni returns null
Error response:
{
  "statusCode": 401,
  "message": "Unathorized!",
  "error": "Unauthorized"
}

User Validation

The JWT payload must contain user_dni field. The JwtStrategy.validate() method:
  1. Checks if payload contains user_dni
  2. Calls UserService.findOneDni(payload.user_dni)
  3. Returns User document if found, null otherwise
  4. JwtAuthguard throws 401 if user is null
User lookup from src/app/user/user.service.ts:
async findOneDni(dni: number): Promise<UserDocument | null> {
  return this.userModel.findOne({ user_dni: dni }).exec();
}

Security Best Practices

Store JWT tokens securely:
  • Use httpOnly cookies for web applications
  • Use secure storage APIs for mobile apps
  • Never store tokens in localStorage if you handle sensitive data
Protect your JWT_SECRET:
  • Use a strong random string (minimum 32 characters)
  • Never commit secrets to version control
  • Rotate secrets periodically
  • Use different secrets for dev/staging/production
Always use HTTPS in production:
  • JWT tokens in Authorization headers are sent with every request
  • HTTPS prevents token interception
  • Configure CORS properly for browser-based clients
The 8-hour expiration requires:
  • Implement token refresh logic for long-lived sessions
  • Handle 401 errors gracefully in your client
  • Prompt users to re-authenticate when tokens expire

Next Steps

JWT Strategy

Deep dive into JwtStrategy implementation

User Service

Learn about user lookup and management

User Schema

Explore the complete User model

Environment Config

Configure JWT_SECRET and other variables

Build docs developers (and LLMs) love