WisarWisar
Dasturlash kitobi/8-QISM — NestJS22 daqiqa

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

text
  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. Strategiya validate() — foydalanuvchini tekshiradi, qaytaradi req.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.

text
  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)

text
  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)

ts
// 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 {}

AuthModuleJwtModule.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)

ts
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). LocalAuthGuard bilan ishlatiladi.

2.5. validateUser (bcrypt — 5.15)

ts
@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)

ts
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:

ts
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 user req.user (8.6, 8.7: RBAC uchun rol). JwtAuthGuard bilan.

2.7. Guard'lar (Local va Jwt)

ts
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'larAuthGuard("strategiya") (Passport guard). LocalAuthGuard (login — LocalStrategy ishga tushadi); JwtAuthGuard (himoyalangan — JwtStrategy). @UseGuards(LocalAuthGuard) strategiya tekshiradi req.user. Global JwtAuthGuard + @Public (8.6: Misol 10) — ko'p ishlatiladi.

2.8. Access + Refresh token (5.16)

ts
@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)

ts
// 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)
  }
}
ts
// 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).

ts
// 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-parser middleware kerak 5.15-bob. httpOnly + Secure + SameSite (5.15: 2.11, 14).

2.11. Signup (bcrypt — 5.15)

ts
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)

ts
// 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:

ts
// 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)

ts
@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)

ts
// 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'naltirish

OAuth (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)

text
   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

ts
// 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)

ts
// 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)

ts
// 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)

ts
@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);
  }
}
ts
@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)

ts
// 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)

ts
// 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)

ts
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)

ts
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)

ts
// 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 id

Misol 10 — Frontend auth oqimi (kontekst — 5.16: 2.10)

ts
// 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

text
 ochiq parol (5.15: 2.2, 14)
 bcrypt.hash (cost 12)

2) Refresh localStorage'da

text
 localStorage (XSS — 5.16: 2.7, 14)
 httpOnly cookie + DB hash

3) Login xatosida qaysi maydon xato (oshkor)

text
 "email topilmadi" (hacker'ga — 5.15: 2.18)
 umumiy "email yoki parol noto'g'ri"

4) Uzoq access token

text
 30 kunlik access (o'g'irlansa — 30 kun — 5.16: 2.1)
 qisqa access (15m) + refresh

5) JWT secret hardcode

text
 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)

  1. AuthModule: JwtModule.registerAsync, PassportModule, strategiyalar (Misol 1, 2.3).
  2. Strategiyalar: Local (login), Jwt (himoyalangan), JwtRefresh (Misol 2, 2.4, 2.6, 2.9).
  3. AuthService: signup (bcrypt), validateUser, login, refresh (rotatsiya), logout (Misol 3, 2.5, 2.8).
  4. Controller: signup/login/refresh/logout/me — cookie bilan (Misol 4, 2.10).
  5. Access + refresh: qisqa access, uzoq refresh (alohida secret — 2.8, 5.16).
  6. Cookie: refresh httpOnly (5.15: 2.11, 14).
  7. Refresh hash + rotatsiya + reuse: DB hash, yangi refresh, reuse detection (2.9, 5.16).
  8. Global guard + @Public: himoyalangan default; login public (Misol 6, 2.12).
  9. RBAC integratsiya: auth roles 8.7-bob.
  10. 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!
8.9-bob: Autentifikatsiya — JWT, Passport, cookie, refresh token — Wisar