[2024.05.22 TIL] 내일배움캠프 26일차 (Access, Refresh Token)

My_Code·2024년 5월 25일
0

TIL

목록 보기
35/112
post-thumbnail

본 내용은 내일배움캠프에서 활동한 내용을 기록한 글입니다.


💻 TIL(Today I Learned)

📌 Today I Done

✏️ Access Token이란?

  • Access Token은 사용자 인증(로그인)이 완료된 후 해당 사용자를 인증하는 용도로 발급하는 토큰

  • 쿠키에 jwt를 설정하고, 만료 시간이 지나면 인증이 만료되는 구조 또한 Access Token이라고 할 수 있음

  • Access Token은 서버에 상태를 저장하지 않는 Stateless(무상태)이기 때문에 Node.js 서버가 재시작되더라도 동일하게 동작함

  • jwt를 이용해 사용자의 인증 여부를 확인할 수 있지만, 처음 토큰을 발급한 사용자가 그 사용자인지 확인할 수 없음

  • Access Token은 사용자 인증에 필요한 모든 정보를 가지고 있기 때문에 토큰을 가지고 있는 시간이 길수록 탈취되었을 때 큰 피해를 입음

  • 그래서 Access Token의 유효 기간은 짧고, 평소 API 통신할 때 사용


✏️ Refresh Token이란?

  • Refresh Token은 특정 사용자가 Access Token을 발급받기 위한 목적으로만 사용됨

  • 사용자의 인증 정보를 검증하는데 사용되며, 이를 서버에서 관리함

  • Refresh Token은 JWT 토큰의 탈취 위험(피해)을 최소화하고, 사용자 경험을 높이기 위해(빈번한 재로그인 방지를 위해) 사용

  • Refresh Token은 사용자가 서버와 최초 인증시에 발급받게 됨

  • Refresh Token의 유효 기간은 길고, Access Token 재발급 받을 때 사용


✏️ Access, Refresh Token 사용 예제

import express from 'express';
import jwt from 'jsonwebtoken';
import cookieParser from 'cookie-parser';

const app = express();
const PORT = 3019;

// 비밀 키는 외부에 노출되면 안되겠죠? 그렇기 때문에, .env 파일을 이용해 비밀 키를 관리함
const ACCESS_TOKEN_SECRET_KEY = `HangHae99`; // Access Token의 비밀 키를 정의함
const REFRESH_TOKEN_SECRET_KEY = `Sparta`; // Refresh Token의 비밀 키를 정의함

app.use(express.json());
app.use(cookieParser());

app.get('/', (req, res) => {
  return res.status(200).send('Hello Token!');
});

const tokenStorages = {}; // 리프레시 토큰을 관리할 객체
// 여기서는 변수를 사용했지만 데이터베이스에 넣어서 관리할 수 있음

// 엑세스, 리프레시 토큰 발급 API
app.post('/tokens', async (req, res) => {
  // ID 전달
  const { id } = req.body;

  // 엑세스 토큰과 리프레시 토큰 발급
  const accessToken = createAccessToken(id);
  const refreshToken = jwt.sign({ id: id }, REFRESH_TOKEN_SECRET_KEY, {
    expiresIn: '7d',
  });

  tokenStorages[refreshToken] = {
    id: id,
    ip: req.ip,
    userAgent: req.headers['user-agent'],
  };

  console.log(tokenStorages);

  // 클라이언트에게 쿠키(토큰) 할당
  res.cookie('accessToken', accessToken);
  res.cookie('refreshToken', refreshToken);

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

// Access Token 검증 API
app.get('/tokens/validate', async (req, res) => {
  const { accessToken } = req.cookies;

  // 엑세스 토큰이 존재하는지 확인
  if (!accessToken) {
    return res
      .status(400)
      .json({ errorMessage: 'Access Token이 존재하지 않습니다.' });
  }

  const payload = validationToken(accessToken, ACCESS_TOKEN_SECRET_KEY);
  if (!payload) {
    return res
      .status(401)
      .json({ errorMessage: 'Access Token이 정상적이지 않습니다.' });
  }

  const { id } = payload;
  return res.status(200).json({
    message: `${id}의 Payload를 가진 Token이 정상적으로 인증되었습니다.`,
  });
});

// Token을 검증하고, Payload를 조회하기 위한 함수
function validationToken(token, secretKey) {
  try {
    return jwt.verify(token, secretKey);
  } catch (err) {
    return null;
  }
}

function createAccessToken(id) {
  return jwt.sign({ id }, ACCESS_TOKEN_SECRET_KEY, {
    expiresIn: '10s',
  });
}

// Refresh Token을 이용해서, Access Token을 재발급하는 API
app.post('/tokens/refresh', async (req, res) => {
  const { refreshToken } = req.cookies;
  if (!refreshToken) {
    return res
      .status(400)
      .json({ errorMessage: 'Refresh Token이 존재하지 않습니다.' });
  }

  const payload = validationToken(refreshToken, REFRESH_TOKEN_SECRET_KEY);
  if (!payload) {
    return res
      .status(401)
      .json({ errorMessage: 'Refresh Token이 정상적이지 않습니다.' });
  }

  const userInfo = tokenStorages[refreshToken];
  if (!userInfo) {
    return res.status(409).json({
      errorMessage: 'Refresh Token의 정보가 서버에 존재하지 않습니다.',
    });
  }

  const newAccessToken = createAccessToken(userInfo.id);

  res.cookie('accessToken', newAccessToken);

  return res
    .status(200)
    .json({ message: 'Access Token을 정상적으로 새롭게 발급되었습니다.' });
});

app.listen(PORT, () => {
  console.log(PORT, '포트로 서버가 열렸어요!');
});


📌 Tomorrow's Goal

✏️ 노드 숙련 강의 시청하기

  • 2주차 남은 강의를 시청

  • 최대한 시청 후 과제 문서를 작성할 수 있도록 노력할 것!!

  • 내일도 시간이 많지 않기 때문에 오전, 오후에 최대한 시청할 예정



📌 Today's Goal I Done

✔️ 노드 숙련 강의 시청하기

  • 예비군으로 밀린 강의를 시청함

  • 원래는 2주차를 오늘 안에 다 시청하려고 했으나, 생각보다 내용이 어려웠음

  • 완전히 막힌다기 보다는 내용이 어려워서 검색하면서 이해하느라 진행이 느렸음

  • 현재 2주차 1/3 정도 완료한 상태

  • 주말을 이용해서 최대한 강의를 들을 예정


profile
조금씩 정리하자!!!

0개의 댓글