λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
Back-end/Server

Token & Token-based Authentication 의 λ™μž‘ κ³Όμ • (feat. JWT)

by ciocio 2021. 11. 26.

πŸ“Œ  Token ?

 

ν”„λ‘œκ·Έλž˜λ° μ–Έμ–΄μ—μ„œμ˜ token

λ¬Έλ²•μ μœΌλ‘œ 더 이상 λ‚˜λˆŒ 수 μ—†λŠ” 기본적인 μ–Έμ–΄ μš”μ†Œλ₯Ό λ§ν•œλ‹€.

ν‚€μ›Œλ“œλ‚˜ μ—°μ‚°μž, ꡬ두점 등이 token 이 될 수 μžˆλ‹€.

 

λ„€νŠΈμ›Œν¬μ—μ„œμ˜ token

λ„€νŠΈμ›Œν¬λ₯Ό 따라 λŒμ•„λ‹€λ‹ˆλŠ” μ•”ν˜Έν™”λœ λΉ„νŠΈμ—΄μ΄λ‹€.

각 λ„€νŠΈμ›Œν¬μ—λŠ” 였직 ν•˜λ‚˜μ˜ token 만 μ‘΄μž¬ν•˜λ―€λ‘œ 2개 μ΄μƒμ˜ 컴퓨터가 λ™μ‹œμ— 메세지λ₯Ό 전솑할 κ°€λŠ₯성을 사전에 μ°¨λ‹¨ν•œλ‹€.

 

λ³΄μ•ˆμ‹œμŠ€ν…œμ—μ„œμ˜ token

ν¬λ ˆλ”§ μΉ΄λ“œ 크기의 μž‘μ€ μž₯치λ₯Ό λ§ν•œλ‹€. 

μ‚¬μš©μžκ°€ μ²˜μŒμ— μ•”ν˜Έλ₯Ό μž…λ ₯ν•˜λ©΄, μΉ΄λ“œλŠ” λ„€νŠΈμ›Œν¬μ— 접속할 수 μžˆλŠ” IDλ₯Ό μˆ˜μ‹œλ‘œ λ³€κ²½ν•΄κ°€λ©° ν‘œμ‹œν•΄μ€€λ‹€.

 

-->  곡유 μžμ› 접근에 λŒ€ν•œ 동기화λ₯Ό 보μž₯ν•˜κΈ° μœ„ν•΄ μ‚¬μš©λ˜λŠ” 좔상적인 κ°œλ…μ΄λ‹€.

-->  token 을 가진 μ‚¬λžŒμ΄λΌλ©΄ λˆ„κ΅¬λ‚˜ νŠΉμ • μžμ›μ— 배타적 접근이 ν—ˆμš©λ˜κ³  (μ„ νƒμ μœΌλ‘œ) 그것을 ν†΅μ œν•  수 μžˆλŠ” κΆŒν•œμ„ κ°–κ²Œλœλ‹€.

 

 

πŸ“Œ  Token-based Authentication λ™μž‘ κ³Όμ • ?

 

 

 

 

πŸ“  session 기반 인증과 token 기반 인증 κ°„ ( κ΅¬ν˜„μ— μžˆμ–΄ ) 차이점이 μžˆλ‹€λ©΄ ?

 

session 기반 인증은

πŸ“Ž express-session λͺ¨λ“ˆμ„ μ‚¬μš©ν–ˆμ„ λ•Œ cookie λ₯Ό μ½μ–΄μ˜€κ±°λ‚˜ μΆ”κ°€ν•  λ•Œ 별 λ‹€λ₯Έ 섀정이 ν•„μš”ν•˜μ§€ μ•Šμ•˜λ‹€.

" μ„Έμ…˜ μΏ ν‚€ " κ°œλ…μ΄λΌ, cookie 의 기본값을 미리 섀정해쀄 수 μžˆμ—ˆλ‹€.

 

token 기반 인증은

μ„Έμ…˜ 기반과 λ§ˆμ°¬κ°€μ§€λ‘œ cookie λ₯Ό ν™œμš©ν•˜κΈ΄ ν•˜μ§€λ§Œ, μ„œλ²„ 내에 인증을 μœ„ν•œ 곡간을 λ”°λ‘œ ν• λ‹Ήν•˜μ§„ μ•ŠλŠ”λ‹€.

token 을 μ•”ν˜Έν™”ν•˜κ±°λ‚˜ λ³΅ν˜Έν™”ν•˜λŠ” μ‹œμŠ€ν…œλ§Œ 있으면 되기 λ•Œλ¬Έμ΄λ‹€. 

-->  ν΄λΌμ΄μ–ΈνŠΈλ‹¨μ˜ cookie λ₯Ό 관리해쀄 ν•„μš”μ„±μ΄ 없어짐 !-!

κ·Έλž˜μ„œ μ΄ˆλ°˜μ—λŠ” μΏ ν‚€λ₯Ό 예쁘게 μ½μ–΄μ˜€λŠ” μž‘μ—…, ν›„λ°˜μ—λŠ” μΏ ν‚€λ₯Ό 예쁘게 λ³΄λ‚΄λŠ” 방법 등이 μΆ”κ°€λœλ‹€ γ…‹γ…‹

 

const express = require('express');
const cookieParser = require('cookie-parser');

const app = express();

app.use(cookieParser());

// 예제 μ½”λ“œ
app.get('/', function(req, res){
  // Cookies that have not been signed
  console.log('Cookies: ', req.cookies);
  // Cookies that have been signed
  console.log('Signed Cookies: ', req.signedCookies);
})

 

μš”μ²­μ΄ λ“€μ–΄μ˜€λ©΄ DBμ—μ„œ μΌμΉ˜ν•˜λŠ” 정보λ₯Ό μ°Ύκ³ , token 을 μƒμ„±ν•œ ν›„ 쿠킀에 λ‹΄μ•„ 응닡을 보낸닀.

 

// controller/users/login.js

// DBμ—μ„œ 뢈러온 Users TABLE
const { Users } = require('../models');  // (model/index.js)
// 토큰을 μƒμ„±ν•˜κ³  νŒŒμ‹±ν•˜λŠ” λͺ¨λ“ˆ
const jwt = require('jsonwebtoken');
// 토큰 생성,νŒŒμ‹±ν•  λ•Œ ν•„μš”ν•œ ν‚€ κ°’λ“€ 뢈러올 λ•Œ μ‚¬μš©ν•˜λŠ” λͺ¨λ“ˆ
require('dotenv').config();

module.exports = (req, res) => {
  // Users ν…Œμ΄λΈ”μ—μ„œ μš”μ²­κ³Ό μΌμΉ˜ν•˜λŠ” 정보λ₯Ό query ν•΄μ˜¨λ‹€.
  Users.findOne({
    where: { userId: req.body.userId, password: req.body.password }
  })
  .then((data) => {
    // λ“±λ‘λœ νšŒμ›μ΄ μ•„λ‹ˆλΌλ©΄ μ ‘κ·Ό λΆˆκ°€ν•˜λ‹€κ³  μ•Œλ €μ€€λ‹€.
    if(!data){
      res
        .status(400)
        .json({
          data: null,
          message: 'not authorized'
        });
    }
    else{
      // λ“±λ‘λœ νšŒμ›μ΄λΌλ©΄ access-token κ³Ό refresh-token 을 μƒμ„±ν•œλ‹€.
      const payload = data.dataValues;
      delete payload.password;
      // jwtλͺ¨λ“ˆμ„ 톡해 토큰 생성
      const access_token = jwt.sign(payload, process.env.ACCESS_SECRET, { expiresIn: 60 * 60 });
      const refresh_token = jwt.sign(payload, process.env.REFRESH_SECRET);
      // access-token 은 response body 에 λ„£κ³ , 
      // refresh-token 은 header의 cookie 에 λ„£μ–΄ λ°˜ν™˜ν•œλ‹€.
      res
        .append('Set-Cookie', 
                `refreshToken=${refresh_token}; 
                 SameSite=none; 
                 Secure; 
                 HttpOnly`)
        .status(200)
        .json({
          data: {
            accessToken: access_token
          },
          message: 'ok'
        })
    }
  })
};

 

DB μ—μ„œ μ›ν•˜λŠ” 정보λ₯Ό 가져와 JWT 을 μƒμ„±ν•΄μ„œ ν΄λΌμ΄μ–ΈνŠΈ 단에 token 을 λ„˜κ²¨μ€€λ‹€.

 

 

 

 

// controller/users/accessTokenRequest.js

// DBμ—μ„œ 뢈러온 Users TABLE
const { Users } = require('../models');  // (model/index.js)
// 토큰을 μƒμ„±ν•˜κ³  νŒŒμ‹±ν•˜λŠ” λͺ¨λ“ˆ
const jwt = require('jsonwebtoken');
// 토큰 생성,νŒŒμ‹±ν•  λ•Œ ν•„μš”ν•œ ν‚€ κ°’λ“€ 뢈러올 λ•Œ μ‚¬μš©ν•˜λŠ” λͺ¨λ“ˆ
require('dotenv').config();

module.exports = async(req, res) => {
  // request header에 authorization 값이 λ‹΄κΈ΄λ‹€λŠ” κ±Έ 확인할 수 μžˆλ‹€.
  const authorization = req.headers['authorization'];
  // μΈκ°€λœ 토큰이 μ—†λ‹€λ©΄,
  if(!authorization){
    // λ“±λ‘λœ νšŒμ› 정보λ₯Ό μ½μ–΄μ˜¬ 수 μ—†μœΌλ―€λ‘œ, μŠΉμΈμ„ κ±°λΆ€ν•œλ‹€.
    res.status(400).json({ data: null, message: 'invalid access token' });
  }
  else{
    // μΈκ°€λœ 토큰이 μžˆλ‹€λ©΄
    const token = authorization.split(' ')[1];
    // 토큰을 λ³΅ν˜Έν™”ν•˜μ—¬ μ›μƒνƒœμ˜ data둜 λ³΅κ΅¬μ‹œν‚¨λ‹€.
    const data = jwt.verify(token, process.env.ACCESS_SECRET);
    // 볡ꡬ된 data 와 μΌμΉ˜ν•˜λŠ” μ‚¬μš©μž 정보λ₯Ό μ°ΎλŠ”λ‹€.
    const userInfo = await Users.findOne({
      where: { userId: data.userId },
    });
    // λ§Œμ•½ μ‚¬μš©μž 정보가 μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ”λ‹€λ©΄
    if(!userInfo){
      // access-token 의 κΈ°ν•œμ΄ λ§Œλ£Œλ˜μ—ˆλ‹€λŠ” μ–˜κΈ°μ΄λ―€λ‘œ, λ°˜ν™˜λ˜λŠ” 값은 μ—†λ‹€.
      res.status(403).json({ data: null, message: 'access token has been tempered' });
    }
    else{
      // access-token 이 λ§Œλ£Œλ˜μ§€ μ•Šμ•˜κ³  && μ‚¬μš©μž 정보가 μ‘΄μž¬ν•œλ‹€λ©΄
      const payload = userInfo.dataValues;
      // λ―Όκ°ν•œ μ •λ³΄λŠ” μ§€μš°κ³ 
      delete payload.password;
      // request body 에 μ‚¬μš©μž 정보λ₯Ό λ‹΄μ•„ μ‘λ‹΅ν•œλ‹€.
      res.status(200).json({ data: { userInfo: payload }, message: 'ok' });
    }
  }
};

 

둜그인 ν›„ νšŒμ›μ •λ³΄λ₯Ό 얻을 λ•Œ authorization header 에 λ‹΄κΈ΄ token 을 λ³΅ν˜Έν™”ν•˜μ—¬ DB 에 μΌμΉ˜ν•˜λŠ” 정보가 μžˆλŠ”μ§€, 또 token이 (μ—¬κΈ°μ„œλŠ” access-token) λ§Œλ£Œλ˜μ§€λŠ” μ•Šμ•˜λŠ”μ§€ ν™•μΈν•˜μ—¬ μΌμΉ˜ν•˜λŠ” 정보λ₯Ό λ°˜ν™˜ν•œλ‹€ !-!

 

// controller/users/refreshTokenRequest.js

// DBμ—μ„œ 뢈러온 Users TABLE
const { Users } = require('../models');  // (model/index.js)
// 토큰을 μƒμ„±ν•˜κ³  νŒŒμ‹±ν•˜λŠ” λͺ¨λ“ˆ
const jwt = require('jsonwebtoken');
// 토큰 생성,νŒŒμ‹±ν•  λ•Œ ν•„μš”ν•œ ν‚€ κ°’λ“€ 뢈러올 λ•Œ μ‚¬μš©ν•˜λŠ” λͺ¨λ“ˆ
require('dotenv').config();

module.exports = (req, res) => {
  const refreshToken = req.cookies.refreshToken;
  
  if(!refreshToken){
    res
      .status(403)
      .json({ 
        data: null, 
        message: 'refresh token not provided' 
      });
  }
  else{
    jwt.verify(refreshToken, process.env.REFRESH_SECRET, 
      async function(err, data){
        if(err){
          res
            .status(400)
            .json({ 
              data: null, 
              message: 'invalid refresh token, please log in again' 
            });
        }
        else{
          const userInfo = await Users.findOne({
            where: { userId: data.userId }, 
          });
          
          if(!userInfo){
            res
              .status(401)
              .json({ 
                data: null, 
                messgae: 'refresh token has been tempered' 
               });
          }
          else{
            const payload = userInfo.dataValues;
          	delete payload.password;
    
            const access_token = jwt.sign(payload, process.env.ACCESS_SECRET, { expiresIn: 60 * 60 });
            
            res
            .status(200)
            .json({ data: {
              accessToken: access_token,
              userInfo: payload
              }, 
              message: 'ok' 
            });
          }
        }
      }
    )
  }
};

 

refresh-token 을 μ²˜λ¦¬ν•˜λŠ” 둜직인데, κ°œμ„ μ˜ 여지가 ν•„μš”ν•΄λ³΄μΈλ‹€ ..!

refresh-token 은 access-token 이 λ§Œλ£Œλ˜μ—ˆμ„ λ•Œ μ„œλ²„μ—μ„œ 확인 ν›„ μƒˆλ‘œμš΄ access-token 을 λ°œκΈ‰ν•΄μ£ΌκΈ° μœ„ν•΄ μ‘΄μž¬ν•œλ‹€.

μ„Έμ»¨λ“œ 확인 토큰이라고 μƒκ°ν•˜λ©΄ 쒋을듯

λ°˜μ‘ν˜•

'Back-end > Server' μΉ΄ν…Œκ³ λ¦¬μ˜ λ‹€λ₯Έ κΈ€

Session & Session-based Authentication 의 λ™μž‘ κ³Όμ •  (0) 2021.11.25

λŒ“κΈ€