login process

전은평·2023년 4월 16일
0

지난 블로그에서 JWT 와 인증,인가에 대해 알아보았는데, 이번 블로그에서는 앞의 개념들이 사용되는 로그인 프로세스에 대해서 알아보자!

개발 공부를 하기 전, 항상 궁금했던 것 중 하나가 로그인 프로세스인데, 그래서인지 로그인 부분이 배우면서도 재밌게 공부했던 부분 중 하나다.

앞서 언급했던 JWT, 인증, 인가 블로그에서 로그인 프로세스에 대해 매번 언급을 하다보니, 그렇게 많은 이야기를 안해도 될 것 같지만, 한번 다시 상기시킨다는 생각으로 짚고 넘어가보자!!

위의 사진을 보면 로그인과 로그인 후 서비스의 로직에 대해 한눈에 볼 수 있다.
그럼 일단 accessToken과 refreshToken이 무엇인지 알아보자.

로그인을 하게 되면 유저 본인임을 증명할 수 있는 토큰을 발급받게 되는데 그것이 바로 accessToken이다. 이 토큰을 이용한다면, 유저들은 매번 로그인 할 필요없이 로그인 후 서비스(결제, 내정보조회 등)를 이용할 수 있다.

그렇다면 accessToken 하나만 있으면 될텐데 refreshToken은 또 뭘까?

accessToken을 JWT를 통해 암호화를 했다고는 하지만, 해킹의 우려가 있기 때문에 토큰의 만료시간이 무한정 길어진 수 없다. 그렇기 때문에 로그인시 만료시간이 조금 더 긴 refreshToken을 같이 발급해서 쿠키에 저장한다. 이미 여기서 다들 눈치채셨겠지만, refreshToken은 accessToken이 만료되었을 때 본인임을 증명하고 accessToken을 재발급할 수 있는 열쇠 같은 역할을 한다!!

// auth.service.ts

import { Injectable, UnprocessableEntityException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UsersService } from '../users/users.service';
import {
  IAuthServiceGetAccessToken,
  IAuthServiceLogin,
  IAuthServiceSetRefreshToken,
} from './interfaces/auth-service.interface';
import * as bcrypt from 'bcrypt';

@Injectable()
export class AuthService {
  constructor(
    private readonly jwtService: JwtService, //

    private readonly usersService: UsersService,
  ) {}

  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, res: context.res });

    // 5. 일치하는 유저도 있고, 비밀번호도 맞았다면?!
    //    => accessToken(=JWT)을 만들어서 브라우저에 전달하기
    return this.getAccessToken({ user });
  }

  setRefreshToken({ user, res }: IAuthServiceSetRefreshToken): void {
    const refreshToken = this.jwtService.sign(
      { email: user.email, sub: user.id },
      { secret: 'myRefreshKey', expiresIn: '2w' },
    );

    // 개발환경
    res.setHeader('Set-Cookie', `refreshToken=${refreshToken}`);

    // 배포환경
    // res.setHeader('Set-Cookie', `refreshToken=${refreshToken}; path=/; domain=.mybacksite.com; SameSite=None; Secure; httpOnly;`)
    // res.setHeader('Access-Control-Allow-Origin', 'https://myfrontsite.com')
  }

  getAccessToken({ user }: IAuthServiceGetAccessToken): string {
    return this.jwtService.sign(
      { sub: user.id },
      { secret: process.env.JWT_ACCESS_KEY, expiresIn: '1h' },
    );
  }
}

login 로직을 코드로 짜면 위와 같다.

코드를 보면 bcrypt 라이브러리를 사용해 비밀번호를 검증하는 과정accessToken과 refreshToken을 발급하는 로직을 볼 수 있을 것이다.

[bcrypt를 이용한 비밀번호 저장 및 비교]

비밀번호 같은 경우에는 데이터베이스에 그대로 저장하게 되면 나중에 해킹의 우려가 매우 크기 때문에, 암호화를 거치고 저장한다. 이때 암호화시 사용한 라이브러리가 bcrypt로 로그인시 입력한 비밀번호와 암호화된 비밀번호를 비교하는 과정을 코드에 넣었다.

accessToken / refreshToken 발급

JWT를 이용해서 토큰을 발급했는데, access 토큰은 만료기간을 한시간으로 설정했고, refresh는 access보다 길게 2주로 설정했다.

또 다른 차이점으로는 access 토큰은 발급하고 클라이언트(프론트)로 건네주었지만, refresh 토큰의 경우에는 발급 후 쿠키에 저장을 따로 해주었다.

오늘 이렇게 로그인의 전반적인 프로세스에 대해 알아봤는데, 다음 블로그에서 소셜로그인 API를 만드는 과정을 통해 더 알아보기로 하자!

출처 및 참고자료: 코드캠프 강의자료

profile
`아는 만큼 보인다` 라는 명언을 좋아합니다. 많이 배워서 많은 걸 볼 수 있는 개발자가 되고 싶습니다.

0개의 댓글