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
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
danpayload
menggunakan alogaritma yang sudah ditentukan oleh header - Menggunakan
secret key
atauprivate 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.
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.
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.
e. Inject jwtService pada auth.service.ts
Kita tinggal inject jwtService pada contructor auth service agar service jwt bisa digunakan di auth service.
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.
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.
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
Mengecek apakah payload sudah sesuai dengan yang kita inginkan di website https://jwt.io/.
Silahkan copy access_token
kemudian paste pada bagian encode
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.
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
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.
JWT Strategy untuk Access Token
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
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
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.
async myProfile(id: number): Promise<ResponseSuccess> {
const user = await this.authRepository.findOne({
where: {
id: id,
},
});
return this._success('OK', user);
}
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
Pengujian Kedua , Request dengan jwt token pada headers .
Silahkan copy token yang ada pada access_token ketika berhasil login
Kemudian kita paste pada headers request pada endpont profile seperti pada gambar di bawah
Kemudian kita tes kembali request pada server
kita sudah berhasil untuk mengimplentasikan guard pada entpoint profile