π Intro
Pernah bingung bedanya login sama izin akses? π€
Login = siapa lo (auth), izin akses = boleh ngapain aja (role).
Di NestJS, kita bisa handle itu pake JWT buat login dan Role-Based Guard buat nentuin siapa boleh ngakses apa. π―
Di mini project ini, kita bakal bikin step by step: login dapet token JWT, terus pake role guard biar cuma role tertentu yang bisa masuk ke endpoint tertentu. Simple, clean, dan gampang dipraktekkin. π
1οΈβ£ Install Package Dulu
npm install @nestjs/jwt @nestjs/passport passport passport-jwt
2οΈβ£ Struktur Folder Sederhana
src/
β£ auth/
β β£ auth.module.ts
β β£ auth.service.ts
β β£ auth.controller.ts
β β£ jwt.strategy.ts
β β£ roles.decorator.ts
β β roles.guard.ts
β£ users/
β β users.service.ts
β£ app.module.ts
β main.ts
3οΈβ£ Users Service (Dummy Data)
// src/users/users.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class UsersService {
private users = [
{ id: 1, username: 'alice', password: '1234', role: 'user' },
{ id: 2, username: 'bob', password: '1234', role: 'admin' },
];
async findOne(username: string) {
return this.users.find(user => user.username === username);
}
}
4οΈβ£ Auth Service
// src/auth/auth.service.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UsersService } from '../users/users.service';
@Injectable()
export class AuthService {
constructor(
private usersService: UsersService,
private jwtService: JwtService,
) {}
async validateUser(username: string, pass: string) {
const user = await this.usersService.findOne(username);
if (user && user.password === pass) {
const { password, ...result } = user;
return result;
}
return null;
}
async login(user: any) {
if (!user) throw new UnauthorizedException('Invalid credentials');
const payload = { username: user.username, sub: user.id, role: user.role };
return {
access_token: this.jwtService.sign(payload),
};
}
}
5οΈβ£ Auth Controller
// src/auth/auth.controller.ts
import { Controller, Post, Body } from '@nestjs/common';
import { AuthService } from './auth.service';
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
@Post('login')
async login(@Body() body: { username: string; password: string }) {
const user = await this.authService.validateUser(body.username, body.password);
return this.authService.login(user);
}
}
6οΈβ£ JWT Strategy
// src/auth/jwt.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_SECRET || 'superSecretKey',
});
}
async validate(payload: any) {
return { userId: payload.sub, username: payload.username, role: payload.role };
}
}
7οΈβ£ Roles Decorator + Guard
// src/auth/roles.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
// src/auth/roles.guard.ts
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 { user } = context.switchToHttp().getRequest();
return requiredRoles.includes(user.role);
}
}
8οΈβ£ Auth Module
// src/auth/auth.module.ts
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { JwtStrategy } from './jwt.strategy';
import { UsersService } from '../users/users.service';
@Module({
imports: [
PassportModule,
JwtModule.register({
secret: process.env.JWT_SECRET || 'superSecretKey',
signOptions: { expiresIn: '1h' },
}),
],
providers: [AuthService, JwtStrategy, UsersService],
controllers: [AuthController],
})
export class AuthModule {}
9οΈβ£ App Module + Protected Route
// src/app.module.ts
import { Module } from '@nestjs/common';
import { AuthModule } from './auth/auth.module';
import { APP_GUARD } from '@nestjs/core';
import { RolesGuard } from './auth/roles.guard';
@Module({
imports: [AuthModule],
providers: [
{ provide: APP_GUARD, useClass: RolesGuard }, // apply global guard
],
})
export class AppModule {}
π Contoh Protected Endpoint
Bisa tambahin di app.controller.ts:
import { Controller, Get, UseGuards, Request } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { Roles } from './auth/roles.decorator';
@Controller()
export class AppController {
@Get('profile')
@UseGuards(AuthGuard('jwt'))
getProfile(@Request() req) {
return req.user; // user dari JWT
}
@Get('admin')
@UseGuards(AuthGuard('jwt'))
@Roles('admin')
getAdminData() {
return { message: 'Hanya admin yang bisa lihat ini π' };
}
}
π Cara Coba
1. Jalankan server:
npm run start:dev
2. Login:
POST http://localhost:3000/auth/login
body: { "username": "bob", "password": "1234" }
β dapet token.
3. Akses endpoint pakai header:
Authorization: Bearer <token>
/profileβ bisa diakses semua user login./adminβ cuma bisa diakses user roleadmin.
π― Kesimpulan
- Authentication (login pakai JWT) = tiket masuk.
- Authorization (role guard) = gelang VIP.
- Dengan project mini ini, lo bisa langsung coba konsep authN + authZ di NestJS.

