노드숙련_3

·2022년 12월 19일
0

Access Token

  • 사용자의 권한이 확인(ex 로그인) 되었을 경우 해당 사용자를 인증하는 용도로 발급

  • Cookie로 jwt를 발급하고 설정한 만료 기간이 지날 때 인증이 만료되게 하는것

  • 사용자가 Access Token을 가지고 인증을 요청할 경우, Token을 생성할 때 사용한 비밀키(Secret Key)를 가지고 인증하기 때문에, 복잡한 설계없이 코드를 구현할 수 있고, 여러 분기를 거치지 않아도 된다는 장점이 있다.

  • jwt를 이용해 사용자의 인증 여부는 확인할 수 있지만, 처음 발급한 사용자 본인인지 확인할 수는 없습니다.

  • 사용자를 인증하는 모든 정보를 가지고 있어 토큰을 가지고 있는 시간이 늘어날 수록 탈취되었을 때 피해가 더욱 커짐
    언제든지 사용자의 토큰이 탈취될 수 있다고 생각을 하고, 피해를 최소화 할 수 있는 방향으로 개발을 진행해야함

Refresh Token

  • Access Token처럼 해당하는 사용자의 모든 인증 정보를 관리하는 것이 아닌, 특정한 사용자가 Access Token을 발급받을 수 있게 하기 위한 용도로만 사용

  • 사용자의 인증정보를 사용자가 가지고 있는 것이 아닌, 서버에서 해당 사용자의 정보를 저장소 또는 별도의 DB에 저장하여 관리한다.
    그렇기 때문에, 서버에서 특정 Token 만료가 필요할 경우 저장된 Token을 제거하여 사용자의 인증 여부를 언제든지 제어가 가능하다는 장점이 있다.

  • 바로 Access Token을 발급하지 않고, Refresh Token을 거쳐서 Access Token을 발급하는것일까?
    => 사용자에게 발급한 Token이 탈취당할 경우 피해를 최소화 하기 위해서 사용

  • 짧은 시간 내에서만 인증 정보를 사용할 수 있게하고, 주기적으로 재발급하여, 토큰이 유출되더라도 오랜 기간동안 피해를 입는것이 아닌, 짧은 기간동안만 사용가능하도록 하여 피해를 최소화할 수 있게 됩니다.

Refresh Token API 목록

  1. 패키지 설치
// 패키지 설치

npm init -y
npm install express jsonwebtoken cookie-parser -S
  1. Refresh Token과 Access Token을 발급하는 API
  • createAccessTokenAccess Token을 생성하는 함수에요!
    jwt 안에는 set-token API를 호출할 때 받은 id 변수를 삽입, 해당 사용자의 id가 무엇인지 확인할 때에는 Access Token에 있는 데이터를 바탕으로 인증을 진행

  • createRefreshTokenRefresh Token을 생성하는 함수에요!
    해당하는 Refresh Token에 대한 정보는 서버에서 tokenObject라는 변수안에 할당하게 되어 jwt 안에는 데이터가 존재하지 않음
    만약 서버에서 해당하는 Refresh Token에 대한 정보를 가지고 있지 않으면 Token의 인증은 실패함

사용자가 GET /set-token/:id API를 호출했을때 Access Token과 Refresh Token을 2개 발급하게 되고, Refresh Token을 Key 값으로 입력된 id를 찾을 수 있게 구현하였습니다.

그리고 accessToken, refreshToken이라는 Key로 Cookie를 2개 발급하게 됩니다.

// Refresh Token과 Access Token을 발급하는 API
// app.js


let tokenObject = {}; // Refresh Token을 저장할 Object

app.get("/set-token/:id", (req, res) => {
  const id = req.params.id;
  const accessToken = createAccessToken(id);
  const refreshToken = createRefreshToken();

  tokenObject[refreshToken] = id; // Refresh Token을 가지고 해당 유저의 정보를 서버에 저장합니다.
  res.cookie('accessToken', accessToken); // Access Token을 Cookie에 전달한다.
  res.cookie('refreshToken', refreshToken); // Refresh Token을 Cookie에 전달한다.

  return res.status(200).send({ "message": "Token이 정상적으로 발급되었습니다." });
})

// Access Token을 생성합니다.
function createAccessToken(id) {
  const accessToken = jwt.sign(
    { id: id }, // JWT 데이터
    SECRET_KEY, // 비밀키
    { expiresIn: '10s' }) // Access Token이 10초 뒤에 만료되도록 설정합니다.

  return accessToken;
}

// Refresh Token을 생성합니다.
function createRefreshToken() {
  const refreshToken = jwt.sign(
    {}, // JWT 데이터
    SECRET_KEY, // 비밀키
    { expiresIn: '7d' }) // Refresh Token이 7일 뒤에 만료되도록 설정합니다.

  return refreshToken;
}
  1. Refresh Token과 Access Token을 검증하는 API
  • validateAccessTokenvalidateRefreshToken은 사용자가 전달한 Token이 정상적인 토큰인지 확인하는 함수에요!
    => 발급한 토큰이 맞는지 검증 & 만료 여부를 검증

  • GET /get-token API 에서는 어떤 에러들이 발생하나요 ?

    1. 사용자가 Cookie를 전달할 때, Access Token이 없다면 에러가 발생합니다.
      • { "message": "Access Token이 존재하지 않습니다." }
    2. 사용자가 Cookie를 전달할 때, Refresh Token이 없다면 에러가 발생합니다.
      • { "message": "Refresh Token이 존재하지 않습니다." }
    3. 사용자가 전달한 Refresh Token이 인증되지 않았다면 에러가 발생합니다.
      • { "message": "Refresh Token이 만료되었습니다." }
    4. Refresh Token이 인증되었지만, 서버에 존재하지 않을 때 에러가 발생합니다.
      • { "message": "Refresh Token의 정보가 서버에 존재하지 않습니다." }

Thuner Client로 API 테스트하기

  • GET /set-token/:id API를 호출

  • GET /get-token API를 호출

// Refresh Token과 Access Token을 검증하는 API
// app. js

app.get("/get-token", (req, res) => {
  const accessToken = req.cookies.accessToken;
  const refreshToken = req.cookies.refreshToken;

  if (!refreshToken) return res.status(400).json({ "message": "Refresh Token이 존재하지 않습니다." });
  if (!accessToken) return res.status(400).json({ "message": "Access Token이 존재하지 않습니다." });

  const isAccessTokenValidate = validateAccessToken(accessToken);
  const isRefreshTokenValidate = validateRefreshToken(refreshToken);

  if (!isRefreshTokenValidate) return res.status(419).json({ "message": "Refresh Token이 만료되었습니다." });


  if (!isAccessTokenValidate) {
    const accessTokenId = tokenObject[refreshToken];
    if (!accessTokenId) return res.status(419).json({ "message": "Refresh Token의 정보가 서버에 존재하지 않습니다." });

    const newAccessToken = createAccessToken(accessTokenId);
    res.cookie('accessToken', newAccessToken);
    return res.json({ "message": "Access Token을 새롭게 발급하였습니다." });
  }

  const { id } = getAccessTokenPayload(accessToken);
	return res.json({ "message": `${id}의 Payload를 가진 Token이 성공적으로 인증되었습니다.` });
})


// Access Token을 검증합니다.
function validateAccessToken(accessToken) {
  try {
    jwt.verify(accessToken, SECRET_KEY); // JWT를 검증합니다.
    return true;
  } catch (error) {
    return false;
  }
}

// Refresh Token을 검증합니다.
function validateRefreshToken(refreshToken) {
  try {
    jwt.verify(refreshToken, SECRET_KEY); // JWT를 검증합니다.
    return true;
  } catch (error) {
    return false;
  }
}

// Access Token의 Payload를 가져옵니다.
function getAccessTokenPayload(accessToken) {
  try {
    const payload = jwt.verify(accessToken, SECRET_KEY); // JWT에서 Payload를 가져옵니다.
    return payload;
  } catch (error) {
    return null;
  }
}
profile
개발자가 되는 과정

0개의 댓글