JWT Authentication — Access Token
Muammodan boshlaylik
Foydalanuvchi login qildi. Endi /profile ga so'rov yubordi. Server kim ekanini qanday biladi?
Session — serverda saqlanadi, masshtablanmaydi. Cookie — brauzer uchun yaxshi, mobile uchun noqulay.
JWT — boshqacha ishlaydi: server hech narsa saqlamaydi. Token foydalanuvchida. Har so'rovda u tokenni yuboradi — server faqat imzoni tekshiradi.
JWT nima?
JWT (JSON Web Token) — ma'lumotni xavfsiz uzatish uchun ochiq standart. Uch qismdan iborat, nuqta bilan ajratilgan:
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI2NGEiLCJlbWFpbCI6Imphc3VyQG1haWwuY29tIiwiaWF0IjoxNzA1MDAwMDAwfQ.xK9mP2vL3qR8nT5wY1uA6bZ4cE7fH0iJ
│──────────────────│ │────────────────────────────────────────────────────────────────────│ │──────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
Header Payload Signature
(algoritm: HS256) (sub, email, iat — ma'lumotlar) (header + payload + secret bilan imzolangan)
Header — algoritm turi.
Payload — foydalanuvchi ma'lumotlari (ID, rol, email). Bu ochiq — har kim o'qiy oladi. Shuning uchun maxfiy ma'lumotlar yozilmaydi.
Signature — server sirli kalit (JWT_SECRET) bilan imzolaydi. Bu qism o'zgartirilsa — server sezadi.
Hayotiy o'xshatma: davlat ID kartasi. Karta sizda — har joyda ko'rsatasiz. Davlat (server) kartani yaratgan, imzolagan. Kimdir uni soxtalashtirmoqchi bo'lsa — imzo mos kelmaydi. Davlat kartani saqlamaydi, faqat imzoni tekshiradi.
O'rnatish
npm install @nestjs/jwt passport-jwt
npm install -D @types/passport-jwt
JWT ni AuthModule ga qo'shish
// src/auth/auth.module.ts
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { LocalStrategy } from './strategies/local.strategy';
import { JwtStrategy } from './strategies/jwt.strategy';
import { UsersModule } from '../users/users.module';
@Module({
imports: [
PassportModule,
UsersModule,
JwtModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
secret: config.get('JWT_SECRET'), // .env dan
signOptions: { expiresIn: config.get('JWT_EXPIRES_IN') }, // '15m', '7d'
}),
}),
],
providers: [AuthService, LocalStrategy, JwtStrategy],
controllers: [AuthController],
exports: [AuthService],
})
export class AuthModule {}
registerAsync — ConfigService tayyor bo'lgandan keyin JWT sozlanadi. Bu .env dan qiymat olish uchun kerak.
AuthService — token yaratish
// src/auth/auth.service.ts
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UsersService } from '../users/users.service';
import * as bcrypt from 'bcrypt';
@Injectable()
export class AuthService {
constructor(
private usersService: UsersService,
private jwtService: JwtService,
) {}
async validateUser(email: string, password: string) {
const user = await this.usersService.findByEmail(email);
if (!user) return null;
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) return null;
const { password: _, ...result } = user.toObject();
return result;
}
// Login — token yaratish
login(user: any) {
const payload = {
sub: user._id, // "subject" — standart JWT claim
email: user.email,
role: user.role,
};
return {
accessToken: this.jwtService.sign(payload), // ← token yaratish
user: {
id: user._id,
name: user.name,
email: user.email,
},
};
}
}
Nima uchun sub? JWT standartida sub (subject) — tokenning kimga tegishli ekanini bildiradi. Bu konvensiya — texnik majburiyat emas, lekin standart.
Payload ga nima qo'shiladi? Faqat tez-tez keradiganlar: id, email, role. Parol, kredit karta — hech qachon. Payload Base64 — har kim ochib o'qiy oladi.
JWT Strategiya — tokenni tekshirish
// src/auth/strategies/jwt.strategy.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { ConfigService } from '@nestjs/config';
import { UsersService } from '../../users/users.service';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
private config: ConfigService,
private usersService: UsersService,
) {
super({
// Tokenni Authorization: Bearer <token> headerdan oladi
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
// Muddati o'tgan tokenlarni rad etadi
ignoreExpiration: false,
// Imzoni tekshirish uchun secret
secretOrKey: config.get('JWT_SECRET'),
});
}
// Passport token imzosini tekshirgandan keyin shu metod chaqiriladi
// payload — dekodlangan token ichidagi ma'lumotlar
async validate(payload: { sub: string; email: string; role: string }) {
// Foydalanuvchi hali bazada bormi?
const user = await this.usersService.findById(payload.sub);
if (!user) throw new UnauthorizedException('Foydalanuvchi topilmadi');
return { id: payload.sub, email: payload.email, role: payload.role };
// ↑ req.user ga qo'yiladi
}
}
Nima uchun bazadan tekshirish kerak? Foydalanuvchi o'chirilgan bo'lishi mumkin. Token hali amal qiladi lekin foydalanuvchi yo'q. Shuning uchun har so'rovda bazadan tekshiriladi.
JWT Guard va Controller
// src/auth/guards/jwt-auth.guard.ts
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}
// src/auth/auth.controller.ts
import { Controller, Post, Get, UseGuards, Req, Body } from '@nestjs/common';
import { LocalAuthGuard } from './guards/local-auth.guard';
import { JwtAuthGuard } from './guards/jwt-auth.guard';
import { AuthService } from './auth.service';
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
// POST /auth/login — email/parol → token
@Post('login')
@UseGuards(LocalAuthGuard) // ← email/parolni tekshiradi
login(@Req() req) {
return this.authService.login(req.user); // ← token yaratib qaytaradi
}
// GET /auth/me — kim ekanini ko'rish
@Get('me')
@UseGuards(JwtAuthGuard) // ← JWT tokenni tekshiradi
me(@Req() req) {
return req.user; // ← JwtStrategy.validate() dan kelgan
}
}
To'liq so'rov oqimi
1. Login:
POST /auth/login { email, password }
│
▼
LocalAuthGuard → LocalStrategy.validate()
│ ✓
▼
AuthService.login(user) → { accessToken: "eyJ..." }
│
▼
Klient tokenni saqlaydi (localStorage, cookie)
2. Himoyalangan so'rov:
GET /profile
Authorization: Bearer eyJ...
│
▼
JwtAuthGuard → JwtStrategy
│ imzoni tekshiradi ✓
▼
JwtStrategy.validate(payload) → req.user
│
▼
Controller → req.user ma'lumotlari
Global Guard — hamma endpointni himoya qilish
Har controllerga @UseGuards(JwtAuthGuard) yozish o'rniga — global qilish:
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { JwtAuthGuard } from './auth/guards/jwt-auth.guard';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Barcha endpointlar himoyalangan
const reflector = app.get(Reflector);
app.useGlobalGuards(new JwtAuthGuard(reflector));
await app.listen(3000);
}
Ba'zi endpointlar ochiq qolishi kerak (login, register). Buning uchun @Public() dekorator:
// src/auth/decorators/public.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
// jwt-auth.guard.ts — global guard
import { ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
import { IS_PUBLIC_KEY } from '../decorators/public.decorator';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
constructor(private reflector: Reflector) {
super();
}
canActivate(context: ExecutionContext) {
// @Public() dekorator bo'lsa — tekshirmasdan o'tkazadi
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
if (isPublic) return true;
return super.canActivate(context); // ← JWT tekshirish
}
}
// auth.controller.ts — login va register ochiq
@Post('login')
@Public() // ← token kerak emas
@UseGuards(LocalAuthGuard)
login(@Req() req) { ... }
@Post('register')
@Public()
register(@Body() dto: CreateUserDto) { ... }
Tez-tez uchraydigan xatolar
JWT_SECRET kalta yoki oddiy
# ❌ Zaif secret — brute force bilan topish mumkin
JWT_SECRET=secret
# ✅ Kamida 32 ta tasodifiy belgi
JWT_SECRET=aK9#mP2$vL3@qR8&nT5!wY1*uA6^bZ4
# Generatsiya: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
Token muddati juda uzun
# ❌ 30 kun — token o'g'irlansa 30 kun davomida ishlaydi
JWT_EXPIRES_IN=30d
# ✅ Access token qisqa, refresh token uzun (keyingi dars)
JWT_EXPIRES_IN=15m # 15 daqiqa
REFRESH_TOKEN_EXPIRES_IN=7d # 7 kun
Payload ga maxfiy ma'lumot qo'shish
// ❌ Payload ochiq — kim ham ko'ra oladi
const payload = {
sub: user._id,
password: user.password, // ← HECH QACHON
cardNumber: '4111...', // ← HECH QACHON
};
// ✅ Faqat zaruriy minimum
const payload = {
sub: user._id,
email: user.email,
role: user.role,
};
Xulosa
JWT — server hech narsa saqlamaydi, foydalanuvchi tokenni o'zi saqlaydi. Har so'rovda token yuboriladi, server imzoni tekshiradi.
Eslab qoling:
JwtModule.registerAsync—.envdan secret olishExtractJwt.fromAuthHeaderAsBearerToken()—Authorization: Bearerheaderdanvalidate(payload)— token tekshirilgandan keyin chaqiriladi@Public()— global guard dan ochiq endpointlar uchun- JWT_SECRET — kamida 32 belgi, tasodifiy
- Access token — qisqa muddatli (15 daqiqa)
Keyingi dars
27-dars: Refresh Token + Token Rotation