본 캠프 20강 TIL

정희준·2023년 4월 13일
0
post-thumbnail

📌 오늘의 내용

오늘은 어제 진행했던 로그인 과정에서 Refresh Token을 포함한 토큰 발급과
쿠키에 담아주어 다시 AccessToken 을 발급하는 기능을 배워보았다
오늘의 내용에 대해 알아보자

Refresh Token?

로그인 요청을 하고 나서, 서버에서 토큰을 프론트에게 넘겨줄 때, 토큰을 하나 더 만들어서 넘겨줍니다. 하나 더 만든 토큰을 refresh token이라고 하고 기존에 발행하던 토큰을 access token이라고 합니다.

refresh token은 access token이 만료되었을 때, access token을 다시 발행하기 위한 용도로 쓸 것이기 때문에 access token보다 유효기간이 길어야 합니다.

### Why Refresh Token?

Access Token(JWT)를 통한 인증 방식의 문제는 해킹을 당했을 경우 보안에 취약하다는 점이 있습니다.

유효기간이 짧은 토큰의 경우 그만큼 사용자는 로그인을 자주 해서 새롭게 토큰을 발급받아야 하므로 불편합니다.

그렇다고 유효기간을 늘리면 토큰을 해킹당했을 때 보안에 더 취약해지게 됩니다.

이러한 점들을 보완하는 것이 Refresh Token입니다.

  • refresh token은 access token과 같은 형태의 JWT입니다. refresh token은 처음에 로그인을 완료했을 때 access token과 동시에 발급됩니다. access token보다 긴 유효기간을 가지면서 access token이 만료되었을 때 새로 발급해 주는 열쇠가 됩니다.
  • access token이 해킹 당하면 정보가 유출됩니다. 하지만 유효기간을 짧게 해두면(30분 ~ 2시간) 그 기간 안에서만 사용이 가능하기 때문에 더 안전하다는 의미가 됩니다.
  • refresh token의 유효기간이 만료되면, 사용자는 새로 로그인 해야 합니다. refresh token도 해킹될 가능성이 있기 때문에 적절한 유효기간(2주 ~ 2달) 설정이 필요합니다.


login ( + JWT기반 refreshToken 쿠키에 저장 )

새로 배운 위 개념들을 토대로 어제 만든 로그인 로직을 수정 해보자!

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,
    };
  }
}

액세스토큰과 달리 리프레시토큰은쿠키에 담겨있기때문에 헤더에 쿠키 정보를 가져와야 한다!

profile
같이 일하고 싶은 사람이 되어보자! 다시 시작하는 개발 블로그.

0개의 댓글