클라이언트로 부터 accessToken , refresh Token을 둘 다 받습니다.
=> 로그아웃 API가 호출되면 JWT를 Redis에 저장됩니다.
=> Redis에 넣을 때 expiration time 을 JWT의 exiration time 과 current time 을 계산해서 저장됩니다.
=> 만료 시간이 지난 토큰이면 Redis에서 자동 삭제됩니다.
=> 클라이언트가 기존 토큰으로 요청시 JwtFilter에 있는 validate 과정에서 redis로 해당 accessToken 있는지 확인합니다.
=> Redis에 값이 있으면 요청을 거부, 없으면 요청을 승인합니다.
1. auth.resolver.ts 에 logout API 생성
@UseGuards(GqlAuthRefreshGuard)
@Mutation(() => String)
async logout(
@Context() context: IContext, //
) {
return this.authService.logout({ req: context.req, res: context.res });
}
2. auth.service.ts에 Token을 검증 해줄 jwt.verify 로직과 logout API 로직 생성
verify({ accessToken, refreshToken }) {
try {
const decodedAccessToken = jwt.verify(
accessToken,
process.env.JWT_ACCESS_KEY,
);
const decodedRefreshToken = jwt.verify(
refreshToken,
process.env.JWT_REFRESH_KEY,
);
return { decodedAccessToken, decodedRefreshToken };
} catch (error) {
throw new UnauthorizedException('검증되지 않은 토큰입니다.');
}
}
async logout({ req, res }) {
const accessToken = req.headers.authorization.replace('Bearer ', '');
const refreshToken = req.headers.cookie.replace('refreshToken=', '');
const { decodedAccessToken, decodedRefreshToken } = this.verify({
accessToken,
refreshToken,
});
const date = new Date().getTime();
const AccessTokenTtl = Math.trunc(
(decodedAccessToken['exp'] * 1000 - date) / 1000,
);
const RefreshTokenTtl = Math.trunc(
(decodedRefreshToken['exp'] * 1000 - date) / 1000,
);
await this.cacheManager.set(`accessToken:${accessToken}`, accessToken, {
ttl: AccessTokenTtl,
});
await this.cacheManager.set(`refreshToken:${refreshToken}`, refreshToken, {
ttl: RefreshTokenTtl,
});
res.setHeader('Set-Cookie', `refreshToken=; path=/; Secure; httpOnly;`);
return '로그아웃 완료';
}
3. jwt-access.strategy.ts 와 jwt-refresh.strategy.ts의 validate에 재검증 로직 생성
async validate(req, payload) {
const accessToken = req.headers.authorization.replace('Bearer ', '');
const checkAccessToken = await this.cacheManager.get(
`accessToken:${accessToken}`,
);
if (checkAccessToken)
throw new UnauthorizedException('로그인이 필요한 서비스입니다.');
// console.log(payload);
return {
email: payload.email,
id: payload.sub,
};
}