Frontend/Next.js

Next.js - next.js์—์„œ session ํ™œ์šฉํ•˜๊ธฐ

2-doooo-2 2024. 9. 23. 00:03
728x90
๋ฐ˜์‘ํ˜•

๐Ÿ“Œsession ์ด๋ž€ ๋ฌด์—‡์ธ๊ฐ€? 

ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„ ๊ฐ„์˜ ์—ฐ๊ฒฐ ์ƒํƒœ๋ฅผ ์˜๋ฏธ, ์ฆ‰ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ธŒ๋ผ์šฐ์ €์— ์ ‘์†ํ•˜์—ฌ ์„œ๋ฒ„์™€ ์ ‘์†์ด ์ข…๋ฃŒํ•˜๊ธฐ ์ „์˜ ์ƒํƒœ๋ฅผ ์˜๋ฏธํ•œ๋‹ค.

 

๐Ÿ“Œsession ๋™์ž‘ ์ˆœ์„œ 

์ถœ์ฒ˜: https://dongsik93.github.io/til/2020/01/08/til-authorization(1)/

  1. ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ์„ ํ•œ๋‹ค
  2. ์„œ๋ฒ„์ธก์—์„œ๋Š” ๋กœ๊ทธ์ธ ์ •๋ณด๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž๋ฅผ ํ™•์ธํ•œ๋‹ค.
  3. ์„œ๋ฒ„์ธก์—์„œ ์„ธ์…˜ ์ €์žฅ์†Œ์— ์‚ฌ์šฉ์ž ์„ธ์…˜ ์ƒ์„ฑ์„ ์š”์ฒญํ•œ๋‹ค.
  4. ์‚ฌ์šฉ์ž์˜ ๊ณ ์œ ํ•œ ID๊ฐ’์„ ๋ถ€์—ฌํ•˜์—ฌ ์„ธ์…˜ ์ €์žฅ์†Œ์— ์ €์žฅํ•˜๊ณ , ์ด์™€ ์—ฐ๊ฒฐ๋˜๋Š” ์„ธ์…˜ ID๋ฅผ ๋ฐœํ–‰ํ•œ๋‹ค.
  5. ์‚ฌ์šฉ์ž๋Š” ์„œ๋ฒ„์—์„œ ํ•ด๋‹น ์„ธ์…˜ID๋ฅผ ๋ฐ›์•„ ์ฟ ํ‚ค์— ์ €์žฅํ•œ๋‹ค
  6. ์ธ์ฆ์ด ํ•„์š”ํ•œ ์š”์ฒญ์„ ํ•  ๋•Œ ํ—ค๋”์— ์ฟ ํ‚ค๋ฅผ ์‹ค์–ด ์š”์ฒญํ•œ๋‹ค
  7. ์„œ๋ฒ„์—์„œ๋Š” ์ฟ ํ‚ค๋ฅผ ๋ฐ›์•„ ์„ธ์…˜ ์ €์žฅ์†Œ์— ๋Œ€์กฐ๋ฅผ ํ•œ๋‹ค
  8. ์ธ์ฆ ํ›„ ์š”์ฒญ๋œ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค
  9. ์ธ์ฆ์ด ์™„๋ฃŒ๋˜๊ณ  ์„œ๋ฒ„๋Š” ์‚ฌ์šฉ์ž์—๊ฒŒ ๋งž๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ด์ค€๋‹ค

 

๐Ÿ“Œsession ๊ณผ cookie

์ €์žฅ์œ„์น˜ ํด๋ผ์ด์–ธํŠธ ์„œ๋ฒ„
๋ผ์ดํ”„์‚ฌ์ดํด(๋งŒ๋ฃŒ์‹œ์ ) ์ฟ ํ‚ค ์ €์žฅ์‹œ ์„ค์ • ๋ธŒ๋ผ์šฐ์ € ์ข…๋ฃŒ ์‹œ ์‚ญ์ œ
๋ณด์•ˆ ๋น„๊ต์  ์ทจ์•ฝ ์•ˆ์ „
์†๋„ ๋น ๋ฆ„ ๋น„๊ต์  ๋А๋ฆผ

 

 

 

๐Ÿ“ŒNext.js์—์„œ session๊ณผ cookie์ด์šฉํ•˜๊ธฐ 

server ์ปดํฌ๋„ŒํŠธ์™€ client ์ปดํฌ๋„ŒํŠธ ๊ด€์ ์—์„œ ํ”„๋กœ์ ํŠธ์—์„œ ์‚ฌ์šฉํ•œ ๋กœ์ง์„ ์ •๋ฆฌํ•ด๋ณด์•˜๋‹ค. 

 

์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ - instance.ts, authApi.ts

'use server';

import { cookies } from 'next/headers'; // ์„œ๋ฒ„ ์‚ฌ์ด๋“œ์—์„œ ์ฟ ํ‚ค๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ๋‚ด์žฅ ๋ชจ๋“ˆ
import { BASE_URL } from '@/constants/url';
import { auth } from '@/auth';
import { redirect } from 'next/dist/server/api-utils';

interface RequestOptions {
  headers?: Record<string, string>;
  [key: string]: string | Record<string, string> | undefined;
}

const fetchInstance = async (url: string, options: RequestOptions = {}) => {
  const session = await auth();
  const accessToken = session?.user?.accessToken;

  const headers: RequestOptions['headers'] = {
    'Content-Type': 'application/json',
    ...options.headers,
  };

  if (accessToken) {
    headers.Authorization = `Bearer ${accessToken}`;
  }

  try {
    const response = await fetch(`${BASE_URL}${url}`, {
      ...options,
      headers,
    });

   //response ๋กœ์ง ์ƒ๋žต 
   
  } catch (error) {
   //์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋กœ์ง ์ƒ๋žต
  }
};

export const instance = fetchInstance;
'use server';

import { BASE_URL } from '@/constants/url';
import { instance } from './instance';

export const signIn = async ({ email, password }: { email: string; password: string }) => {
  const response = await instance('sign-in', {
    body: JSON.stringify({ email, password }),
    credentials: 'include',
    method: 'POST',
  });

  return response;
};

export const reissueToken = async (refresh_token: string) => {
  const response = await fetch(`${BASE_URL}reissue`, {
    headers: {
      Cookie: `refresh_token=${refresh_token}`,
    },
    method: 'POST',
  });

  // reponse์—๋Ÿฌ ๋กœ์ง ์ƒ๋žต

  const cookie = response.headers.get('Set-Cookie');
  const { accessToken } = await response.json();
  const refreshToken = cookie?.split(';')[0].split('=')[1];

  return { accessToken, refreshToken };
};

 

์„ธ์…˜(Session)

  • ์„œ๋ฒ„ ์‚ฌ์ด๋“œ์—์„œ๋Š” auth() ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ์„ธ์…˜ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค. 
    • ์—ฌ๊ธฐ์—๋Š” ๋ณดํ†ต ์‚ฌ์šฉ์ž ์•ก์„ธ์Šค ํ† ํฐ๊ณผ ๊ฐ™์€ ์ธ์ฆ ๊ด€๋ จ ์ •๋ณด๊ฐ€ ํฌํ•จ๋˜๋ฉฐ, ์ด๋ฅผ ์ด์šฉํ•ด API ์š”์ฒญ ์‹œ ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•œ๋‹ค. 
    • fetchInstance ํ•จ์ˆ˜์—์„œ๋Š” ์„ธ์…˜ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์™€์„œ ์•ก์„ธ์Šค ํ† ํฐ(session?.user?.accessToken)์„ ์ถ”์ถœํ•˜๊ณ , ์ด ํ† ํฐ์„ Authorization ํ—ค๋”์— ๋„ฃ์–ด API ์š”์ฒญ ์‹œ ์‚ฌ์šฉํ•œ๋‹ค.  (Bearer ${accessToken})

์•ก์„ธ์Šค ํ† ํฐ(Access Token)

  • ์•ก์„ธ์Šค ํ† ํฐ์€ ์„œ๋ฒ„์—์„œ ์™ธ๋ถ€ ์„œ๋น„์Šค๋กœ์˜ API ์š”์ฒญ์„ ์ธ์ฆํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋œ๋‹ค. ์ด ํ† ํฐ์ด ์žˆ์–ด์•ผ๋งŒ ๊ถŒํ•œ์ด ํ•„์š”ํ•œ ์ž‘์—…๋“ค์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค. 
  • ์ฝ”๋“œ์—์„œ๋Š” /sign-in์ด๋‚˜ ํ† ํฐ ์žฌ๋ฐœ๊ธ‰(reissue) ๊ฐ™์€ API ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ, ํ—ค๋” ๋˜๋Š” ๋ณธ๋ฌธ์— ์•ก์„ธ์Šค ํ† ํฐ์„ ํฌํ•จ์‹œ์ผœ ์š”์ฒญ์„ ์ธ์ฆํ•œ๋‹ค. ๋งŒ์•ฝ ์•ก์„ธ์Šค ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜๊ฑฐ๋‚˜ ์—†์„ ๊ฒฝ์šฐ, ์ƒˆ๋กœ์šด ํ† ํฐ์„ ๋ฐœ๊ธ‰๋ฐ›๊ฑฐ๋‚˜ ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋กœ์ง์ด ์‹คํ–‰๋œ๋‹ค.

์ฟ ํ‚ค(Cookies)

  • ์ฟ ํ‚ค๋Š” ์ฃผ๋กœ ์„œ๋ฒ„ ์‚ฌ์ด๋“œ์—์„œ ์„ธ์…˜์„ ์œ ์ง€ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋œ๋‹ค. reissueToken ํ•จ์ˆ˜์—์„œ๋Š” refresh_token์ด ์ฟ ํ‚ค์— ์ €์žฅ๋˜์–ด, ์ด๋ฅผ ์ด์šฉํ•ด ์ƒˆ๋กœ์šด ์•ก์„ธ์Šค ํ† ํฐ์„ ๋ฐœ๊ธ‰๋ฐ›๋Š”๋‹ค.
  • ๋˜ํ•œ ์„œ๋ฒ„๋Š” Set-Cookie ํ—ค๋”๋ฅผ ํ†ตํ•ด ์ƒˆ๋กœ์šด ํ† ํฐ์„ ํด๋ผ์ด์–ธํŠธ์— ์ €์žฅํ•  ์ˆ˜ ์žˆ๋‹ค. (response.headers.get('Set-Cookie'))

 

ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ - Header.tsx

'use client';

//style๊ด€๋ จ import ์ƒ๋žต

import useUserInfo from '@/hook/useUserInfo';
import { useUserInfoStore } from '@/store/memberStore';
import { useSession } from 'next-auth/react';
import { useEffect } from 'react';
import { getUserInfo } from '@/api/memberApi';

export default function Header() {
  const userInfo = useUserInfo();
  const { setUserInfo } = useUserInfoStore();

  const { data: session, update } = useSession();

  const accessToken = session?.user?.accessToken;
  const refreshToken = session?.user?.refreshToken;

  // update();

  useEffect(() => {
    const storedUserInfo = localStorage.getItem('userInfo');
    if (storedUserInfo && !accessToken && !refreshToken) {
      localStorage.removeItem('userInfo');
      setUserInfo(null);
    }
  }, [accessToken, refreshToken, setUserInfo]);

  // ์œ ์ €์ •๋ณด์กฐํšŒ
  useEffect(() => {
    (async () => {
      if (!localStorage.getItem('userInfo') && accessToken && refreshToken) {
        const result = await getUserInfo();
        const storedUserInfo = { name: result.name, course: result.course, email: result.email };
        if (result) {
          localStorage.setItem('userInfo', JSON.stringify(storedUserInfo));
          setUserInfo(result);
        }
      }
    })();
  }, [accessToken, refreshToken, setUserInfo]);

  return (
     // html ๋กœ์ง ์ƒ๋žต
  );
}

์„ธ์…˜(Session)

  • ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ์—์„œ๋Š” next-auth์˜ useSession์„ ์‚ฌ์šฉํ•ด ์‚ฌ์šฉ์ž ์„ธ์…˜ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•œ๋‹ค. ๋กœ๊ทธ์ธ ์‹œ ์•ก์„ธ์Šค ํ† ํฐ๊ณผ ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ์„ ์„ธ์…˜์— ์ €์žฅํ•œ ํ›„, ์ด๋ฅผ ์ด์šฉํ•ด ์ธ์ฆ๋œ API ์š”์ฒญ์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋‹ค. 
  • ํ† ํฐ์„ ๊ฐ€์ ธ์˜ค๊ณ (session?.user?.accessToken, session?.user?.refreshToken), ์ด ํ† ํฐ๋“ค์„ ์ด์šฉํ•ด ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์„œ๋ฒ„์—์„œ ๋ฐ›์•„์™€ ์ „์—ญ ์ƒํƒœ์— ์ €์žฅํ•˜๊ฑฐ๋‚˜ localStorage์— ์ €์žฅ

์•ก์„ธ์Šค ํ† ํฐ(Access Token)

  • ์•ก์„ธ์Šค ํ† ํฐ์ด ์—†์œผ๋ฉด, ๊ธฐ์กด์— ์ €์žฅ๋œ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์ง€์šฐ๋„๋ก ๋กœ์ง์ด ์ฒ˜๋ฆฌ

์ฟ ํ‚ค(Cookies)

  • ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ์—์„œ๋Š” ์ฟ ํ‚ค๋ฅผ ์ง์ ‘์ ์œผ๋กœ ๋‹ค๋ฃจ์ง„ ์•Š์ง€๋งŒ, credentials: 'include' ์˜ต์…˜์„ ํ†ตํ•ด ์ฟ ํ‚ค๋ฅผ ์ž๋™์œผ๋กœ ์š”์ฒญ์— ํฌํ•จ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ž๋™์œผ๋กœ ์ฟ ํ‚ค(์˜ˆ: ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ)๋ฅผ ์„œ๋ฒ„์— ์ „์†กํ•˜์—ฌ ์„ธ์…˜์ด ์œ ์ง„๋œ๋‹ค.

 

์ „๋ฐ˜์ ์œผ๋กœ ํ•ต์‹ฌ์€ next.js๋Š” ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์—์„œ ์ฟ ํ‚ค๋ฅผ ์ฝ๊ณ  ์ฒ˜๋ฆฌํ•œ ๋’ค ํด๋ผ์ด์–ธํŠธ๋กœ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋งŒ ๋ณด๋‚ด์ฃผ๋Š” ๋ฐฉ์‹์ด๊ธฐ ๋•Œ๋ฌธ์— ์ฟ ํ‚ค ๊ฐ’์„ ํด๋ผ์ด์–ธํŠธ์—์„œ ์ง์ ‘ ๋…ธ์ถœ์‹œํ‚ค์ง€ ์•Š๊ณ ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค!! 

๊ทธ๋ฆฌ๊ณ  ์šฐ๋ฆฌ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ์„ธ์…˜๊ณผ ์ฟ ํ‚ค, ํ† ํฐ์„ ๋ชจ๋‘ ์‚ฌ์šฉํ–ˆ๋‹ค. ๋ณดํ†ต ์„ธ์…˜, ์ฟ ํ‚ค vs jwt(ํ† ํฐ) ์ด๋ ‡๊ฒŒ ๋‚˜๋ˆ ์„œ ๋กœ๊ทธ์ธ์„ ๊ตฌํ˜„ํ•œ๋‹ค. ํ•˜์ง€๋งŒ Next.js์—์„œ ์ œ๊ณตํ•˜๋Š” useSessionํ›…์„ ์‚ฌ์šฉํ•ด ์„ธ์…˜ ๊ด€๋ฆฌ ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•˜๋ฉด์„œ๋„, ํด๋ผ์ด์–ธํŠธ์—์„œ ํ† ํฐ์„ ํ†ตํ•ด API ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ํ˜ผํ•ฉ๋œ ๊ตฌ์กฐ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. 

 

 

์ฐธ๊ณ ์ž๋ฃŒ

728x90
๋ฐ˜์‘ํ˜•