Skip to content

3. JSON Web Token (JWT) dan Guard

1. Konsep Json Web Token (JWT)

Pada materi ini kita akan menggunakan JWT (JSON web Token) untuk token dalam proses authentikasi. Dokumentasi jwt : https://jwt.io/

kita akan bahas terlebih dahulu

a. Apa itu jwt?

  • JWT merupakan standar untuk melakukan pertukaran data dan authentikasi
  • JWT mengikuti standar RFC 7591
  • Data yang dipertukarkan mengguankan format JSON
  • JWT dapt di enkripsi menggunakan secret key seperti HMAC atau public/private key seperti RSA

b.Kapan JWT digunakan ?

  • Authorization (login)
  • Information Exchange

c. Struktur JWT

Alt text Alt text

Seperti pada struktur jwt di atas, jwt membutuhkan signature yang hanya diketahui oleh server untuk mengecek apakan token valid atau tidak.

d. Apa itu Header JWT?

  • Berisikan informasi algoritma enkripsi yang digunakan
  • Merupakan hasil base64. dari informati header dalam bentuk JSON

e. Apa itu payload JWT?

  • Berisi informasi yang digunakan untuk menverifikasi informasi yang dikirm dari client ke server
  • Payload tidak boleh berisi data sensitif , contoh nya password
  • Hasil dari base64 dalam bentuk JSON

f. Signature

  • Berisikan informasi yang dapat digunakan untuk memvalidasi JWT
  • Didapatkan dengan mengenkripsi header dan payload menggunakan alogaritma yang sudah ditentukan oleh header
  • Menggunakan secret key atau private key dari penerima

g. Access Token vs Refresh Token

Access Token Refresh Token
Digunakan untuk mengkases resource Digunakan untuk membuat access token baru
Tidak disimpan di database Disimpan di database
Punya waktu expired sedikit Punya waktu expired panjang

h. Best Practice JWT

  • Jangan meletakan data sensitif di payload
  • Expired time untuk access token tidak terlalu lama
  • Gunakan refesh token untuk membuat access_token
  • Jangan simpan access token di database Implentasi access token dan refeesh token bisa berbeda-beda pada setiap programmer

2. Membuat token dengan JWT (JSON Web Token)

Pada Pengujian sebelumnya kita sudah berhasil untuk login, namun ketika login kita belum memberikan token pada client untuk authentikasi yang akan disimpan di front end.

Selanjutnya kita akan implentasikan jwt token pada project nestjs kita Pertama kita akan instalasi package jwt di nestJS

a. Instalasi Package

npm install @nestjs/passport passport passport-jwt
npm install -D @types/passport-jwt
npm install @nestjs/jwt

b. Membuat jwt config

Pada materi ini kita buat jwt config untuk menyimpan signature dari jwt pada aplikasi kita. Kita akan membuat pada folder config.

src/config/jwt.config.ts
export const jwt_config = {
  access_token_secret: 'belajar_jwt',
  expired: 3600,
  refresh_token_secret: 'fajfngjgan',
};

Note

access_token_secret adalah secret signature untuk access token refresh_token_secre adalah secret signature untuk refresh token

c. Import Module JWT ke dalam Auth

Selanjutkan kita import module jwt pda module auth agar pada module auth. Kita akan menggunakan method register dari JWT Module dengan konfigurasi seperti di bawah.

app/auth/auth.module.ts
import { Module } from "@nestjs/common";
import { AuthController } from "./auth.controller";
import { AuthService } from "./auth.service";
import { TypeOrmModule } from "@nestjs/typeorm";
import { Auth } from "./auth.entity";
import { PassportModule } from "@nestjs/passport";
import { JwtModule } from "@nestjs/jwt";
import { jwt_config } from "src/config/jwt.config";

@Module({
  imports: [
    TypeOrmModule.forFeature([Auth]),
    JwtModule.register({}),
  ],
  controllers: [AuthController],
  providers: [AuthService],
})
export class AuthModule {}

d. Membuat interface untuk jwtPayload

Kemudian kita buat interface JWT payload untuk menghindari kesalahan katika membuat payload untuk JWT Token.

app/auth/auth.interface.ts
interface jwtPayload {
  id: number;
  nama: string;
  email: string;
}

e. Inject jwtService pada auth.service.ts

Kita tinggal inject jwtService pada contructor auth service agar service jwt bisa digunakan di auth service.

app/auth/auth.service.ts
constructor(
    @InjectRepository(Auth) private readonly authRepository: Repository<Auth>,
    private jwtService: JwtService, // panggil kelas jwt service
  ) {
    super();
  }

f. Membuat method generate jwt pada service

Kita akan membuat method dengan nama generateJWT untuk membuat JWT Token dengan parameter waktu expire dan payload yang berisi id, email, nama. Untuk membuat token kita bisa menggunakan method sign dari jwtService.

app/auth/auth.service.ts
 generateJWT(payload: jwtPayload, expiresIn: string | number, token: string) {
    return this.jwtService.sign(payload, {
      secret: token,
      expiresIn: expiresIn,
    });
  } //membuat method untuk generate jwt

g. Membuat Token pada saat Login dengan method generateJWT

Kita akan membuat access_token dan refresh token dan method generateJWT ketika user login. Pada kasus ini kita akan berikan waktu expired untuk access_token selama 1 hari dan refresh_token selama 7 hari.

app/auth/auth.service.ts
async login(payload: LoginDto): Promise<ResponseSuccess> {
    const checkUserExists = await this.authRepository.findOne({
      where: {
        email: payload.email,
      },
      select: {
        id: true,
        nama: true,
        email: true,
        password: true,
        refresh_token: true,
      },
    });

    if (!checkUserExists) {
      throw new HttpException(
        'User tidak ditemukan',
        HttpStatus.UNPROCESSABLE_ENTITY,
      );
    }

    const checkPassword = await compare(
      payload.password,
      checkUserExists.password,
    );

    if (checkPassword) {
      const jwtPayload: jwtPayload = {
        id: checkUserExists.id,
        nama: checkUserExists.nama,
        email: checkUserExists.email,
      };

      const access_token = await this.generateJWT(
        jwtPayload,
        '1d',
        jwt_config.access_token_secret,
      );
      const refresh_token = await this.generateJWT(
        jwtPayload,
        '7d',
        jwt_config.refresh_token_secret,
      );
      await this.authRepository.save({
        refresh_token: refresh_token,
        id: checkUserExists.id,
      }); // simpan refresh token ke dalam tabel
      return this._success('Login Success', {
        ...checkUserExists,
        access_token: access_token,
        refresh_token: refresh_token,
      });
    } else {
      throw new HttpException(
        'email dan password tidak sama',
        HttpStatus.UNPROCESSABLE_ENTITY,
      );
    }
  }

h.Testing pada Postman

Alt text

Mengecek apakah payload sudah sesuai dengan yang kita inginkan di website https://jwt.io/.

Silahkan copy access_token kemudian paste pada bagian encode

Alt text

Silahkan cek pada bagian decode untuk melihat data apa saya yang ada pada jwt.

2. Impelementasi Authentication Guard

Guard bertujuan untuk memproteksi suatu end point.

Alt text

Seperti pada contoh gambar di atas, endpoint https://mysqmk.com/profile memerlukan jwt token / user yang sudah login untuk mengaksesnya, jika request dilakukan tanpa membawa token, maka backend akan meresponse dengan Unauthorized, sedangkan jika berhasil maka akan memberikan data yang di request.

Pada materi ini kita akan mengimpelentasikan guard pada nestjs.

a. Membuat Guard pada Auth

Kita akan membuat file auth.guard.ts pada folder auth

auth.guard.ts
import { ExecutionContext, UnauthorizedException } from '@nestjs/common';

import { AuthGuard } from '@nestjs/passport';

export class JwtGuard extends AuthGuard('jwt_access_token') {
  canActivate(context: ExecutionContext) {
    return super.canActivate(context);
  }

  handleRequest(err, user) {
    if (err || !user) {
      throw err || new UnauthorizedException();
    }
    return user;
  }
}

export class JwtGuardRefreshToken extends AuthGuard('jwt_refresh_token') {
  canActivate(context: ExecutionContext) {
    return super.canActivate(context);
  }

  handleRequest(err, user) {
    if (err || !user) {
      throw err || new UnauthorizedException();
    }
    return user;
  }
}

b. Membuat JWT Strategy pada Auth

JWT strategy bertujuan untuk melakukan verifikasi token JWT yang dikirimkan oleh client dan menentukan apakan request akan diteruskan ke router handler atau tidak.

Alt text

JWT Strategy untuk Access Token

app/auth/jwtAccessToken.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { jwt_config } from 'src/config/jwt.config';

@Injectable()
export class JwtAccessTokenStrategy extends PassportStrategy(
  Strategy,
  'jwt_access_token',
) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: jwt_config.access_token_secret,
    });
  }

  async validate(payload: any) {
    return payload;
  }
}

JWT Strategy untuk Refresh Token

app/auth/jwtRefreshToken.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { jwt_config } from 'src/config/jwt.config';

@Injectable()
export class JwtRefreshTokenStrategy extends PassportStrategy(
  Strategy,
  'jwt_refresh_token',
) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: jwt_config.refresh_token_secret,
    });
  }

  async validate(payload: any) {
    return payload;
  }
}

c. Menambahkan JwtStrategy pada Provider Module Auth

Karena jwt strategy memiliki decorator @Injectable() maka kita harus menambahkan JwtStrategy pada provider di auth module

app/auth/auth.module.ts
import { Module } from '@nestjs/common';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './auth.entity';
import { JwtModule } from '@nestjs/jwt';
import { JwtAccessTokenStrategy } from './jwtAccessToken.strategy';
import { MailModule } from '../mail/mail.module';
import { ResetPassword } from './reset_password.entity';
import { JwtRefreshTokenStrategy } from './jwtRefreshToken.strategy';

@Module({
  imports: [
    TypeOrmModule.forFeature([User, ResetPassword]),
    JwtModule.register({}),
    MailModule,
  ],
  controllers: [AuthController],
  providers: [AuthService, JwtAccessTokenStrategy, JwtRefreshTokenStrategy],
})
export class AuthModule {}

d. Membuat endpont Profil

Kita akan membuat endpoint yang hanya bisa diakses ketika user sudah login/membawa token saat request. Pada contoh ini , kita akan membuat endpoint profil dimana endpoint ini akan memberikan data profil user berdasarkan token yang dikirim. Jika token tidak dikirim di dalam header, maka request akan ditolak.

auth.service.ts
async myProfile(id: number): Promise<ResponseSuccess> {
    const user = await this.authRepository.findOne({
      where: {
        id: id,
      },
    });

    return this._success('OK', user);
  }
Pada kode di atas, kita membuat service untuk mencari profile berdasarkan id

auth.controller.ts
import { Controller, Post, Body, Get, UseGuards, Req } from '@nestjs/common'; //import UseGuard
import { LoginDto, RegisterDto } from './auth.dto';
import { AuthService } from './auth.service';
import { JwtGuard } from './auth.guard'; //import JwtGuard

@Controller('auth')
export class AuthController {
  constructor(private authService: AuthService) {}
  @Post('register')
  async register(@Body() payload: RegisterDto) {
    return this.authService.register(payload);
  }

  @Post('login')
  async login(@Body() payload: LoginDto) {
    return this.authService.login(payload);
  }

  @UseGuards(JwtGuard) // impelementasi guard pada route , hal ini berarti endpoint profile hanya bisa diakses jika client membawa token
  @Get('profile')
  async profile(@Req() req) {  // hasil validate dari jwt strategy akan ditambakan pada req.user. isi object req.user akan sama dengan payload dari jwt token. Silahkan coba console.log(req.user)
    const { id } = req.user;
    return this.authService.myProfile(id);
  }
}

e. Pengujian Pada Postman

Pengujian Pertama, Request tanpa membawa jwt token pada headers

Alt text

Pengujian Kedua , Request dengan jwt token pada headers .

Silahkan copy token yang ada pada access_token ketika berhasil login

Alt text

Kemudian kita paste pada headers request pada endpont profile seperti pada gambar di bawah

Alt text

Kemudian kita tes kembali request pada server

Alt text

kita sudah berhasil untuk mengimplentasikan guard pada entpoint profile