JWT Authentication — Access Token

~25 daq · NestJS · Autentifikatsiya

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 — .env dan secret olish
  • ExtractJwt.fromAuthHeaderAsBearerToken() — Authorization: Bearer headerdan
  • validate(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