Merhaba,

 

Bu yazıda NestJS ile JWT (Json Web Token) kullanımından bahsedeceğim.

 

İlk olarak bir NestJS projesi oluşturuyoruz:

nest new nest-jwt-sample

 

Sonrasında gerekli paketleri kuruyoruz:

npm i @nestjs/jwt @nestjs/passport passport-jwt passport-local

 

auth ve users şeklinde module ve service oluşturuyoruz:

nest g service auth
nest g module auth
nest g service users
nest g module users

 

Oluşturduğumuz modülleri app.module.ts dosyasına dahil ediyoruz:

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AuthModule } from './auth/auth.module';
import { UsersModule } from './users/users.module';

@Module({
  imports: [AuthModule, UsersModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

 

users.service.ts dosyasını açarak şu şekilde bir kodlama yapıyoruz:

import { Injectable } from '@nestjs/common';

@Injectable()
export class UsersService {
  private readonly users = [
    {
      userId: 1,
      username: 'yusufborucu',
      password: '123456'
    },
    {
      userId: 2,
      username: 'test',
      password: 'test'
    }
  ];

  async findOne(username: string): Promise<any> {
    return this.users.find(user => user.username === username);
  }
}

Burada yaptığımız işlemler;

  • users adında örnek bir dizi tanımladık.
  • findOne adında bir fonksiyon tanımladık. Bu fonksiyon ile, gelen username verisinin users dizisi içinde olup olmadığını kontrol ediyoruz.

 

users.module.ts dosyasını açarak users servisini provider ve dışarıdan kullanılabilir olarak tanımlıyoruz:

import { Module } from '@nestjs/common';
import { UsersService } from './users.service';

@Module({
  providers: [UsersService],
  exports: [UsersService]
})
export class UsersModule {}

 

auth.service.ts dosyasını açarak şu şekilde bir kodlama yapıyoruz:

import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UsersService } from 'src/users/users.service';

@Injectable()
export class AuthService {
  constructor(private usersService: UsersService, private jwtService: JwtService) {}

  async validateUser(username: string, password: string): Promise<any> {
    const user = await this.usersService.findOne(username);

    if (user && user.password === password) {
      // user verisi içinde userId, username ve password bulunuyor.
      // Bu tanımlama ile result verisi içinde userId ve username olmasını sağlıyoruz.
      const { password, ...result } = user;
      return result;
    }
    return null;
  }

  async loginWithCredentials(user: any) {
    // Jwt oluştururken payload verisi gerekiyor. 
    // Bunu da kullanıcıya özel yapabilmek adına username ve userId verilerini kullandık.
    const payload = { username: user.username, sub: user.userId };

    return {
      access_token: this.jwtService.sign(payload)
    };
  }
}

Burada yaptığımız işlemler;

  • Users servisini import ettik.
  • validateUser fonksiyonu ile, gelen username ve password verilerini kontrol ediyoruz, eğer bilgiler doğruysa geriye userId ve username verisi dönecek şekilde ayarlıyoruz.
  • loginWithCredentials fonksiyonu ile, gelen user verisinin içindeki username ve userId verilerini kullanarak token oluşturuyoruz.

 

auth.module.ts dosyasını açarak şu şekilde bir kodlama yapıyoruz:

import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { UsersModule } from 'src/users/users.module';
import { AuthService } from './auth.service';
import { jwtConstants } from './constants';
import { LocalStrategy } from './local.strategy';
import { JwtStrategy } from './jwt.strategy';

@Module({
  imports: [
    UsersModule,
    PassportModule,
    JwtModule.register({
      secret: jwtConstants.secret,
      signOptions: { expiresIn: '60s' }
    })
  ],
  providers: [AuthService, LocalStrategy, JwtStrategy],
  exports: [AuthService]
})
export class AuthModule {}

Burada yaptığımız işlemler;

  • Users, Passport ve Jwt modülleri ile Auth servisini import ettik. Ayrıca Auth servisini dışarı açtık (export).
  • Passport paketinde farklı stratejiler bulunmakta. Özetlemek gerekirse authentication işlemini nasıl yapmak istediğimizi belirtmek için bu strateji tanımlarını kullanıyoruz. Örneğin local (username ve password ile), jwt (adı üstünde json web token ile), google (google ile giriş yapmak) gibi stratejiler mevcut. Bunlarla ilgili detaylı bilgiye şuradan ulaşabilirsiniz. Biz bu projede local ve jwt stratejisini kullanacağız. Bunlar için gerekli dosyaları sonradan oluşturacağız. O dosyaları burada provider olarak tanımlıyoruz.
  • jwt ile ilgili sabitleri tanımlamak adına (secret key gibi) constants dosyası oluşturacağız. Bunu da import ediyoruz.
  • Jwt modülünü register ediyoruz. Token'ın 60 saniyelik bir expire süresi olduğunu belirtiyoruz.

 

constants.ts dosyasını oluşturuyoruz:

export const jwtConstants = {
  // Normalde bu tarz bilgileri açıkça göstermemek gerekiyor.
  // Burada test projesi yaptığımız için problem bulunmuyor.
  secret: 'secretKey'
};

 

local.strategy.ts dosyasını oluşturuyoruz:

import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-local';
import { AuthService } from './auth.service';

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
  constructor(private authService: AuthService) {
    super();
  }

  async validate(username: string, password: string): Promise<any> {
    const user = await this.authService.validateUser(username, password);

    if (!user) {
      throw new UnauthorizedException();
    }

    return user;
  }
}

Burada yaptığımız işlemler;

  • Bu sınıfı PassportStrategy'den kalıtım (extends) yaptık.
  • Auth servisini import ettik.
  • constructor kısmını super() olarak bıraktık. Bu da default olarak username ve password verilerini bekleyeceği anlamına geliyor. Örneğin super({ usernameField: ‘email’ }) gibi bir tanımla username değil de email verisi beklemesini sağlayabilirdik.
  • validate fonksiyonu ile, gelen username ve password verilerini kontrol ediyoruz. Eğer kullanıcı bulunamazsa Unauthorized hatası döndürüyoruz, kullanıcı varsa da user verisini döndürüyoruz. 

 

jwt.strategy.ts dosyasını oluşturuyoruz:

import { PassportStrategy } from "@nestjs/passport";
import { ExtractJwt, Strategy } from "passport-jwt";
import { jwtConstants } from "./constants";

export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: jwtConstants.secret
    })
  }

  async validate(payload: any) {
    return { userId: payload.sub, username: payload.username }
  }
}

Burada yaptığımız işlemler;

  • Bu sınıfı PassportStrategy'den kalıtım yaptık.
  • constructor kısmında yaptığımız tanımlarla şunu belirtmiş olduk;
    • Token'ı gelen isteğin header kısmındaki Authorization: Bearer {token} verisinden al (jwtFromRequest).
    • Expiration'ı yoksaymayı false olarak tanımlıyoruz. Yani expire olan bir token ile istek geldiğinde Unauthorized hatası döndürülecek (ignoreExpiration: false).

 

app.controller.ts dosyasını açarak route tanımlarını yapıyoruz:

import { Controller, Get, Post, Request, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { AuthService } from './auth/auth.service';

@Controller()
export class AppController {
  constructor(private authService: AuthService) {}

  @UseGuards(AuthGuard('local'))
  @Post('login')
  async login(@Request() req) {
    return this.authService.loginWithCredentials(req.user);
  }

  @UseGuards(AuthGuard('jwt'))
  @Get('user-info')
  getUserInfo(@Request() req) {
    return req.user;
  }
}

Burada yaptığımız işlemler;

  • Auth servisini import ettik.
  • login adında, Post isteğini kabul eden, local auth guard korumasına sahip (request body olarak username ve password bekleyen) bir route tanımladık.
  • user-info adında, Get isteğini kabul eden, jwt auth guard korumasına sahip (header kısmında Authorization: Bearer {token} bekleyen) bir route tanımladık.

 

Son olarak projemizi ayağa kaldırıp Postman üzerinden testlerimizi yapıyoruz:

npm run start

 

 

Projenin kaynak kodlarına buradan ulaşabilirsiniz.

 

Umarım yararlı olmuştur.

 

İyi çalışmalar.