프론트 위주의 JWT Authentication

YUKI KIM·2022년 1월 15일
0

내가 나중에 보려고 정리한다. JWT 토큰을 이용한 Authentication. 개념과 로그인 구현을 위한 모든 것들을 프론트 위주에서 기록해보았다. 0 to 100. (해당 글은 미완성이며 추가하는 중입니당)


개념과 사용하는 이유

일단 JWT에 대해 알아보기 전에 session을 이용한 로그인 처리를 알고 있으면 좋은데 이 포스팅에서는 session에 대한 자세한 설명은 생략한다.

JWT의 개념

  • JSON Web Token의 약자로, 데이터가 JSON으로 이루어져 있는 토큰을 의미.
  • AccessToken과 RefreshToken으로 이루어져 있으며 이 두 토큰을 클라이언트에서 가지고 있다가 필요할 때 서버에 요청하여 인증 받음.

"이 두 토큰을 클라이언트에서 가지고 있다가" ... 오키. 그럼 클라이언트 어디에 저장하면 좋을까?

JWT 저장 위치

JWT 토큰의 저장 위치는 크게 웹 스토리지, 쿠키 두 곳으로 나뉜다.

  1. 웹 스토리지
    웹 스토리지에는 LocalStorage와 SessionStorage가 있다. 서버에서는 접근할 수 없다는 특징을 갖는다. 스토리지에는 바로 접근이 가능하기 때문에 XSS에 취약하다.

  2. 쿠키
    서버에서 접근할 수 있으며 HttpOnly 설정을 추가해서 XSS를 차단할 수 있다. Http Request 시 자동으로 포함된다.

결론적으로 웹 스토리지 방식은 구현이 더 쉽지만 보안 측면에서는 쿠키가 더 좋다.

JWT를 사용하는 이유

  • session과 같이 별도의 저장소가 필요 없기 때문에 stateless한 서버를 만들 수 있다.
  • 서버의 유지, 보수 측면에서와 확장성 측면에서 유리하다. (다른 인증 시스템으로 쉽게 확장 가능)

프론트 - 백 인증 절차

우선은 토큰이 만료 되었을 때 연장하는 것은 제외하고 설명한다. (그럼 이 경우는 AccessToken만 필요하겠지만 RefreshToken 없이 말하면 어색해서 언급함)

AccessToken을 이용한 인증

  1. [F] 로그인 페이지에서 얻은 ID, PW를 서버로 보낸다.
  2. [B] ID, PW를 검증하고 AccessToken과 RefreshToken, AccessToken의 만료시간을 반환해준다.
  3. [F] 서버로부터 받은 토큰을 저장한다.
  4. [F] 요청이 필요한 경우 AccessToken을 매 api 호출마다 헤더에 붙여서 전송한다.
  5. [B] 토큰이 유효한지 확인해서 이상이 없으면 api 호출에 대한 응답을 한다.

RefreshToken과 만료시간

그리고 토큰이 만료되었을 때 연장하는 절차는 아래와 같다.

  1. [F] 로그인 페이지에서 얻은 ID, PW를 서버로 보낸다.
  2. [B] ID, PW를 검증하고 AccessToken과 RefreshToken, AccessToken의 만료시간을 반환해준다. (RefreshToken은 서버의 DB에 저장되어 있음)
  3. [F] 서버로부터 받은 토큰을 저장한다.
  4. [F] 요청이 필요한 경우 AccessToken을 매 api 호출마다 헤더에 붙여서 전송한다.
  5. [B] 토큰이 유효한지 확인해서 이상이 없으면 api 호출에 대한 응답을 한다.
  6. [F] AccessToken의 만료 시간이 적게 남았다면 서버에 RefreshToken을 보내 Reissue 요청을 보낸다.
  7. [B] Reissue를 받고, RefreshToken이 유효한지 확인해서 AccessToken과 새로운 만료 시간을 반환한다.
  8. [F] 서버로부터 받은 AccessToken을 저장하다 필요 시 api 호출에 사용한다.

리액트에서 쿠키 사용하기

나는 보안 측면에서 유리한 쿠키에 토큰을 저장하기로 했다. 그럼 우선 npm 패키지를 사용해서 리액트에서 쿠키에 get, set 하는 방법에 대해 언급하고자 한다.

react-cookie 라이브러리을 사용하면 리액트 코드에서 쿠키를 손쉽게 사용할 수 있다. 자세한 설명은 패키지 설명을 참고 바람.

import React, { useState, useEffect } from "react";
import { useHistory } from "react-router-dom"
import { useCookies } from 'react-cookie';

const SigninForm = () => {
    const history = useHistory();
    const [userId, setUserId] = useState("");
    const [userPassword, setUserPassword] = useState("");
    // 'rememberUser'라는 key를 갖는 쿠키 생성
    const [cookies, setCookie, removeCookie] = useCookies(['rememberUser']);

    useEffect(() => {
        if(cookies.rememberUser !== undefined) {
            setUserId(cookies.rememberUser);
        }
     }, [cookies.rememberUser]);

    const handleSignin = (event) => {
        if (로그인 성공하면) {
            setCookie('rememberUser', userId, {path:'/', maxAge: 2000});
        } else {
            removeCookie('rememberUser');
        }
        history.go(0); // 새로고침
        event.stopPropagation();
    };

    return (
        <div>
            // 생략
        </div>
    )
}

우선은 Hook을 이용해서 JWT 토큰이 아닌 아이디 자체를 저장하는 간단한 예제를 진행해봤다. 매우 간단한 예제라 코드만 보면 금방 이해가 될 것이다. setCookie, removeCookie 등으로 쿠키를 쉽게 조작할 수 있다.

그리고 위 예제를 실행하고 개발자 모드를 켜서 확인하면 로그인 성공 시, 쿠키에 rememberUser라는 key로 쿠키가 세팅되는 것을 확인할 수 있다.

import React, { useEffect, useState } from "react";
import { Cookies } from "react-cookie";

const Navbar = () => {
    const cookies = new Cookies();
    // 생략

    return (
        <div>
            <Link >
                {cookies.get('rememberUser') ? "로그아웃" : "로그인"}
            </Link>
        </div>
    );
};

그리고 다른 컴포넌트에서 쿠키에 get 하고자 하면 위처럼 한다. 쿠키 객체를 생성해주고 cookies.get()으로 얻는다. 위 예제는 로그인이 되어 있으면 "로그아웃", 로그인이 안되어 있으면 "로그인" 이라고 나오게 하는 예제다.

이와 같은 방식으로 JWT 토큰을 저장하면 되는데, 그건 추후에 추가하겠음!


레퍼런스

profile
유키링と 욘데 쿠다사이

0개의 댓글