Overview
Walle uses JWT (JSON Web Token) authentication with Passport strategies to secure API endpoints. The authentication system validates user credentials from MongoDB and issues time-limited access tokens.
Authentication Flow
JWT Strategy Implementation
The JWT validation logic is implemented in src/app/auth/strategies/jwt.strategy.ts:
import { ExtractJwt , Strategy } from 'passport-jwt' ;
import { PassportStrategy } from '@nestjs/passport' ;
import { Injectable } from '@nestjs/common' ;
import { ConfigService } from '@nestjs/config' ;
import { JWT_SECRET } from 'src/common/constants/settings.contant' ;
import { UserService } from 'src/app/user/user.service' ;
@ Injectable ()
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 Components
jwtFromRequest : ExtractJwt . fromAuthHeaderAsBearerToken ()
Extracts the JWT from the Authorization header in the format:
Authorization: Bearer <token>
Token Validation
ignoreExpiration : false ,
secretOrKey : config . get < string >( JWT_SECRET ) !
Expiration checking enabled : Tokens expire after 8 hours
Secret key : Loaded from environment variables via ConfigService
Automatic verification : Passport verifies signature and expiration
User Lookup
async validate ( payload : any ) {
if ( 'user_dni' in payload ) {
return await this . userService . findOneDni ( payload . user_dni );
}
return null ;
}
After token verification, the strategy:
Extracts user_dni from the JWT payload
Queries MongoDB for the user document
Returns the user object (attached to request) or null (authentication fails)
The validate() method is called automatically by Passport after successful JWT verification. The returned user object is attached to the request as req.user.
Auth Module Configuration
The AuthModule configures JWT settings and dependencies:
import { Module } from "@nestjs/common" ;
import { JwtStrategy } from "./strategies/jwt.strategy" ;
import { JwtModule } from "@nestjs/jwt" ;
import { ConfigModule , ConfigService } from "@nestjs/config" ;
import { JWT_SECRET } from "src/common/constants/settings.contant" ;
import { PassportModule } from "@nestjs/passport" ;
import { UserModule } from "../user/user.module" ;
@ Module ({
imports: [
PassportModule ,
ConfigModule ,
UserModule ,
JwtModule . registerAsync ({
inject: [ ConfigService ],
useFactory : ( config : ConfigService ) => ({
secret: config . get < string >( JWT_SECRET ),
signOptions: {
expiresIn: '8h'
}
})
})
],
providers: [ JwtStrategy ]
})
export class AuthModule { }
Module Dependencies
PassportModule : Provides Passport integration
ConfigModule : Injects environment configuration
UserModule : Provides UserService for user lookup
JwtModule : Handles token signing and verification
JWT Configuration
JwtModule . registerAsync ({
inject: [ ConfigService ],
useFactory : ( config : ConfigService ) => ({
secret: config . get < string >( JWT_SECRET ),
signOptions: {
expiresIn: '8h' // Tokens expire after 8 hours
}
})
})
Tokens expire after 8 hours . Clients must implement token refresh logic or re-authenticate when tokens expire.
User Service Integration
The UserService retrieves user data from MongoDB’s DeltaDispatch database:
import { Injectable } from "@nestjs/common" ;
import { InjectModel } from "@nestjs/mongoose" ;
import { Model } from "mongoose" ;
import { User , UserDocument } from "./schema/user.schema" ;
import { DELTA_DISPATCH_DB_NAME } from "src/common/constants/database.constant" ;
@ Injectable ()
export class UserService {
constructor (
@ InjectModel ( User . name , DELTA_DISPATCH_DB_NAME )
private readonly userModel : Model < UserDocument >,
) { }
async findOneDni ( dni : number ) : Promise < UserDocument | null > {
return this . userModel . findOne ({ user_dni: dni }). exec ();
}
}
User Lookup by DNI
The service queries MongoDB by the user’s DNI (Document Number ID):
async findOneDni ( dni : number ): Promise < UserDocument | null > {
return this.userModel.findOne({ user_dni : dni }).exec();
}
DNI (Documento Nacional de Identidad) is used as the unique identifier in the JWT payload and MongoDB user collection.
Token Structure
Walle JWT tokens contain the following payload:
{
"user_dni" : 12345678 ,
"iat" : 1709481600 ,
"exp" : 1709510400
}
Field Description user_dniUser’s DNI (used for lookup) iatIssued at timestamp (Unix time) expExpiration timestamp (iat + 8 hours)
Protecting Endpoints
Use the @UseGuards(AuthGuard('jwt')) decorator to protect routes:
import { Controller , Get , UseGuards } from '@nestjs/common' ;
import { AuthGuard } from '@nestjs/passport' ;
import { PointsService } from './points.service' ;
@ Controller ( 'points' )
export class PointsController {
constructor ( private readonly pointsService : PointsService ) { }
@ Get ()
@ UseGuards ( AuthGuard ( 'jwt' )) // Requires valid JWT
async findAll () {
// Only accessible with valid JWT token
return this . pointsService . findAll ();
}
}
Accessing the Authenticated User
Extract the user object from the request using the @Req() decorator:
import { Controller , Get , UseGuards , Req } from '@nestjs/common' ;
import { AuthGuard } from '@nestjs/passport' ;
import { Request } from 'express' ;
@ Controller ( 'points' )
export class PointsController {
@ Get ( 'my-points' )
@ UseGuards ( AuthGuard ( 'jwt' ))
async getMyPoints (@ Req () req : Request ) {
const user = req . user ; // User object from JWT validation
// Filter points by user's IMEI or other criteria
return this . pointsService . findByUser ( user );
}
}
Create a custom decorator for cleaner code:
import { createParamDecorator , ExecutionContext } from '@nestjs/common' ;
export const CurrentUser = createParamDecorator (
( data : unknown , ctx : ExecutionContext ) => {
const request = ctx . switchToHttp (). getRequest ();
return request . user ;
},
);
Usage:
import { Controller , Get , UseGuards } from '@nestjs/common' ;
import { AuthGuard } from '@nestjs/passport' ;
import { CurrentUser } from './decorators/current-user.decorator' ;
import { UserDocument } from './user/schema/user.schema' ;
@ Controller ( 'points' )
export class PointsController {
@ Get ( 'my-points' )
@ UseGuards ( AuthGuard ( 'jwt' ))
async getMyPoints (@ CurrentUser () user : UserDocument ) {
// Clean access to authenticated user
return this . pointsService . findByUser ( user );
}
}
Environment Configuration
Ensure your .env file contains the JWT secret:
# JWT Configuration
JWT_SECRET = your-secret-key-here
# MongoDB URIs
MONGO_DELTA_DISPATCH_URI = mongodb://localhost:27017/delta_dispatch
MONGO_AUTHSOFTWARE_URI = mongodb://localhost:27017/auth_software
Security Best Practices:
Use a strong, randomly generated secret (minimum 32 characters)
Never commit secrets to version control
Rotate secrets periodically in production
Use different secrets for development and production
Authentication Error Responses
Missing Token
{
"statusCode" : 401 ,
"message" : "Unauthorized"
}
Invalid Token
{
"statusCode" : 401 ,
"message" : "Unauthorized"
}
Expired Token
{
"statusCode" : 401 ,
"message" : "Unauthorized"
}
User Not Found
{
"statusCode" : 401 ,
"message" : "Unauthorized"
}
Passport returns a generic “Unauthorized” message for all authentication failures to prevent information leakage.
Example: Using the Authentication Infrastructure
The auth infrastructure is implemented but you’ll need to create your own auth controller with login endpoints. Below is an example of how to implement and use it.
1. Implement Login Endpoint
import { Controller , Post , Body , UnauthorizedException } from '@nestjs/common' ;
import { JwtService } from '@nestjs/jwt' ;
import { UserService } from '../user/user.service' ;
import * as bcrypt from 'bcrypt' ;
@ Controller ( 'auth' )
export class AuthController {
constructor (
private jwtService : JwtService ,
private userService : UserService
) {}
@ Post ( 'login' )
async login (@ Body () loginDto : { user_dni : number ; password : string }) {
const user = await this . userService . findOneDni ( loginDto . user_dni );
if ( ! user || ! await bcrypt . compare ( loginDto . password , user . user_password )) {
throw new UnauthorizedException ( 'Invalid credentials' );
}
const payload = { user_dni: user . user_dni };
return {
access_token: this . jwtService . sign ( payload )
};
}
}
2. Protect Endpoints with @Auth()
import { Controller , Get } from '@nestjs/common' ;
import { Auth } from 'src/common/decorators' ;
import { User } from 'src/common/decorators' ;
@ Controller ( 'protected' )
export class MyController {
@ Get ()
@ Auth () // Requires JWT authentication
getData (@ User () user ) {
// user is populated by JwtStrategy.validate()
return {
message: 'Authenticated' ,
userDni: user . user_dni
};
}
}
3. JWT Token Flow
Client sends credentials to your login endpoint
Server validates and signs JWT with user_dni in payload
Client receives JWT token (valid for 8 hours)
Client includes token in Authorization: Bearer <token> header
JwtStrategy.validate() looks up user by DNI from payload
Request proceeds with user object populated
Testing Authentication
Test the JwtStrategy and guards:
import { Test } from '@nestjs/testing' ;
import { JwtStrategy } from './strategies/jwt.strategy' ;
import { UserService } from '../user/user.service' ;
import { ConfigService } from '@nestjs/config' ;
describe ( 'JwtStrategy' , () => {
let strategy : JwtStrategy ;
let userService : UserService ;
beforeEach ( async () => {
const module = await Test . createTestingModule ({
providers: [
JwtStrategy ,
{
provide: UserService ,
useValue: { findOneDni: jest . fn () }
},
{
provide: ConfigService ,
useValue: { get: jest . fn (). mockReturnValue ( 'test-secret' ) }
}
]
}). compile ();
strategy = module . get < JwtStrategy >( JwtStrategy );
userService = module . get < UserService >( UserService );
});
it ( 'should validate user with valid DNI in payload' , async () => {
const mockUser = { user_dni: 12345678 , user_name: 'Test' };
jest . spyOn ( userService , 'findOneDni' ). mockResolvedValue ( mockUser as any );
const result = await strategy . validate ({ user_dni: 12345678 });
expect ( result ). toEqual ( mockUser );
expect ( userService . findOneDni ). toHaveBeenCalledWith ( 12345678 );
});
it ( 'should return null if DNI not in payload' , async () => {
const result = await strategy . validate ({ sub: 'some-id' });
expect ( result ). toBeNull ();
});
});
Additional Security Considerations
HTTPS Only Always use HTTPS in production to prevent token interception
Token Storage Store tokens securely on the client (httpOnly cookies or secure storage)
Rate Limiting Implement rate limiting on login endpoints to prevent brute force attacks
Token Refresh Implement refresh tokens for better UX with 8-hour expiration