기존에 만들었떤 로그인 API에서 로그인을 하면 토큰이 발급된다. 토큰은 한번 만들어지면 변경할 수 없다.
여기서 문제가 생긴다. 로그아웃을 했지만 아직 만료되지 않은 토큰으로 API를 요청할 때, 토큰을 검증해 봤자
아무런 문제가 없이 정상처리 된다.
이를 해결하기 위해 JWT
, Redis
를 활용해 로그아웃 API를 만들어 볼것이다.
Redis 설정 전 라이브러리 설치하기
yarn add redis
yarn add cache-manager@4.1.0
yarn add cache-manager-redis-store@2.0.0
yarn add --dev @types/cache-manager-redis-store
JWT 라이브러리 설치하기
yarn add jsonwebtoken
import * as jwt from 'jsonwebtoken' 써주기!
jwt.verify 함수 사용하기!
=> 토큰 유효성을 확인 할 수 있다.
=> jwt.verify() 에 들어가는 매개변수 3개
token: client 에게서 받은 token
sercetkey: token 생성시 사용했던 sercetkey
3번째 인자로 들어간 익명함수: 유효성 검사 결과를 처리할 콜백함수
====== 예제 ======
const secretKey = ''; // 아까 token 만들때 썼던 secretkey
const router = (req, res) => {
const token = req.headers['x-access-token'] || req.query.token;
jwt.verify(token, secretKey,
function(err, decoded){
console.log(err) // 유효하지 않은 토큰
console.log(decoded) // 유효한 토큰, 유저 정보 Object 반환
}
}
// auth.resolver.ts
@UseGuards(GqlAuthRefreshGuard)
@Mutation(() => String)
async logout(
@Context() context: IContext, //
) {
return this.authService.logout({ req: context.req, res: context.res });
}
// auth.service.ts
// 따로 분리
verify({ accessToken, refreshToekn}){
try{
const decodeAccess = jwt.verify(accessToken,
process.env.JWT_ACCESS_KEY)
const decodeRefresh = jwt.verify(refreshToken,
process.env.JWT_REFRESH_KEY)
return ({decodeAccess,decodeRefresh})
}catch(e){
throw new UnauthorizedException('검증이 안된 인증번호입니다!')
}
}
async logout({req, res}){
// 0. 토큰만을 받아 올 수 있게 잘라준다.
const accessToken = req.headers['accessToken'].replace(
'Bearer ',
'',
)
const refreshToken = await context.req.headers['cookie'].split(
'refreshToken=',
)[1];
const refresh_Token = await refreshToken.split(' path=')[0];
// 1. 검증된 인증번호 인지 확인 . verify 을 바깥으로 빼준다.
const ({decodeAccess,decodeRefresh}) = this.verify({
accessToken, refreshToken
})
// 2. 레디스에 넣을때 , TTl을 넣어줘서 만료기간을 지정해주자.
await this.cacheManager.set(`accessToken:${accessToken}`, 'accessToken', {
ttl: jwtAccessKey['exp'] - jwtAccessKey['iat'],
});
await this.cacheManager.set(
`refresh_Token:${refresh_Token}`,
'refresh_Token',
{
ttl: jwtRefreshKey['exp'] - jwtRefreshKey['iat'],
},
);
// res는 어디에 쓰지...
return '로그아웃 만료'
// strategy에 다시 레디스에 토큰이 저장되어있는지 확인해준다.
}
// jwt - access strategy.ts
constructor(
@Inject(CACHE_MANAGER)
private readonly cacheManager: Cache,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_ACCESS_KEY,
passReqToCallback: true,
});
async validate(req, payload) {
const myAccessToken = req.headers.authorization.split('Bearer ')[1];
const cache = await this.cacheManager.get(`accessToken:${myAccessToken}`);
console.log(myAccessToken, '🦊🦊🦊🦊 hi');
if (cache !== null)
throw new UnauthorizedException('로그인이 필요한 유저입니다.');
return {
id: payload.sub,
};
}
}