오늘은 어제 진행했던 로그인 과정에서 Refresh Token을 포함한 토큰 발급과
쿠키에 담아주어 다시 AccessToken 을 발급하는 기능을 배워보았다
오늘의 내용에 대해 알아보자
로그인 요청을 하고 나서, 서버에서 토큰을 프론트에게 넘겨줄 때, 토큰을 하나 더 만들어서 넘겨줍니다. 하나 더 만든 토큰을 refresh token
이라고 하고 기존에 발행하던 토큰을 access token
이라고 합니다.
refresh token
은 access token
이 만료되었을 때, access token
을 다시 발행하기 위한 용도로 쓸 것이기 때문에 access token
보다 유효기간이 길어야 합니다.
Access Token(JWT)
를 통한 인증 방식의 문제는 해킹을 당했을 경우 보안에 취약하다는 점이 있습니다.
유효기간이 짧은 토큰의 경우 그만큼 사용자는 로그인을 자주 해서 새롭게 토큰을 발급받아야 하므로 불편합니다.
그렇다고 유효기간을 늘리면 토큰을 해킹당했을 때 보안에 더 취약해지게 됩니다.
이러한 점들을 보완하는 것이 Refresh Token
입니다.
새로 배운 위 개념들을 토대로 어제 만든 로그인 로직을 수정 해보자!
import { Injectable, UnprocessableEntityException } from '@nestjs/common';
import { UsersService } from '../users/users.service';
import * as bcrypt from 'bcrypt';
import { JwtService } from '@nestjs/jwt';
import {
IAuthServiceGetAccessToken,
IAuthServiceLogin,
IAuthServiceRestoreAccessToken,
IAuthServiceSetRefreshToken,
} from './interfaces/auth-service.interface';
@Injectable()
export class AuthService {
constructor(
private readonly usersService: UsersService, //
private readonly jwtService: JwtService,
) {}
async login({
email,
password,
context,
}: IAuthServiceLogin): Promise<string> {
// 1. 이메일 일치하는 유저를 DB에서 찾기
const user = await this.usersService.findOneByEmail({ email });
// 2. 일치하는 유저가 없으면 에러 던지기!
if (!user) throw new UnprocessableEntityException('이메일이 없습니다.');
// 3. 일치하는 유저가 있지만, 비밀번호가 틀렸다면?!
const isAuth = await bcrypt.compare(password, user.password);
if (!isAuth)
throw new UnprocessableEntityException('비밀번호가 일치하지 않습니다.');
// 4. refreshToken(=JWT)을 만들어서 브라우저 쿠키에 저장해서 보내주기
this.setRefreshToken({ user, context });
// 5. 일치하는 유저도 있고, 비밀번호도 맞았다면?!
// => accessToken(=JWT)을 만들어서 브라우저에 전달하기
return this.getAccessToken({ user });
}
restoreAccessToken({ user }: IAuthServiceRestoreAccessToken): string {
return this.getAccessToken({ user });
}
setRefreshToken({ user, context }: IAuthServiceSetRefreshToken): void {
const refreshToken = this.jwtService.sign(
{ id: user.id },
{ secret: '나의리프레시비밀번호', expiresIn: '2w' },
);
// 개발환경
context.res.setHeader(
'set-Cookie',
`refreshToken=${refreshToken}; path=/;`,
);
// 배포환경
// context.res.setHeader(
// 'set-Cookie',
// `refreshToken=${refreshToken}; path=/; domain=.myBackendSite.com; SameSite=None; Secure; httpOnly;`,
// );
// context.res.setHeader(
// 'Access-Control-Allow-Origin',
// 'https://myFrontendSite.com',
// );
}
getAccessToken({ user }: IAuthServiceGetAccessToken): string {
return this.jwtService.sign(
{ id: user.id },
{ secret: '나의비밀번호', expiresIn: '10s' },
);
}
}
어제와 다르게 로그인시 accessToken만 생성하지 않고 refreshToken을 같이 생성 한후
리프레시토큰은 쿠키에 담아 주는 로직이다 또한 리프레시 토큰용 스트레티지 파일도 생성해 주어야 한다!
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
export class JwtRefreshStrategy extends PassportStrategy(Strategy, 'refresh') {
constructor() {
super({
jwtFromRequest: (req) => {
const cookie = req.headers.cookie; // refreshToken=abcdefg
const refreshToken = cookie.replace('refreshToken=', '');
return refreshToken;
},
secretOrKey: '나의리프레시비밀번호',
});
}
validate(payload) {
// 성공시 로직 처리
console.log(payload); // {id: adasdasdqwdqw(유저ID)}
return {
id: payload.id,
};
}
}
액세스토큰과 달리 리프레시토큰은쿠키에 담겨있기때문에 헤더에 쿠키 정보를 가져와야 한다!