8.9-bob: Autentifikatsiya — JWT, Passport, cookie, refresh token
8-QISM — NestJS (chuqur) · 9-mavzu
1. Kirish va motivatsiya
Endi NestJS'ning eng muhim, eng ko'p so'raladigan mavzusiga keldik — autentifikatsiya. Bu — har NestJS ilovaning yuragi, va karyerangizda deyarli har loyihada quradigan tizim. 5.15-5.16 boblarda auth nazariyasini (bcrypt, JWT, refresh token, cookie) Express'da o'rgandik; endi NestJS'da — Passport strategiyalari, guards 8.6-bob, va to'liq, professional, production-tayyor tizim sifatida quramiz. Bu bob — 8.6 (guards), 8.7 (RBAC), 8.5 (DTO) — hammasini birlashtiradi.
NestJS auth — Passport (Node.js'ning standart auth kutubxonasi) ustiga qurilgan. Passport strategiya (strategy) tushunchasi bilan ishlaydi: har strategiya — ma'lum kredentsialni tekshirish usuli (local — email/parol; JWT — token; OAuth — Google). NestJS @nestjs/passport bilan buni dekorator/guard'ga aylantiradi: LocalStrategy (login), JwtStrategy (himoyalangan endpoint), JwtRefreshStrategy (refresh). Strategiya foydalanuvchini tekshiradi, req.userga qo'yadi (8.6: guard).
Bu bob — chuqur va batafsil: auth oqimi, Passport strategiyalari (local/jwt/refresh), JwtModule, access + refresh token (5.16 — rotatsiya, hashlash, cookie), bcrypt 5.15-bob, va to'liq auth modul. Va eng muhimi — xavfsizlik (14): qisqa access, refresh hashlash, httpOnly cookie, bekor qilish. Bu bob: auth oqimi, strategiyalar, JWT modul, refresh, cookie, guards — chuqur. Bu — RBAC 8.7-bob bilan to'liq auth tizimi.
O'xshatish: Passport — xavfsizlik kompaniyasi (5.15: klubga kirish). Har strategiya — turli tekshirish usuli: Local — eshikdagi pasport nazorati (email/parol bir marta — login); JWT — bilaguzuk nazorati (har xonada bilaguzuk — token tekshirish); Refresh — bilaguzukni yangilash (eskirsa, yangi). NestJS — bu kompaniyani tashkillaydi (guard, strategiya, modul). Siz faqat "qaysi eshikda qaysi nazorat" deysiz (
@UseGuards).
Nega muhim?
- Har ilova yuragi — auth'siz jiddiy ilova yo'q.
- Passport — NestJS auth standarti (strategiyalar).
- Production xavfsizlik (14) — refresh, cookie, hashlash.
- Birlashma — guards 8.6-bob + RBAC 8.7-bob + DTO 8.5-bob — to'liq tizim.
2. Nazariya — chuqur tushuntirish
2.1. NestJS auth — Passport asosi
Paketlar:
@nestjs/passport @nestjs/jwt passport passport-jwt passport-local bcrypt
Passport oqimi:
So'rov Guard STRATEGY (tekshiradi) validate() req.user handler
Strategiya — kredentsialni tekshirish usuli:
- LocalStrategy: email/parol (login — 2.4)
- JwtStrategy: access token (himoyalangan — 2.6)
- JwtRefreshStrategy: refresh token (yangilash — 2.9)Passport — Node.js auth standarti; strategiya — tekshirish usuli (local/jwt/oauth). NestJS
@nestjs/passport— Passport'ni guard/dekoratorga aylantiradi 8.6-bob. Strategiyavalidate()— foydalanuvchini tekshiradi, qaytaradireq.user(guard qo'yadi). Bu — auth'ning NestJS naqshi.
2.1.1. Session vs JWT — qaysi birini tanlash
Auth holatini (kim kirgan) saqlashning ikki asosiy yondashuvi bor. NestJS Passport har ikkalasini ham qo'llab-quvvatlaydi (passport-local + session yoki passport-jwt — stateless). Farqni tushunmasangiz, arxitektura tanlashda adashasiz.
SESSION (server holatni saqlaydi — stateful):
Login server sessiya yaratadi (xotira/Redis) sessionId cookie'da
Har so'rov cookie'dagi sessionId server sessiyani qidiradi user
Server bekor qila oladi (sessiyani o'chirish — darhol logout)
Cookie kichik (faqat id)
Server holat saqlaydi (Redis/DB kerak — gorizontal masshtabda ulashish)
CSRF himoyasi kerak (cookie avtomatik yuboriladi)
JWT (token o'zi holatni saqlaydi — stateless):
Login server imzolangan token beradi (payload ichida user id/rol)
Har so'rov token server IMZONI tekshiradi (DB'ga bormaydi) user
Stateless — server holat saqlamaydi (masshtab oson, mikroservis)
Payload'da ma'lumot (rol — RBAC uchun 8.7)
Bekor qilish qiyin (token muddatigacha yaroqli — shuning uchun qisqa access)
Payload katta (har so'rovda yuboriladi)Session vs JWT — session server holatni saqlaydi (bekor qilish oson, lekin Redis/masshtab yuki); JWT stateless (masshtab oson, lekin bekor qilish qiyin). Zamonaviy API/SPA/mobil — odatda JWT (stateless, mikroservis-do'st). Bekor qilish muammosini qisqa access + refresh rotatsiya hal qiladi 2.9-bob — bu bobning asosiy strategiyasi. Monolit server-render ilovada session ham to'g'ri tanlov. Biz JWT'ni chuqur quramiz.
2.2. Auth oqimi (umumiy rasm)
RO'YXATDAN O'TISH (signup):
POST /auth/signup validatsiya 8.5-bob email band? parol hash (bcrypt — 5.15)
DB'ga saqla token(lar) ber
LOGIN:
POST /auth/login LocalStrategy (email/parol tekshir — 2.4)
access + refresh token 2.8-bob cookie 2.10-bob
HIMOYALANGAN:
GET /profile JwtStrategy (access token tekshir — 2.6) req.user handler
REFRESH:
POST /auth/refresh JwtRefreshStrategy yangi access (+ refresh rotatsiya — 2.9)Auth oqimi (5.15, 5.16) — NestJS'da Passport strategiyalari bilan: signup (hash + token), login (LocalStrategy), himoyalangan (JwtStrategy), refresh (JwtRefreshStrategy). Har bosqich — strategiya + guard. Toza, tashkillangan.
2.3. AuthModule sozlash (JwtModule)
// auth.module.ts
import { JwtModule } from "@nestjs/jwt";
import { PassportModule } from "@nestjs/passport";
@Module({
imports: [
UsersModule, // user service (8.3)
PassportModule,
JwtModule.registerAsync({ // async (ConfigService — 8.3: 2.4)
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
secret: config.get("JWT_ACCESS_SECRET"), // .env (5.8, 14)
signOptions: { expiresIn: "15m" }, // qisqa access (5.16: 2.9)
}),
}),
],
controllers: [AuthController],
providers: [AuthService, LocalStrategy, JwtStrategy, JwtRefreshStrategy],
})
export class AuthModule {}AuthModule —
JwtModule.registerAsync(secret env'dan — 14; qisqa expiresIn — 5.16: 2.9),PassportModule, strategiyalar (provider). UsersModule import (user service). Bu — auth'ning markaziy moduli. Access/refresh alohida secret (5.16: 2.11 — 2.9).
2.4. LocalStrategy (login — email/parol)
import { Strategy } from "passport-local";
import { PassportStrategy } from "@nestjs/passport";
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super({ usernameField: "email" }); // email bilan (default username)
}
async validate(email: string, parol: string): Promise<any> {
const user = await this.authService.validateUser(email, parol); // tekshir (5.15)
if (!user) throw new UnauthorizedException("Email yoki parol noto'g'ri"); // umumiy (5.15: 2.18)
return user; // req.user
}
}LocalStrategy — login uchun (email/parol).
validate(email, parol)—authService.validateUser(bcrypt.compare — 5.15) foydalanuvchi yoki xato.usernameField: "email"(default username o'rniga). Umumiy xato ("email yoki parol" — qaysi xato oshkor qilma — 5.15: 2.18, 14).LocalAuthGuardbilan ishlatiladi.
2.5. validateUser (bcrypt — 5.15)
@Injectable()
export class AuthService {
constructor(
private usersService: UsersService,
private jwtService: JwtService,
private config: ConfigService,
) {}
async validateUser(email: string, parol: string): Promise<any> {
const user = await this.usersService.emailBilan(email); // parol bilan (8.3)
if (user && await bcrypt.compare(parol, user.parol)) { // bcrypt (5.15: 2.5)
const { parol, ...natija } = user; // parolsiz (14)
return natija;
}
return null;
}
}validateUser — bcrypt.compare (5.15: 2.5) bilan parol tekshirish. Foydalanuvchini parolsiz qaytaradi (14).
usersService.emailBilan— parol bilan oladi (select: false override — 8.3). Bu — login'ning yuragi (parol tekshirish).
2.6. JwtStrategy (himoyalangan — access token)
import { Strategy, ExtractJwt } from "passport-jwt";
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private config: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), // "Bearer X" (5.15)
secretOrKey: config.get("JWT_ACCESS_SECRET"), // (14)
});
}
async validate(payload: any) {
return { id: payload.sub, email: payload.email, rol: payload.rol }; // req.user
}
}Diqqat: validate() faqat imzo va muddat (exp) tekshiruvidan keyin chaqiriladi — Passport buni avtomatik bajaradi (secretOrKey bilan). Ya'ni bu yerga yetib kelgan payload allaqachon ishonchli; siz faqat undan kerakli maydonlarni tanlab olasiz. Xohlasangiz, DB'dan foydalanuvchini qayta yuklashingiz mumkin (bloklangan/o'chirilgan user tekshiruvi), lekin bu har so'rovda DB so'rovi qo'shadi (stateless afzalligini kamaytiradi).
Token'ni header o'rniga (yoki header'dan tashqari) cookie'dan olish uchun ExtractJwt.fromExtractors ishlatiladi — global guard + cookie'dagi access token ssenariysida foydali:
super({
jwtFromRequest: ExtractJwt.fromExtractors([
(req) => req.cookies?.accessToken, // avval cookie'dan
ExtractJwt.fromAuthHeaderAsBearerToken(), // bo'lmasa — header
]),
secretOrKey: config.get("JWT_ACCESS_SECRET"),
});JwtStrategy — himoyalangan endpoint uchun (access token).
ExtractJwt.fromAuthHeaderAsBearerToken()— header'dan token 5.15-bob; yoki cookie'dan (2.10 —fromExtractors).secretOrKey— verify (avtomatik: imzo +exp).validate(payload)— payload'dan userreq.user(8.6, 8.7: RBAC uchun rol).JwtAuthGuardbilan.
2.7. Guard'lar (Local va Jwt)
import { AuthGuard } from "@nestjs/passport";
// Strategiyaga mos guard (AuthGuard("strategiya nomi"))
@Injectable()
export class LocalAuthGuard extends AuthGuard("local") {} // LocalStrategy
@Injectable()
export class JwtAuthGuard extends AuthGuard("jwt") {} // JwtStrategy
// Ishlatish
@UseGuards(LocalAuthGuard) // login (LocalStrategy ishga tushadi)
@Post("login")
login(@Request() req) { return this.authService.login(req.user); }
@UseGuards(JwtAuthGuard) // himoyalangan (JwtStrategy)
@Get("profile")
profile(@CurrentUser() user) { return user; }Guard'lar —
AuthGuard("strategiya")(Passport guard).LocalAuthGuard(login — LocalStrategy ishga tushadi);JwtAuthGuard(himoyalangan — JwtStrategy).@UseGuards(LocalAuthGuard)strategiya tekshiradireq.user. Global JwtAuthGuard + @Public (8.6: Misol 10) — ko'p ishlatiladi.
2.8. Access + Refresh token (5.16)
@Injectable()
export class AuthService {
async login(user: any) {
const tokens = await this.tokenlarYarat(user);
await this.refreshSaqla(user.id, tokens.refreshToken); // hash DB'ga (2.9, 5.16: 2.8)
return tokens;
}
private async tokenlarYarat(user: any) {
const payload = { sub: user.id, email: user.email, rol: user.rol };
const [accessToken, refreshToken] = await Promise.all([
this.jwtService.signAsync(payload, { // access (qisqa — 5.16: 2.9)
secret: this.config.get("JWT_ACCESS_SECRET"),
expiresIn: "15m",
}),
this.jwtService.signAsync(payload, { // refresh (uzoq, alohida secret — 5.16: 2.11)
secret: this.config.get("JWT_REFRESH_SECRET"),
expiresIn: "7d",
}),
]);
return { accessToken, refreshToken };
}
}Access + refresh 5.16-bob: access (qisqa — 15m, header'da); refresh (uzoq — 7d, alohida secret — 5.16: 2.11, httpOnly cookie + DB hash — 2.9).
jwtService.signAsync— token yasash. Bu — 5.16 strategiyasining NestJS amaliyoti.
2.9. JwtRefreshStrategy + rotatsiya (5.16)
// Refresh strategiya (cookie'dan token + DB tekshir)
@Injectable()
export class JwtRefreshStrategy extends PassportStrategy(Strategy, "jwt-refresh") {
constructor(private config: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromExtractors([(req) => req.cookies?.refreshToken]), // cookie (2.10)
secretOrKey: config.get("JWT_REFRESH_SECRET"),
passReqToCallback: true, // req'ni validate'ga (token kerak)
});
}
validate(req: Request, payload: any) {
const refreshToken = req.cookies?.refreshToken;
return { ...payload, refreshToken }; // token DB tekshiruvi uchun (service)
}
}// Refresh service (rotatsiya + reuse detection — 5.16: 2.4, 2.5)
async refresh(userId: number, refreshToken: string) {
const user = await this.usersService.bitta(userId);
const saqlangan = await this.usersService.refreshHash(userId); // DB hash (5.16: 2.8)
if (!saqlangan || !(await bcrypt.compare(refreshToken, saqlangan))) {
await this.usersService.refreshBekor(userId); // reuse bekor (5.16: 2.5)
throw new ForbiddenException("Ruxsat yo'q");
}
const tokens = await this.tokenlarYarat(user);
await this.refreshSaqla(userId, tokens.refreshToken); // rotatsiya (yangi refresh — 5.16: 2.4)
return tokens;
}Refresh (5.16: 2.4, 2.5): JwtRefreshStrategy (cookie'dan token), service DB hash'ni tekshiradi (5.16: 2.8), rotatsiya (yangi refresh — eski bekor), reuse detection (bekor token barcha bekor — 5.16: 2.5). Bu — 5.16'ning to'liq NestJS amaliyoti (xavfsiz).
2.10. Cookie (httpOnly — 5.15: 2.11)
// Refresh'ni httpOnly cookie'da (5.15: 2.11, 14)
@UseGuards(LocalAuthGuard)
@Post("login")
async login(@Request() req, @Res({ passthrough: true }) res: Response) {
const tokens = await this.authService.login(req.user);
res.cookie("refreshToken", tokens.refreshToken, { // httpOnly cookie (5.15: 2.11)
httpOnly: true, // JS o'qiy olmaydi (XSS — 14)
secure: this.config.get("NODE_ENV") === "production", // HTTPS
sameSite: "lax", // CSRF (14)
maxAge: 7 * 24 * 60 * 60 * 1000,
path: "/auth", // faqat auth route
});
return { accessToken: tokens.accessToken }; // access — javobda (5.16: 2.7)
}Cookie (5.15: 2.11): refresh — httpOnly cookie (JS ko'rmaydi — XSS himoyasi — 14); access — javobda (frontend xotirada — 5.16: 2.7).
@Res({ passthrough: true })— NestJS'da response (return ham ishlaydi).cookie-parsermiddleware kerak 5.15-bob. httpOnly + Secure + SameSite (5.15: 2.11, 14).
2.11. Signup (bcrypt — 5.15)
async signup(dto: SignupDto) {
const mavjud = await this.usersService.emailBilan(dto.email);
if (mavjud) throw new ConflictException("Email band"); // 409 (8.1)
const parolHash = await bcrypt.hash(dto.parol, 12); // hash (5.15: 2.5)
const user = await this.usersService.yarat({ ...dto, parol: parolHash });
const tokens = await this.tokenlarYarat(user);
await this.refreshSaqla(user.id, tokens.refreshToken);
return { accessToken: tokens.accessToken, refreshToken: tokens.refreshToken };
}Signup (5.15: 2.7): email band tekshir (409), parol hash (bcrypt cost 12 — 5.15: 2.5), DB'ga saqla, token ber. DTO validatsiya (8.5 — kuchli parol). Yoki user entity hook'da hash (8.3: Misol 2) — DRY.
2.12. Global JwtAuthGuard + @Public (8.6 takrori)
// app.module.ts — global auth (8.6: 2.12)
{ provide: APP_GUARD, useClass: JwtAuthGuard }, // hamma endpoint himoyalangan
// JwtAuthGuard — @Public istisno (8.6: Misol 10)
@Injectable()
export class JwtAuthGuard extends AuthGuard("jwt") {
constructor(private reflector: Reflector) { super(); }
canActivate(context: ExecutionContext) {
const isPublic = this.reflector.getAllAndOverride("isPublic", [
context.getHandler(), context.getClass(),
]);
if (isPublic) return true; // @Public — token shart emas
return super.canActivate(context); // JwtStrategy
}
}
// @Public() @Post("login") — token shart emas (login/signup)@Public() dekoratorning o'zi — oddiy SetMetadata (8.6: 2.9). Guard uni Reflector bilan o'qiydi:
// public.decorator.ts
import { SetMetadata } from "@nestjs/common";
export const IS_PUBLIC_KEY = "isPublic";
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true); // metadata qo'yadi (8.6)Global JwtAuthGuard + @Public (8.6: Misol 10): default hamma endpoint himoyalangan;
@Public()(login/signup) — istisno. Bu — eng xavfsiz (unutilgan endpoint ham himoyalangan). RBAC 8.7-bob — JwtAuthGuard'dan keyin RolesGuard.
2.13. Logout va token bekor qilish (5.16: 2.6)
@UseGuards(JwtAuthGuard)
@Post("logout")
async logout(@CurrentUser() user, @Res({ passthrough: true }) res: Response) {
await this.usersService.refreshBekor(user.id); // DB'dan refresh bekor (5.16: 2.6)
res.clearCookie("refreshToken", { path: "/auth" }); // cookie tozalash (5.15)
return { message: "Chiqdingiz" };
}Logout (5.16: 2.6): refresh'ni DB'dan bekor (revoke) + cookie tozalash. Access — stateless (bekor qilib bo'lmaydi, lekin qisqa — 5.16: 2.6). Darhol kerak bo'lsa — Redis blacklist (5.16: 2.6, 8.15). "Barcha qurilmadan chiq" — barcha refresh bekor 5.16-bob.
2.14. OAuth (Google — qisqacha, bonus)
// Google OAuth strategiyasi (passport-google-oauth20)
@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, "google") {
constructor(config: ConfigService) {
super({
clientID: config.get("GOOGLE_CLIENT_ID"),
clientSecret: config.get("GOOGLE_SECRET"),
callbackURL: config.get("GOOGLE_CALLBACK"),
scope: ["email", "profile"],
});
}
async validate(accessToken, refreshToken, profile) {
return { email: profile.emails[0].value, ism: profile.displayName }; // req.user
}
}
// @UseGuards(AuthGuard("google")) — Google'ga yo'naltirishOAuth (Google/GitHub — passport-google-oauth20) — Passport strategiyasi (boshqa). Foydalanuvchi Google bilan kiradi; strategiya profilni oladi; siz user yaratasiz/topasiz + JWT berasiz. Bu keng tarqalgan yondashuv (social login). Passport'ning kuchi — ko'p strategiya.
2.15. Auth xavfsizligi (14 — eng muhim)
Parol bcrypt (cost 12); ochiq saqlama (5.15: 2.2, 14)
Login xato UMUMIY ("email yoki parol" — 5.15: 2.18)
Access qisqa (15m); refresh uzoq (7d) + alohida secret (5.16: 2.9, 2.11)
Refresh httpOnly cookie + DB HASH (localStorage emas — 5.16: 2.7, 2.8, 14)
Refresh rotatsiya + reuse detection (5.16: 2.4, 2.5)
JWT secret .env'da, kuchli (32+ belgi — 14)
Login/refresh rate limiting (brute-force — 8.16: Throttler, 5.20)
Global JwtAuthGuard + @Public (himoyalangan default — 2.12)3. Sintaksis — tez ma'lumotnoma
// Module (2.3)
JwtModule.registerAsync({ useFactory: (c) => ({ secret, signOptions: { expiresIn: "15m" } }) })
// Strategiyalar (2.4, 2.6, 2.9)
class LocalStrategy extends PassportStrategy(Strategy) { validate(email, parol) {} }
class JwtStrategy extends PassportStrategy(Strategy) { validate(payload) {} }
// Guard (2.7)
class JwtAuthGuard extends AuthGuard("jwt") {}
@UseGuards(JwtAuthGuard)
// Token 2.8-bob: jwtService.signAsync(payload, { secret, expiresIn })
// Cookie 2.10-bob: res.cookie("refreshToken", token, { httpOnly: true, ... })4. Batafsil kod namunalari
Misol 1 — AuthModule (to'liq — 2.3)
// 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 { UsersModule } from "../users/users.module";
@Module({
imports: [
UsersModule,
PassportModule,
JwtModule.registerAsync({ // access token modul (2.3)
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
secret: config.get("JWT_ACCESS_SECRET"), // (14)
signOptions: { expiresIn: config.get("JWT_ACCESS_EXPIRES") || "15m" },
}),
}),
],
controllers: [AuthController],
providers: [AuthService, LocalStrategy, JwtStrategy, JwtRefreshStrategy],
exports: [AuthService],
})
export class AuthModule {}Misol 2 — Strategiyalar (Local, Jwt, Refresh — 2.4, 2.6, 2.9)
// local.strategy.ts
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super({ usernameField: "email" });
}
async validate(email: string, parol: string) {
const user = await this.authService.validateUser(email, parol);
if (!user) throw new UnauthorizedException("Email yoki parol noto'g'ri"); // umumiy (14)
return user;
}
}
// jwt.strategy.ts
@Injectable()
export class JwtStrategy extends PassportStrategy(JwtStrat) {
constructor(config: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: config.get("JWT_ACCESS_SECRET"),
});
}
validate(payload: any) {
return { id: payload.sub, email: payload.email, rol: payload.rol }; // req.user (RBAC — 8.7)
}
}
// jwt-refresh.strategy.ts
@Injectable()
export class JwtRefreshStrategy extends PassportStrategy(JwtStrat, "jwt-refresh") {
constructor(config: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromExtractors([(req: Request) => req.cookies?.refreshToken]),
secretOrKey: config.get("JWT_REFRESH_SECRET"),
passReqToCallback: true,
});
}
validate(req: Request, payload: any) {
return { ...payload, refreshToken: req.cookies?.refreshToken };
}
}Misol 3 — AuthService (to'liq — 2.5, 2.8, 2.9, 2.11)
@Injectable()
export class AuthService {
constructor(
private usersService: UsersService,
private jwtService: JwtService,
private config: ConfigService,
) {}
// Signup (2.11)
async signup(dto: SignupDto) {
const mavjud = await this.usersService.emailBilan(dto.email);
if (mavjud) throw new ConflictException("Email band"); // 409
const parolHash = await bcrypt.hash(dto.parol, 12); // (5.15)
const user = await this.usersService.yarat({ ...dto, parol: parolHash });
return this.tokenlarBer(user);
}
// Login uchun tekshirish (2.5)
async validateUser(email: string, parol: string) {
const user = await this.usersService.emailBilan(email); // parol bilan (8.3)
if (user && (await bcrypt.compare(parol, user.parol))) {
const { parol: _, ...natija } = user; // parolsiz (14)
return natija;
}
return null;
}
// Login (token ber)
async login(user: any) {
return this.tokenlarBer(user);
}
// Token yaratish + refresh saqlash (2.8, 2.9)
private async tokenlarBer(user: any) {
const payload = { sub: user.id, email: user.email, rol: user.rol };
const [accessToken, refreshToken] = await Promise.all([
this.jwtService.signAsync(payload, {
secret: this.config.get("JWT_ACCESS_SECRET"), expiresIn: "15m", // qisqa
}),
this.jwtService.signAsync(payload, {
secret: this.config.get("JWT_REFRESH_SECRET"), expiresIn: "7d", // alohida secret
}),
]);
await this.usersService.refreshSaqla(user.id, await bcrypt.hash(refreshToken, 10)); // DB hash (5.16: 2.8)
return { accessToken, refreshToken };
}
// Refresh (rotatsiya + reuse — 2.9)
async refresh(userId: number, refreshToken: string) {
const user = await this.usersService.bitta(userId);
const hash = await this.usersService.refreshHash(userId);
if (!hash || !(await bcrypt.compare(refreshToken, hash))) {
await this.usersService.refreshBekor(userId); // reuse bekor (5.16: 2.5)
throw new ForbiddenException("Ruxsat buzilishi");
}
return this.tokenlarBer(user); // rotatsiya (5.16: 2.4)
}
// Logout (2.13)
async logout(userId: number) {
await this.usersService.refreshBekor(userId);
}
}Misol 4 — AuthController (cookie bilan — 2.7, 2.10)
@ApiTags("auth")
@Controller("auth")
export class AuthController {
constructor(private authService: AuthService, private config: ConfigService) {}
@Public() // token shart emas (2.12)
@Post("signup")
async signup(@Body() dto: SignupDto, @Res({ passthrough: true }) res: Response) {
const tokens = await this.authService.signup(dto);
this.cookieQoy(res, tokens.refreshToken);
return { accessToken: tokens.accessToken };
}
@Public()
@UseGuards(LocalAuthGuard) // LocalStrategy (2.7)
@Post("login")
async login(@Request() req, @Res({ passthrough: true }) res: Response) {
const tokens = await this.authService.login(req.user); // req.user — LocalStrategy
this.cookieQoy(res, tokens.refreshToken);
return { accessToken: tokens.accessToken };
}
@Public()
@UseGuards(JwtRefreshGuard) // JwtRefreshStrategy (2.9)
@Post("refresh")
async refresh(@CurrentUser() user, @Res({ passthrough: true }) res: Response) {
const tokens = await this.authService.refresh(user.id, user.refreshToken);
this.cookieQoy(res, tokens.refreshToken);
return { accessToken: tokens.accessToken };
}
@UseGuards(JwtAuthGuard) // himoyalangan (2.6)
@Post("logout")
async logout(@CurrentUser() user, @Res({ passthrough: true }) res: Response) {
await this.authService.logout(user.id);
res.clearCookie("refreshToken", { path: "/auth" });
return { message: "Chiqdingiz" };
}
@UseGuards(JwtAuthGuard)
@Get("me")
me(@CurrentUser() user) { return user; }
private cookieQoy(res: Response, refreshToken: string) { // cookie (2.10)
res.cookie("refreshToken", refreshToken, {
httpOnly: true,
secure: this.config.get("NODE_ENV") === "production",
sameSite: "lax",
maxAge: 7 * 24 * 60 * 60 * 1000,
path: "/auth",
});
}
}Misol 5 — Guards (2.7, 2.12)
// local-auth.guard.ts
@Injectable()
export class LocalAuthGuard extends AuthGuard("local") {}
// jwt-refresh.guard.ts
@Injectable()
export class JwtRefreshGuard extends AuthGuard("jwt-refresh") {}
// jwt-auth.guard.ts (@Public istisno — 2.12, 8.6: Misol 10)
@Injectable()
export class JwtAuthGuard extends AuthGuard("jwt") {
constructor(private reflector: Reflector) { super(); }
canActivate(context: ExecutionContext) {
const isPublic = this.reflector.getAllAndOverride<boolean>("isPublic", [
context.getHandler(), context.getClass(),
]);
if (isPublic) return true;
return super.canActivate(context);
}
}Misol 6 — Global auth + RBAC (8.7 bilan — 2.12)
// app.module.ts — global auth + RBAC (8.6: 2.12, 8.7)
@Module({
providers: [
{ provide: APP_GUARD, useClass: JwtAuthGuard }, // 1. auth (hamma himoyalangan)
{ provide: APP_GUARD, useClass: RolesGuard }, // 2. RBAC (8.7)
],
})
export class AppModule {}
// Endpoint
@Public()
@Post("auth/login") // public (token shart emas)
login() {}
@Roles(Rol.ADMIN) // himoyalangan + admin (8.7)
@Delete("users/:id")
ochir() {}Misol 7 — DTO (auth — 8.5)
export class SignupDto {
@ApiProperty() @IsString() @MinLength(2) ism: string;
@ApiProperty() @IsEmail() email: string;
@ApiProperty()
@IsString() @MinLength(8)
@Matches(/[A-Z]/, { message: "Katta harf kerak" }) // kuchli parol (5.15)
@Matches(/\d/, { message: "Raqam kerak" })
parol: string;
}
export class LoginDto {
@ApiProperty() @IsEmail() email: string;
@ApiProperty() @IsString() parol: string;
}Misol 8 — Refresh rate limiting (8.16 kirish — 14)
import { Throttle } from "@nestjs/throttler"; // (8.16, 5.20)
@Public()
@UseGuards(LocalAuthGuard)
@Throttle({ default: { limit: 5, ttl: 60000 } }) // 1 daqiqada 5 (brute-force — 14)
@Post("login")
login(@Request() req, @Res({ passthrough: true }) res: Response) {}
// Login brute-force himoyasi (5.15: Misol 12, 8.16)Misol 9 — Custom @CurrentUser (8.6: 2.9)
// current-user.decorator.ts
export const CurrentUser = createParamDecorator(
(data: string | undefined, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
const user = request.user; // strategiya qo'ygan (2.6)
return data ? user?.[data] : user;
},
);
// Ishlatish (toza)
@Get("me")
@UseGuards(JwtAuthGuard)
me(@CurrentUser() user) { return user; }
@Get("my-orders")
orders(@CurrentUser("id") userId: number) {} // faqat idMisol 10 — Frontend auth oqimi (kontekst — 5.16: 2.10)
// Frontend (11) — backend uchun kontekst (silent refresh — 5.16: 2.10)
// Login: POST /auth/login accessToken (xotirada) + refreshToken (cookie avtomatik)
// Har so'rov: Authorization: Bearer ${accessToken}
// 401 POST /auth/refresh (cookie avtomatik) yangi accessToken qayta urin
// Logout: POST /auth/logout (refresh bekor + cookie tozalash)
// Axios interceptor (5.16: Misol 7) — silent refresh
api.interceptors.response.use(null, async (err) => {
if (err.response?.status === 401) {
const { data } = await api.post("/auth/refresh"); // cookie avtomatik
accessToken = data.accessToken;
return api(err.config); // qayta urin
}
return Promise.reject(err);
});5. To'g'ri va noto'g'ri holatlar
1) Parolni ochiq saqlash
ochiq parol (5.15: 2.2, 14)
bcrypt.hash (cost 12)2) Refresh localStorage'da
localStorage (XSS — 5.16: 2.7, 14)
httpOnly cookie + DB hash3) Login xatosida qaysi maydon xato (oshkor)
"email topilmadi" (hacker'ga — 5.15: 2.18)
umumiy "email yoki parol noto'g'ri"4) Uzoq access token
30 kunlik access (o'g'irlansa — 30 kun — 5.16: 2.1)
qisqa access (15m) + refresh5) JWT secret hardcode
kodda secret (14)
.env (registerAsync — ConfigService)6. Keng tarqalgan xatolar va yechimlari
Xato 1 — Unauthorized (token to'g'ri bo'lsa ham)
Sababi: JwtStrategy secret mos emas, yoki ExtractJwt noto'g'ri 2.6-bob. Yechimi: access/refresh secret aniq; ExtractJwt header/cookie to'g'ri.
Xato 2 — req.user undefined
Sababi: guard/strategiya ishlamagan, yoki validate qaytarmagan 2.6-bob. Yechimi: @UseGuards; validate return.
Xato 3 — Cookie kelmaydi (refresh)
Sababi: cookie-parser yo'q, yoki CORS credentials 5.20-bob. Yechimi: cookie-parser; CORS credentials: true; frontend withCredentials.
Xato 4 — secretOrPrivateKey must have a value
Sababi: JWT secret .env'da yo'q yoki registerAsync xato 2.3-bob. Yechimi: .env; ConfigService.
Xato 5 — Refresh ishlamaydi
Sababi: cookie path, yoki DB hash mos emas 2.9-bob. Yechimi: cookie path; refresh DB'da hash tekshir.
Xato 6 — Global guard login'ni bloklaydi
Sababi: @Public yo'q 2.12-bob. Yechimi: @Public() login/signup'da.
7. Integratsiya — bu mavzu stack'ning qayerida uchraydi
- Auth (5.15, 5.16): JWT, bcrypt, refresh — NestJS'da.
- Guards 8.6-bob: AuthGuard, JwtAuthGuard.
- RBAC 8.7-bob: auth roles (req.user.rol).
- DTO 8.5-bob: signup/login validatsiya.
- DB 8.3-bob: user, refresh hash.
- Config 8.14-bob: JWT secret env.
- Throttler 8.16-bob: login rate limit.
- Custom decorator (8.6: 2.9): @CurrentUser, @Public.
- Frontend (11): silent refresh.
- Xavfsizlik (14): OWASP auth.
8. Eng yaxshi amaliyotlar (best practices)
- Parol bcrypt (cost 12); ochiq saqlama (5.15: 2.2, 14).
- Login xato UMUMIY ("email yoki parol" — 5.15: 2.18).
- Access qisqa (15m) + refresh (7d), alohida secret (5.16: 2.9, 2.11).
- Refresh httpOnly cookie + DB hash (localStorage emas — 5.16: 2.7, 2.8, 14).
- Refresh rotatsiya + reuse detection (5.16: 2.4, 2.5).
- JWT secret .env'da, kuchli (registerAsync — 14).
- Global JwtAuthGuard + @Public (himoyalangan default — 2.12).
- Login/refresh rate limiting (Throttler — 8.16, 14).
- @CurrentUser (toza req.user — 2.9).
- Logout — refresh bekor + cookie tozalash 2.13-bob.
9. Amaliy loyiha: "To'liq NestJS Auth Tizimi"
NestJS auth'ni professional darajada mustahkamlash (eng muhim loyiha).
Maqsad
NestJS'da to'liq, production-tayyor auth tizimini qurish: Passport (local/jwt/refresh), access+refresh token, cookie, RBAC integratsiya.
Talablar (requirements)
- AuthModule: JwtModule.registerAsync, PassportModule, strategiyalar (Misol 1, 2.3).
- Strategiyalar: Local (login), Jwt (himoyalangan), JwtRefresh (Misol 2, 2.4, 2.6, 2.9).
- AuthService: signup (bcrypt), validateUser, login, refresh (rotatsiya), logout (Misol 3, 2.5, 2.8).
- Controller: signup/login/refresh/logout/me — cookie bilan (Misol 4, 2.10).
- Access + refresh: qisqa access, uzoq refresh (alohida secret — 2.8, 5.16).
- Cookie: refresh httpOnly (5.15: 2.11, 14).
- Refresh hash + rotatsiya + reuse: DB hash, yangi refresh, reuse detection (2.9, 5.16).
- Global guard + @Public: himoyalangan default; login public (Misol 6, 2.12).
- RBAC integratsiya: auth roles 8.7-bob.
- Rate limiting: login (Misol 8, 8.16); xavfsizlik 2.15-bob.
Maslahatlar (hint)
- Parol bcrypt cost 12 (5.15, 1-xato).
- Login xato umumiy (5.15: 2.18, 3-holat).
- Access qisqa + refresh (5.16: 2.9, 4-holat).
- Refresh httpOnly cookie + DB hash (5.16: 2.7, 2.8, 2-xato).
- JWT secret .env (registerAsync — 14, 5-holat).
- @Public global istisno (2.12, 6-xato).
"Tayyor" mezonlari (acceptance criteria)
- AuthModule (JwtModule, strategiyalar).
- Local/Jwt/Refresh strategiyalar.
- Signup (bcrypt), login, refresh, logout.
- Access + refresh (alohida secret).
- Refresh httpOnly cookie + DB hash.
- Rotatsiya + reuse detection.
- Global guard + @Public.
- RBAC integratsiya (authroles).
- Login rate limiting.
- Xavfsizlik (parol/secret/xato).
Yechim kodi ataylab berilmagan — bu loyihani o'zingiz yozib ko'ring.
10. Xulosa va keyingi bobga ko'prik
Bu bobda NestJS auth'ning yuragini chuqur o'rgandik:
- Passport (strategiya — tekshirish usuli — 2.1); auth oqimi (signup/login/refresh — 2.2); AuthModule (JwtModule.registerAsync — 2.3).
- Strategiyalar: LocalStrategy (login — email/parol — 2.4), JwtStrategy (himoyalangan — 2.6), JwtRefreshStrategy (refresh — 2.9); guards (AuthGuard — 2.7).
- Access + refresh (5.16 — 2.8); refresh rotatsiya + reuse + DB hash 2.9-bob; cookie (httpOnly — 2.10); signup (bcrypt — 2.11); logout 2.13-bob.
- Global guard + @Public 2.12-bob; RBAC integratsiya 8.7-bob; xavfsizlik (14 — 2.15); OAuth 2.14-bob.
Keyingi bob — 8.10-bob: Email (Nodemailer), guard va tokenlar amaliyoti. Auth'ni bildik; endi uni amaliy to'ldiramiz: NestJS'da email (Nodemailer — 5.19 — email tasdiqlash, parol tiklash), va guard/token'larni real loyihada birlashtirish (email tasdiqlash + auth). Bu — auth'ning amaliy davomi.
Foydalanilgan rasmiy/ishonchli manbalar
- docs.nestjs.com/security/authentication; /recipes/passport (Passport, JWT, strategy, guard)
- Encore / Medium — NestJS Authentication Guide 2026 (JWT, Passport, refresh, cookie)
- docs.nestjs.com/security/authentication — JwtModule, bcrypt, refresh token best practices
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!