๐Ÿช COOKIE, SESSION, JWT, ACCESS TOKEN, REFRESH TOKEN

yyยท2023๋…„ 11์›” 8์ผ
1

์žก๋™์‚ฐ์ด

๋ชฉ๋ก ๋ณด๊ธฐ
1/21

๋กœ๊ทธ์ธ, ํšŒ์›๊ฐ€์ž…, ์‚ฌ์šฉ์ž์ธ์ฆ


๐Ÿ‘‰ COOKIES + SESSION

(cookies with session ID)

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

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

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

// 1. **cookie-parser** ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ ์šฉํ•ด์ฃผ์„ธ์š”!
// 2. `GET` Method๋กœ `http://localhost:5001/set`์„ ํ˜ธ์ถœํ–ˆ์„ ๋•Œ, **name**์ด๋ผ๋Š” ์ด๋ฆ„์„ ๊ฐ€์ง„ โ€œ**nodejsโ€** ๋ฌธ์ž์—ด์„ ์ €์žฅํ•œ **์ฟ ํ‚ค๋ฅผ ๋ฐ˜ํ™˜**ํ•ด์ฃผ์„ธ์š”.
// 3. `GET` Method๋กœ `http://localhost:5001/get`์„ ํ˜ธ์ถœํ–ˆ์„ ๋•Œ, ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์ „๋‹ฌ๋ฐ›์€ ๋ชจ๋“  ์ฟ ํ‚ค ์ •๋ณด๋“ค์ด ๋ฐ˜ํ™˜๋˜๋Š” API๋ฅผ ๋งŒ๋“ค์–ด์ฃผ์„ธ์š”!

// ์ฟ ํ‚ค ์ด์šฉ
app.get('/set', (req, res) => {
  res.cookie('name', 'nodejs');
  //res.cookie๋ฅผ ํ†ตํ•ด ํด๋ผ์ด์–ธํŠธํ•œํ…Œ ์ฟ ํ‚ค๋ฅผ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Œ
  return res.status(200).end();
});

app.get('/get', (req, res) => {
  const cookies = req.cookies;
  console.log(cookies);
  return res.status(200).json({ cookies });
});

// ์„ธ์…˜์ด์šฉ (uniqueInt์— ์‹œ๊ฐ„์„ ๋„ฃ์–ด์„œ ๊ตฌ๋ถ„ํ•˜๊ธฐ)
let session = {};
app.get('/set-session', (req, res) => {
  const name = '์œค์˜';
  const uniqueInt = Date.now();
  session[uniqueInt] = { name };

  console.log(session[uniqueInt]); //{ name: '์œค์˜' }

  res.cookie('sessionKey', uniqueInt);
  // sessionKey๋ฅผ ๋ฐœ๊ธ‰ํ• ๊ฑด๋ฐ ๊ทธ ์•ˆ์— uniqueInt๋กœ sessionKey๋ฅผ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์Œ.
  return res.status(200).end();
});

app.get('/get-session', (req, res) => {
  const { sessionKey } = req.cookies;
  console.log(req.cookies); //{ sessionKey: '1699348802445' }
  console.log(sessionKey); //1699348721612 -> ํ•  ๋•Œ๋งˆ๋‹ค ๋ณ€๊ฒฝ๋จ
  console.log(session); //{ '1699348721612': { name: '์œค์˜' } }
  const name = session[sessionKey];
  console.log(name); //{ name: '์œค์˜' }
  return res.status(200).json({ name });
});

app.listen(PORT, () => {
  console.log(PORT, 'ํฌํŠธ๋กœ ์„œ๋ฒ„๊ฐ€ ์—ด๋ ธ์–ด์š”!');
});

EXPRESS-SESSION ์ด์šฉ

์ฟ ํ‚ค์™€ ์„ธ์…˜์„ ์ด์šฉํ•ด์„œ ๊ทผ๋ณธ์ ์œผ๋กœ ์ €๋ ‡๊ฒŒ ์ฝ”๋“œ๋ฅผ ์งค ์ˆ˜ ์žˆ์ง€๋งŒ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ด์šฉํ•ด์„œ ๊ฐ„๋‹จํ•˜๊ฒŒ ์งค ์ˆ˜๋„ ์žˆ๋‹ค.

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์น˜

// express, express-session๋ฅผ ์„ค์น˜
yarn add express express-session
// app.js
import express from 'express';
import expressSession from 'express-session';

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

app.use(express.json());
app.use(
  expressSession({
    secret: 'express-session-secret-key.', // ์„ธ์…˜์„ ์•”ํ˜ธํ™”ํ•˜๋Š” ๋น„๋ฐ€ ํ‚ค๋ฅผ ์„ค์ •
    resave: false, // ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์ด ์˜ฌ ๋•Œ๋งˆ๋‹ค ์„ธ์…˜์„ ์ƒˆ๋กญ๊ฒŒ ์ €์žฅํ•  ์ง€ ์„ค์ •, ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ์—†์–ด๋„ ๋‹ค์‹œ ์ €์žฅ
    saveUninitialized: false, // ์„ธ์…˜์ด ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์•˜์„ ๋•Œ ์„ธ์…˜์„ ์ €์žฅํ•  ์ง€ ์„ค์ •
    cookie: {
      // ์„ธ์…˜ ์ฟ ํ‚ค ์„ค์ •
      maxAge: 1000 * 60 * 60 * 24, // ์ฟ ํ‚ค์˜ ๋งŒ๋ฃŒ ๊ธฐ๊ฐ„์„ 1์ผ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
    },
  }),
);

app.listen(PORT, () => {
  console.log(PORT, 'ํฌํŠธ๋กœ ์„œ๋ฒ„๊ฐ€ ์—ด๋ ธ์–ด์š”!');
});
/** ์„ธ์…˜ ๋“ฑ๋ก API **/
app.post('/sessions', (req, res, next) => {
  const { userId } = req.body;

  // ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์ „๋‹ฌ๋ฐ›์€ userId๋ฅผ ์„ธ์…˜์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
  req.session.userId = userId;

  return res.status(200).json({ message: '์„ธ์…˜์„ ์„ค์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.' });
});

/** ์„ธ์…˜ ์กฐํšŒ API **/
app.get('/sessions', (req, res, next) => {
  return res.status(200).json({
    message: '์„ธ์…˜์„ ์กฐํšŒํ–ˆ์Šต๋‹ˆ๋‹ค.',
    session: req.session.userId ?? null, // ์„ธ์…˜์— ์ €์žฅ๋œ usrId๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.
  });
});

๋‹ค๋งŒ ์„œ๋ฒ„์— ์ž ๊น ์ €์žฅ๋œ ์ธ๋ฉ”๋ชจ๋ฆฌ ํ˜•์‹์ด๋ผ ์„œ๋ฒ„๋ฅผ ๊ป๋‹ค ํ‚ค๋ฉด ์„ธ์…˜์ •๋ณด๊ฐ€ ๋‚ ๋ผ๊ฐ„๋‹ค. ๊ณ ๋กœ mongoDB๋‚˜ mysql๋“ฑ ๊ณผ ๊ฐ™์€ DB์— ์—ฐ๊ฒฐํ•ด์„œ ์„ธ์…˜๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์„ธ์…˜id๋ฅผ ์ €์žฅํ•ด ๊ด€๋ฆฌํ•ด์•ผํ•œ๋‹ค.
// ์™ธ๋ถ€ ์„ธ์…˜ ์Šคํ† ๋ฆฌ์ง€๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ, express-mysql-session ๋ชจ๋“ˆ์„ ์„ค์น˜
yarn add express-mysql-session
// src/app.js
import express from 'express';
import cookieParser from 'cookie-parser';
import expressSession from 'express-session';
import expressMySQLSession from 'express-mysql-session';
import LogMiddleware from './middlewares/log.middleware.js';
import ErrorHandlingMiddleware from './middlewares/error-handling.middleware.js';
import UsersRouter from './routes/users.router.js';
import PostsRouter from './routes/posts.router.js';
import CommentsRouter from './routes/comments.router.js';

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

// MySQLStore๋ฅผ Express-Session์„ ์ด์šฉํ•ด ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
const MySQLStore = expressMySQLSession(expressSession);
// MySQLStore๋ฅผ ์ด์šฉํ•ด ์„ธ์…˜ ์™ธ๋ถ€ ์Šคํ† ๋ฆฌ์ง€๋ฅผ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค.
const sessionStore = new MySQLStore({
  user: 'root',
  password: 'aaaa4321',
  host: 'express-database.clx5rpjtu59t.ap-northeast-2.rds.amazonaws.com',
  port: 3306,
  database: 'community_hub',
  expiration: 1000 * 60 * 60 * 24, // ์„ธ์…˜์˜ ๋งŒ๋ฃŒ ๊ธฐ๊ฐ„์„ 1์ผ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
  createDatabaseTable: true, // ์„ธ์…˜ ํ…Œ์ด๋ธ”์„ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
});

app.use(LogMiddleware);
app.use(express.json());
app.use(cookieParser());
app.use(
  expressSession({
    secret: 'customized_secret_key', // ์„ธ์…˜์„ ์•”ํ˜ธํ™”ํ•˜๋Š” ๋น„๋ฐ€ ํ‚ค๋ฅผ ์„ค์ •
    resave: false, // ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์ด ์˜ฌ ๋•Œ๋งˆ๋‹ค ์„ธ์…˜์„ ์ƒˆ๋กญ๊ฒŒ ์ €์žฅํ•  ์ง€ ์„ค์ •, ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ์—†์–ด๋„ ๋‹ค์‹œ ์ €์žฅ
    saveUninitialized: false, // ์„ธ์…˜์ด ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์•˜์„ ๋•Œ ์„ธ์…˜์„ ์ €์žฅํ•  ์ง€ ์„ค์ •
    store: sessionStore, // ์™ธ๋ถ€ ์„ธ์…˜ ์Šคํ† ๋ฆฌ์ง€๋ฅผ MySQLStore๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
    cookie: {
      // ์„ธ์…˜ ์ฟ ํ‚ค ์„ค์ •
      maxAge: 1000 * 60 * 60 * 24, // ์ฟ ํ‚ค์˜ ๋งŒ๋ฃŒ ๊ธฐ๊ฐ„์„ 1์ผ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
    },
  }),
);
app.use('/api', [UsersRouter, PostsRouter, CommentsRouter]);
app.use(ErrorHandlingMiddleware);

app.listen(PORT, () => {
  console.log(PORT, 'ํฌํŠธ๋กœ ์„œ๋ฒ„๊ฐ€ ์—ด๋ ธ์–ด์š”!');
});




๐Ÿ‘‰ JWT (JasonWebToken)

sign ๋ฉ”์†Œ๋“œ : jwt ์ƒ์„ฑ
decode ๋ฉ”์†Œ๋“œ : jwt ๋ณตํ˜ธํ™”
verify ๋ฉ”์†Œ๋“œ : jwt ๋ณ€์กฐ ๊ฒ€์ฆ

  • ๋ณ€์กฐ๋˜์ง€ ์•Š์€ ๋ฐ์ดํ„ฐ : ๋ณตํ˜ธํ™”๋œ ๊ฐ’(payload) ์ถœ๋ ฅ
  • ๋ณ€์กฐ๋œ
import jwt from "jsonwebtoken";

const token = jwt.sign({ myPayloadData: 1234 }, "secretKey");
console.log(token); //eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJteVBheWxvYWREYXRhIjoxMjM0LCJpYXQiOjE2OTkzNTQzMTR9.YWfR1LNuKabazvkhWnipzwppNCHGprwlb9oporShoGQ

const decodedtoken = jwt.decode(token);
console.log(decodedtoken); //{ myPayloadData: 1234, iat: 1699354314 }

const decodedValueByVerify1 = jwt.verify(token, "secretKey");
console.log(decodedValueByVerify1); //๋งž๋‹ค๋ฉด ๋ณตํ˜ธํ™”๋œ ๊ฐ’ ๋ฐ˜ํ™˜{ myPayloadData: 1234, iat: 1699354608 }

// const decodedValueByVerify2 = jwt.verify(token, "SecretKey");
// console.log(decodedValueByVerify2); //JsonWebTokenError: invalid signature

๐Ÿ‘‰ ACCESS TOKEN + REFRESH TOKEN + JWT

// app.js
import express from 'express';
import jwt from 'jsonwebtoken';
import cookieParser from 'cookie-parser';

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

const ACCESS_TOKEN_SECRET_KEY = 'Hello';
const REFRESH_TOKEN_SECRET_KEY = 'World';

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

//-----------------------------------------------------------
//accessToken, refreshToken ๋งŒ๋“ค๊ธฐ ํ•จ์ˆ˜
function createAccessToken(id) {
  return jwt.sign({ id }, ACCESS_TOKEN_SECRET_KEY, {
    expiresIn: '10s',
  });
}

function createRefreshToken(id) {
  return jwt.sign({ id }, REFRESH_TOKEN_SECRET_KEY, {
    expiresIn: '7d',
  });
}

function validateToken(token, secretKey) {
  try {
    return jwt.verify(token, secretKey);
  } catch (error) {
    return null;
  }
}
//--------------------------------------------------

let tokenStorage = {};

//==================================================
/** access, refresh token ๋ฐœ๊ธ‰ **/
app.post('/tokens', async (req, res) => {
  const { id } = req.body;
  const accessToken = createAccessToken(id);
  const refreshToken = createRefreshToken(id);

  //refreshToken์„ ๊ฐ€์ง€๊ณ  ํ•ด๋‹น ์œ ์ €์˜ ์ •๋ณด๋ฅผ ์„œ๋ฒ„์— ์ €์žฅ
  tokenStorage[refreshToken] = {
    id: id,
    ip: req.ip,
    userAgent: req.headers['user-agent'],
  };

  // access, refresh Token -> ์ฟ ํ‚ค์— ์ „๋‹ฌ
  res.cookie('accessToken', accessToken);
  res.cookie('refreshToken', refreshToken);

  return res
    .status(200)
    .json({ message: 'token์ด ์ •์ƒ์ ์œผ๋กœ ๋ฐœ๊ธ‰๋˜์—ˆ์Šต๋‹ˆ๋‹ค.' });
});

//-----------------------------------------------------------
/** accessToken ๊ฒ€์ฆ : verify๋กœ ๊ฒ€์ฆ -> payload๋กœ ๋งŒ๋“ค์–ด -> id๋งŒ ์ทจํ•ด -> req.user์— ๋‹ด๊ธฐ**/
app.get('/tokens/validate', async (req, res) => {
  const { accessToken } = req.cookies;

  if (!accessToken) {
    return res
      .status(400)
      .json({ messge: 'Access Token์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.' });
  }

  const payload = validateToken(accessToken, ACCESS_TOKEN_SECRET_KEY);
  if (!payload) {
    return res
      .status(400)
      .json({ message: 'Access Token์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.' });
  }

  const { id } = payload;
  return res.status(200).json({
    message: `${id}์˜ payload๋ฅผ ๊ฐ€์ง„ token์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ธ์ฆ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.`,
  });
});

//-----------------------------------------------------------
/** refreshToken์œผ๋กœ accessToken ๊ฐฑ์‹ ํ•˜๊ธฐ : refreshToken ๊ฐ€์ ธ์™€์„œ -> payload ๋ฝ‘์•„๋‚ด์„œ refreshtoken์ด ์œ ํšจํ•œ์ง€ ํ™•์ธ -> ์ƒ์„ฑํ•  ๋•Œ ๋งŒ๋“ค์–ด๋’€๋˜ userInfor๋ฅผ ๊ฐ€์ ธ์™€์„œ ๊ทธ๊ฑธ **/
app.post('/tokens/refresh', async (req, res) => {
  const { refreshToken } = req.cookies;

  if (!refreshToken) {
    return res
      .status(400)
      .json({ message: 'Refresh Token์ด ์กด์žฌํ•˜์ง€์•Š์Šต๋‹ˆ๋‹ค.' });
  }

  const payload = validateToken(refreshToken, REFRESH_TOKEN_SECRET_KEY);
  if (!payload) {
    return res
      .status(400)
      .json({ message: 'Refresh Token์ด ์กด์žฌํ•˜์ง€์•Š์Šต๋‹ˆ๋‹ค.' });
  }

  const userInfo = tokenStorage[refreshToken];
  if (!userInfo) {
    return res
      .status(400)
      .json({ message: 'Refresh Token ์ •๋ณด๊ฐ€ ์„œ๋ฒ„์— ์กด์žฌํ•˜์ง€์•Š์Šต๋‹ˆ๋‹ค.' });
  }

  const newAccessToken = createAccessToken(userInfo.id);
  res.cookie('accessToken', newAccessToken);

  return res
    .status(200)
    .json({ message: 'Access Token์„ ์ƒˆ๋กญ๊ฒŒ ๋ฐœ๊ธ‰ํ•˜์˜€์Šต๋‹ˆ๋‹ค.' });
});

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

app.listen(PORT, () => {
  console.log(PORT, 'ํฌํŠธ๋กœ ์„œ๋ฒ„๊ฐ€ ์—ด๋ ธ์–ด์š”!');
});

  return res
    .status(200)
    .json({ message: 'Access Token์„ ์ƒˆ๋กญ๊ฒŒ ๋ฐœ๊ธ‰ํ•˜์˜€์Šต๋‹ˆ๋‹ค.' });
});

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

app.listen(PORT, () => {
  console.log(PORT, 'ํฌํŠธ๋กœ ์„œ๋ฒ„๊ฐ€ ์—ด๋ ธ์–ด์š”!');
});

๐Ÿ‘† post('/tokens') ์‹คํ–‰ํ•œ ๋ชจ์Šต : cookies์— accessToken๊ณผ refreshToken ์ด ์žˆ๋‹ค.

๐Ÿ‘† get('/tokens/validate') ์‹คํ–‰ํ•œ ๋ชจ์Šต : accessToken์˜ ๋งŒ๋ฃŒ์‹œ๊ฐ„(10์ดˆ)๊ฐ€ ์ง€๋‚˜์„œ ์œ ํšจํ•˜์ง€ ์•Š๋‹ค๊ณ  ๋‚˜์˜ค๋Š” ๊ฒƒ.

๐Ÿ‘† get('/tokens/validate') ์‹คํ–‰ํ•œ ๋ชจ์Šต :
10์ดˆ ์•ˆ์— ์‹คํ–‰ํ•œ ๋ชจ์Šต.


๐Ÿ‘† post('/tokens/refresh') ์‹คํ–‰ํ•œ ๋ชจ์Šต : accessToken๊ฐ€ ๋งŒ๋ฃŒ ํ›„๋‚˜ ์ „์ด๋‚˜ refreshToken์„ ๋ฐœ๊ธ‰ํ•˜์—ฌ accessToken๋ฅผ ๊ฐฑ์‹ ํ•œ ๋ชจ์Šต์ด๋‹ค. ์ฒ˜์Œ ๋ฐœ๊ธ‰๋ฐ›์•˜์„ ๋•Œ์™€ ์ƒˆ๋กœ ๊ฐฑ์‹ ํ•ด์„œ ๋ฐ›์€ accessToken์˜ ๊ฐ’์ด ๋‹ค๋ฅด๋‹ค! ์™œ๋ƒ๋ฉด jwt์˜ ๊ตฌ์กฐ ์ค‘ payload์˜ ๋‚ด์šฉ ์ค‘ iat (issued at) ๋•Œ๋ฌธ์ด๋‹ค. iat์€ ํ•ญ์ƒ ๋ฐ”๋€Œ๋Š”๋ฐ ์ด๊ฑฐ๋•Œ๋ฌธ์— jwt์˜ ํ˜•ํƒœ๊ฐ€ ๊ฐฑ์‹ ํ•  ๋•Œ๋งˆ๋‹ค ๋ฐ”๋€๋‹ค.

profile
์‹œ๊ฐ„์ด ๊ฑธ๋ฆด ๋ฟ ๋‚ด๊ฐ€ ๋ชปํ•  ๊ฑด ์—†๋‹ค.

0๊ฐœ์˜ ๋Œ“๊ธ€