[13일차] 회원가입 구현(jwt)

h-a-n-a·2023년 8월 20일

💫Ed Sheeran

목록 보기
13/24

백엔드가 없으면 JWT 가 변조되지 않았는지 확인할 수 없기에 사용자를 완전히 인증하는 것은 불가능하겠지만, 지금은 진짜 서비스라기보다는 사용자 인증을 구현해보려는 것이 목적이기에 json-server-auth 을 사용하기로 했다.

로그인을 구현하는 방법도 여러가지인데, 쿠키, 세션, JWT 중 JWT에 대해 오늘 개념공부를 쪼꼼하고 넘어가려고 한다!!

이전에 쿠키,세션에 대해 공부하고 적은 글

jwt

json web token의 줄임말로, 특정한 json 데이터(유저 정보)에 대해서 암호화를 하여, 이를 유저의 인증정보로서 사용한다. 가장 큰 특징으로는 무상태(stateless)가 있는데, 유저의 인증정보를 서버에 저장하지 않아도 되므로 세션 인증 방식의 여러 문제점들을 해결할 수 있게 된 것이다. 메모리를 잡아먹을 일도 없고, 여러 사용자가 접속해도 과부하가 없으며 확장에 용이하고 개발 및 유지보수 비용도 많이 들지 않는다.

구조

JWT는 .을 구분자로 나누어지는 세 가지 문자열의 조합으로, 다음과 같다.

  • header: 토큰의 타입과 해싱 알고리즘 방식을 규정

  • payload: 서버와 클라이언트가 주고받는 시스템에서 실제로 사용될 정보에 대한 내용 포함

  • signature: (헤더+페이로드)와 서버가 갖고 있는 유일한 key값을 합친 것을 헤더에서 정의한 알고리즘으로 암호화

accessToken을 통한 인증

사용자가 ID, PW을 입력하여 서버에 로그인 인증을 요청하면, 서버는 Header, Payload, Signature을 정의한다.

그리고 각각 base64로 한 번 더 암호화하여 JWT를 생성하고 이를 쿠키에 담아 클라이언트에게 발급한다.

클라이언트는 서버로부터 받은 JWT를 로컬스토리지에 저장한다.(쿠키나 다른곳일 수도 있음) 그리고 서버에 API를 요청할 때 authorization header에 access token을 담아서 보낸다.

서버가 할 일은 클라이언트가 header에 담아서 보낸 JWT가 서버에서 발행한 토큰인지 일치여부를 확인하여 통과를 시키거나 시키지 않는 것이다.

refreshToken

access token만을 통한 인증방식의 문제는 만일 제 3자에게 탈취당할 경우 보안에 취약하다는 점이다.

access token은 발급된 이후, 서버에 저장되지 않고 토큰 자체로 검증을 하며 사용자 권한을 인증한다. 그래서 access token이 탈취되면 토큰이 만료되기 전까지는 토큰을 획득한 사람은 누구나 권한 접근이 가능해진다.

JWT는 발급한 후 삭제가 불가능하기 때문에 접근에 관여하는 토큰에 유효시간을 부여하는 식으로 탈취문제에 대응을 해야 한다.

그렇다고 무작정 토큰 유효시간을 짧게 할수만도 없는것이, 그만큼 사용자는 로그인을 자주해서 새롭게 token을 재발급받아야 하기 때문이다. 그래서 "유효기간을 짧게 하면서 보안도 취약하지 않게 해줄수 있는 방법"으로 나온 것이 refresh token 이다.

refresh token 역시 access token과 똑같은 JWT이지만, access token은 접근에 관여하는 토큰이고, refresh token은 재발급에 관여하는 토큰이라 할 수 있다.

처음에 로그인을 했을 때, 서버는 로그인을 성공시키면서 클라이언트에게 access token과 refresh token을 동시에 발급받는다. 서버는 데이터베이스에 refresh token을 저장하고 클라이언트는 access token과 refresh token을 쿠키, 세션 혹은 웹스토리지에 저장하고 요청이 있을때마다 이 둘을 헤더에 담아서 보낸다.

이 refresh token은 긴 유효기간을 가지면서, access token이 만료됐을 때 새로 재발급하게 해주는 열쇠가 된다. 만일 만료된 access token을 서버에 보내면, 서버는 같이 보내진 refresh token을 DB에 있는 것과 비교해서 일치하면 다시 access token을 재발급해주는 원리이다. 그리고 사용자가 로그아웃을 하면 저장소에서 refresh token을 삭제하여 사용이 불가능하도록 하고 새로 로그인하면 서버에서 다시 재발급해서 DB에 저장한다.

yarn add json-server-auth json-server

json-server-auth를 설치했는데도 계속 Error: Cannot find module 'json-server' 라는 에러가 떴다. 알고보니 json-server-auth와 별개로 json-server를 설치해줘야 했다. 사실 공식문서만 제대로 읽었어도 이렇게 고생할 일은 없었을텐데ㅠㅠ 공식문서 첫줄에 둘 다 설치하라고 써있다.

회원가입

공식문서에 따르면POST/register , POST/signup , POST/users 어디로 해도 상관없다고 한다.
포스트맨 실험 결과 성공!

로그인

✅로그인도 잘 되는 모습을 확인할 수 있었다.


❌ 잘못된 비밀번호를 입력했을 때

이제 본격적으로 사이트에 적용해보도록 하겠다!

코드

axios 이용해서 회원가입, 로그인 구현!

//src/api/axios.ts
import axios, {
  InternalAxiosRequestConfig,
  AxiosResponse,
  AxiosError,
  HttpStatusCode,
} from 'axios';

const createAccount = async (data: FormData) => {
  try {
    const response = await axios({
      url: '/register',
      method: 'post',
      baseURL: 'http://localhost:8000',
      data: data,
    });
    return response.data;
  } catch (err) {
    console.log(err);
  }
};

const loginUser = async (data: FormData) => {
  try {
    const response = await axios({
      url: '/login',
      method: 'post',
      baseURL: 'http://localhost:8000',
      data: data,
    });
    return response.data;
  } catch (err) {
    console.log(err);
  }
};

아래엔 로그인 부분에 연결한 코드이다.

import React from 'react';
import { useForm } from 'react-hook-form';
import * as S from './style';
import { blue } from '@mui/material/colors';
import { useRef } from 'react';
import { createAccount } from '@src/api/axios';

export interface FormData {
  email: string;
  password: string;
  confirmPassword?: string | undefined;
}

const SignupForm = () => {
  const {
    register,
    handleSubmit,
    watch,
    reset,
    formState: { errors },
  } = useForm<FormData>({ mode: 'onChange' });

  const passwordRef = useRef<string | null>(null);
  passwordRef.current = watch('password');
  const onSubmit = (data: FormData) => {
    console.log('Submitted data:', data);
    const userData = {
      email: data.email,
      password: data.password,
      confirmPassword: data.confirmPassword,
    };
    createAccount(userData)
      .then((response) => {
        console.log('userData', userData);
        console.log('success', response);
        reset();
      })
      .catch((err) => console.log(err));
  };
return (
    <S.FormContainer onSubmit={handleSubmit(onSubmit)}>
    (중략)
    </S.FormContainer>
  );
};

button

mui에서 button은 type에 'submit'을 적어줘야 하는군...
보통 대부분의 브라우저에서 button은 default 값이 submit이어서 안 적어줬는데, 계속 폼 제출이 안돼서 당황했다.

참고한 이슈

 <S.StyledButton variant='contained' type='submit'>
        로그인
 </S.StyledButton>

오늘의 결과물

참고사이트: 오늘만큼은 로그인을 부셔보자. by leehyunho2001
JWT 토큰 인증 이란? (쿠키 vs 세션 vs 토큰) by 인파


오늘의 일기:

아잇 힘들어....ㅎ

profile
하루하루가 연습이니 내일은 더 강해질 겁니다

0개의 댓글