본문 바로가기
공부/프로그래밍

[react, next.js] SSR환경에서 access_token, refresh_tokne 관리하기(cookie이용)

by demonic_ 2021. 3. 12.
반응형

로그인 한 후에 받은 access_token을 관리하는데는 여러가지가 있지만 여기서는 쿠키를 이용한 관리. 그리고 재활용을 확인해보려 한다.

 

처음 react를 공부할때 애먹었던 것중 하나도 이것이었는데, redux에 넣어두어도 새로고침하면 모두 사라지는 문제가 있었다. 이것에 대해 3가지 대안법이 있는데

1. Cookie에 저장

2. localStorage에 저장

3. Session에 저장

하는 방법이었다.

 

2번인 localStorage는 SSR이 적용되었을때 써먹지 못한다. 그래서 일단 제외.

세션을 이용한 방법은 아직 정리가 다 끝난 상태가 아니라 일단 1번인 Cookie에 저장하기로 했다.

 

다음 패키지를 설치한다.

npm i react-cookies
npm i @types/react-cookies

그리고 next-cookies를 하나 더 설치한다.

npm i next-cookies

cookie에 담겨있는 리소스를 json으로 리턴해주는 것인데, 예를들어 다음과 같이 여러개가 들어가 있을때 json으로 변환이 용이하다.

# cookie 내용: 
accessToken=AAA; refreshToken=BBB

# next-cookies 로 변환 후
{
  accessToken: 'AAA',
  refreshToken: 'BBB'
}

 

access_token을 서버에서 받으면 등록해야 할 곳은 2군대다.

1. axios

2. cookie

 

그리고 새로고침해서 axios에 설정이 날라갔다면 cookie에 저장되어 있는 access_token을 cookie에 옮겨줄 것이다. 우선은 access_token을 받았을때 저장하는 것부터 해보자.

 

TokenManager.tsx 파일을 만든 후 아래 내용을 추가했다.

import axios from "axios";
import cookie from 'react-cookies'
import { HTTP_ONLY } from "../config/config";

function setToken(accessToken:string, refreshToken:string) {

    axios.defaults.headers.Authorization = "Bearer " + accessToken;

    const expires = new Date()
    expires.setDate(Date.now() + 1000 * 60 * 60 * 24)

    cookie.save(
        'accessToken'
        , accessToken
        , {
            path: '/'
            , expires
            , httpOnly: HTTP_ONLY // dev/prod 에 따라 true / false 로 받게 했다.
        }
    )
    cookie.save(
        'refreshToken'
        , refreshToken
        , {
            path: '/'
            , expires
            , httpOnly: HTTP_ONLY
        }
    )
}

export {setToken}

accessToken과 refreshToken을 넘겨 받고 그중에 accessToken은 axios의 header에 설정한다.

만료시간을 1일로 계산하고 cookie를 생성할때 포함해준다.

 

cookie에 저장할때 httpOnly부분이 있는데, httpOnly는 자바스크립트로 document.cookie를 이용해 쿠키 접근을 막는 옵션이다. 도메인이 아닌환경, 즉 IP, localhost 등으로 접속하는 경우 저장되지 않는데, 문제는 개발하는동안은 localhost를 써야할 것이다. 이것을 처리해주기 위해 개발에서는 fales하기 위해 config에서 HTTP_ONLY값을 받아온다.

 

개발설정은 가볍게 다음과 같이 했다.

config/config.json

{
    "development": {
        "BACKEND_URL": "...",
        "HTTP_ONLY": false
    },
    "production": {
        "BACKEND_URL": "...",
        "HTTP_ONLY": true
    }
}

config/config.tsx

const configFile = require("../config/config.json")

const serverEnv = process.env.NODE_ENV
const config = configFile[serverEnv]

export const BACKEND_URL = config["BACKEND_URL"]
export const HTTP_ONLY = config['HTTP_ONLY']

 

로그인에 성공하면 TokenManager.tsx 파일의 setToken을 호출하면 된다.

login.tsx

import {setToken} from "../../function/TokenManager";
...
        // 호출 한 후 결과값 result.
        const accessToken = result.accessToken;
        const refreshToken = result.refreshToken;
        setToken(accessToken, refreshToken)
...

 

 

서버사이드(SSR)에서 설정하기

새로고침하면 axios의 설정이 리셋된다. 이것을 복구하기 위해선 _app.tsx 파일을 손봐야 한다.

_app.tsx 파일을 열어보자.

...
App.getInitialProps = async (appContext: AppContext) => {
    const appProps = await App.getInitialProps(appContext)
    return {...appProps}
}
...

 

초기 단순한 세팅만 되어있는데, 여기서 다음을 추가해 줄 것이다.

우선 next-cookie를 설치햇다면 import 하자

 

# _app.tsx 파일
...
import cookies from "next-cookies";
...​

appContext 안에 내용을 보면 다음의 4개 값이 있다.

[ 'AppTree', 'Component', 'router', 'ctx' ]

이중에 ctx 안에 있는 것을 뒤질것이다. ctx 안의 내용을 보자

...
InshopApp.getInitialProps = async (appContext: AppContext) => {
    const appProps = await App.getInitialProps(appContext)
    
    const {ctx} = appContext;
    console.log(Object.keys(ctx));
...
[
  'err',     'req',
  'res',     'pathname',
  'query',   'asPath',
  'AppTree'
]

저 중 req 내 header에 쿠키가 같이 포함되어 들어올것이다. 다음코드를 입력해 cookie 내용을 확인하자.

...
    const cookieReq = ctx.req ? ctx.req.headers.cookie : null
    console.log("cookieReq: ", cookieReq)
...
cookieReq:  accessToken=AAA; refreshToken=BBB

next-cookie 를 이용해 header에 있는 cookie를 json으로 변경한다.

...
    const allCookies = cookies(ctx);
    console.log("allCookies: ", allCookies)
    const accessTokenByCookie = allCookies['accessToken'];
    console.log("accessTokenByCookie: ", accessTokenByCookie)
...

출력내용

allCookies:  {
  accessToken: 'AAA',
  refreshToken: 'BBB'
}
accessTokenByCookie:  AAA

이 설정을 이전에 만들어둔 setToken 에다 던져주는 것으로 마무리하면 될 듯 하다.

import {setToken} from "../function/TokenManager";
import cookies from "next-cookies";
...

...
App.getInitialProps = async (appContext: AppContext) => {
    const appProps = await App.getInitialProps(appContext)

    const {ctx} = appContext;
    const allCookies = cookies(ctx);
    const accessTokenByCookie = allCookies['accessToken'];
    if(accessTokenByCookie !== undefined) {
        const refreshTokenByCookie = (allCookies["refreshToken"] || "");
        setToken(accessTokenByCookie, refreshTokenByCookie)
    }

    return {...appProps}
}
...

로그인한 유저정보를 redux에 넣어야 한다면 받은 accessToken으로 유저정보를 요청하는 코드를 추가하면 된다

 

 

끝.

 

 

반응형

댓글