JWT 발급
일반적으로는 이전 포스트까지 완료됐다면 DB에 해당 유저가 있는지 확인 후 없으면 DB에 저장, 있으면 JWT를 발급해서 로그인을 시켜야 한다. DB에 관련된 부분은 최대한 건너뛰고 JWT 관련된 것들만 작성하겠다.
userService 작성
JWT 발급 전 회원가입이 되어 있는지 확인하기 위해서 DB를 확인해야 한다. 이때 userService에 로직을 작성한다.
// user.service.ts
import { Injectable } from "@nestjs/common";
export interface GoogleUser {
id: string;
provider: string;
name: string;
email: string;
photo: string;
}
@Injectable()
export class UserService {
async findGoogleDataOrSave(user: GoogleUser) {
return user;
}
}
findGoogleDataOrSave 메서드에 DB에서 확인하고 저장하는 로직이 들어가 있어야 한다. 해당 로직은 생략 후 받아온 user 파라미터를 그대로 리턴한다.
JWT 발급 사전 준비
payload
JWT를 발급받기 위해서 payload가 필요하다.
// auth.controller.ts
export interface JwtPayload {
iss: string;
name: string;
email: string;
sub: string;
}
// AuthController
@Get("google")
@UseGuards(GoogleAuthGuard)
async googleAuthRedirect(@Req() req: Request, @Res() res: Response) {
const user = await this.userService.findGoogleDataOrSave(req.user as GoogleUser);
const payload: JwtPayload = {
iss: "JoY",
name: user.name,
email: user.email,
sub: user.id,
};
}
userService에서 받아온 user 데이터의 name, email, id 값과 , iss에는 발급자인 JoY를 페이로드에 넣는다
JWT 모듈 import
jwt 모듈 import 방법은 이전 포스트에 있다.
expires와 secret_key 등은 accessToken과 refreshToken에 따라서 다르고, 따로 설정해야 하는 공용 옵션이 없기 때문에 register로 JWT 모듈을 등록하겠다.
// auth.module.ts
import { Module } from "@nestjs/common";
import { AuthService } from "./auth.service";
import { AuthController } from "./auth.controller";
import { GoogleStrategy } from "./google/auth.google.strategy";
import { UserModule } from "../user/user.module";
import { JwtModule } from "@nestjs/jwt";
import { ConfigService } from "@nestjs/config";
@Module({
imports: [
UserModule,
JwtModule.register({}),
],
controllers: [AuthController],
providers: [AuthService, GoogleStrategy],
exports: [AuthService],
})
export class AuthModule {}
JWT 발급
access token과 refresh token
access token은 보안을 위해 짧은 시간 동안만 유효하고, 만료되면 더 이상 사용이 불가능하다. access token을 통해서 서버에 api를 날려 데이터등을 요청할 수 있다.
refresh token은 새로운 access token을 발급하기 위해 필요한 토큰이다. access token과 함께 발급되고, access token이 만료되었을 때 새로운 access token와 refresh token을 발급해 준다. 또한 refresh token의 유효기간은 길어서 로그인 상태를 장기간 유지시켜 준다. refresh token으로는 서버에서 데이터 요청이 불가능하다.
refresh token은 DB에 저장하는 것이 가장 안전하고, 권장되는 방법이다.
두 개의 토큰을 발급하는 이유는 access token의 유효기간이 짧아서 탈취되더라도 오랫동안 사용이 불가능하며 refresh token으로는 직접적으로 데이터에 접근이 불가능하다. 이로 인해 보안이 향상된다.
JWT 발급 로직
JWT를 발급해주는 로직은 auth.service.ts에 작성한다.
// auth.service.ts
import { Injectable } from "@nestjs/common";
import { JwtService } from "@nestjs/jwt";
import { JwtPayload } from "./auth.controller";
import { ConfigService } from "@nestjs/config";
@Injectable()
export class AuthService {
constructor(private readonly jwtService: JwtService, private readonly configService: ConfigService) {}
getToken(payload: JwtPayload) {
const accessToken = this.jwtService.sign(payload, {
expiresIn: "1h",
secret: this.configService.get("JWT_SECRET_ACCESS_TOKEN_KEY"),
});
const refreshToken = this.jwtService.sign(payload, {
expiresIn: "7d",
secret: this.configService.get("JWT_SECRET_REFRESH_TOKEN_KEY"),
});
return { accessToken, refreshToken };
}
}
access token의 유효기간은 1시간, refresh token의 유효기안은 7일로 한다. 또한 env파일에서 시크릿키를 가져온다.
이어서 JWT 를 프론트 쿠키에 심어줘야 한다.
그전에 refresh token을 DB에 저장하는 메서드를 작성한다.
// user.service.ts
@Injectable()
export class UserService {
//...
async updateRefreshToken(refreshToken: string) {
const isSuccess = true;
return isSuccess;
}
}
isSuccess는 DB에 저장이 성공했는지 판단한다.
//auth.controller.ts
enum CookieExpires {
EXPIRES_IN_2HOURS = 3600000,
EXPIRES_IN_7DAYS = 604800000,
}
@Controller("auth")
export class AuthController {
//...
@Get("google")
@UseGuards(GoogleAuthGuard)
async googleAuthRedirect(@Req() req: Request, @Res() res: Response) {
//...
const { accessToken, refreshToken } = this.authService.getToken(payload);
const isSuccess = await this.userService.updateRefreshToken(refreshToken);
if (isSuccess) {
res.cookie("access_token", accessToken, {
expires: new Date(Date.now() + CookieExpires.EXPIRES_IN_2HOURS), // 1시간 후 만료
httpOnly: true, // 자바스크립트로 접근 불가
secure: true, // HTTPS로만 전송
sameSite: "strict", // 같은 사이트에서만 전송
});
res.cookie("refresh_token", refreshToken, {
expires: new Date(Date.now() + CookieExpires.EXPIRES_IN_7DAYS), // 7일 후 만료
httpOnly: true, // 자바스크립트로 접근 불가
secure: true, // HTTPS로만 전송
sameSite: "strict", // 같은 사이트에서만 전송
});
res.redirect("도메인");
} else res.redirect(401, "localhost:3000");
}
}
DB에 refresh token을 저장하고 성공하면 쿠키와 함께 페이지를 redirect 시키면 완료다. 만약 DB에 저장 실패 시 status 401과 함께 페이지만 redirect 시킨다.
개인 기록용입니다. 만약 잘못된 부분이 있다면 댓글로 알려주신다면 감사하겠습니다!