Nextjs와 기본 인증 (accessToken, refreshToken)

2024. 11. 21. 17:54컴퓨터언어/React JS

728x90
반응형
1. 인증 API 관리 클래스 설계
class ApiClient {
  constructor(baseURL) {
    this.baseURL = baseURL;
  }

  // 기본 API 요청 메서드
  async request(endpoint, options = {}) {
    const url = `${this.baseURL}${endpoint}`;
    const response = await fetch(url, {
      ...options,
      credentials: 'include', // 쿠키 포함
    });

    if (response.status === 401) {
      // AccessToken 만료: RefreshToken으로 갱신 시도
      const refreshed = await this.refreshAccessToken();
      if (refreshed) {
        return this.request(endpoint, options); // 원래 요청 재시도
      } else {
        throw new Error('Authentication failed. Please log in again.');
      }
    }

    return response;
  }

  // AccessToken 갱신 메서드
  async refreshAccessToken() {
    const response = await fetch(`${this.baseURL}/auth/refresh`, {
      method: 'POST',
      credentials: 'include', // RefreshToken 쿠키 포함
    });

    if (response.ok) {
      return true; // 새 AccessToken 발급 성공
    }

    return false; // RefreshToken 만료 또는 실패
  }
}

export const apiClient = new ApiClient(process.env.NEXT_PUBLIC_API_BASE_URL);

 

 

2-1. 서버 컴포넌트에서 cookies 객체로 API 호출

 

import { apiClient } from '../../utils/apiClient';

export default async function DashboardPage() {
  let data;

  try {
    const response = await apiClient.request('/dashboard', { method: 'GET' });
    data = await response.json();
  } catch (error) {
    console.error('Failed to fetch dashboard data:', error.message);
    data = null;
  }

  return (
    <div>
      <h1>Dashboard</h1>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
}

 

2-2. 클라이언트 컴포넌트에서 API 호출

 

쿠키는 브라우저가 자동으로 처리하므로, credentials: 'include'만 추가하면 된다.

'use client';

import { useEffect, useState } from 'react';
import { apiClient } from '../../utils/apiClient';

export default function ClientDashboard() {
  const [data, setData] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await apiClient.request('/dashboard', { method: 'GET' });
        const json = await response.json();
        setData(json);
      } catch (error) {
        console.error('Error fetching dashboard data:', error.message);
      }
    };

    fetchData();
  }, []);

  return (
    <div>
      <h1>Client Dashboard</h1>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
}

 

 

 

3. 인증 관련 서버 라우트

 

app/api/auth/refresh/route.ts

import { NextResponse } from 'next/server';

export async function POST(request) {
  // RefreshToken 확인 및 AccessToken 갱신
  const newAccessToken = 'new-access-token'; // 실제 로직 필요
  const response = NextResponse.json({ success: true });
  response.cookies.set('accessToken', newAccessToken, {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'strict',
    path: '/',
    maxAge: 60 * 15, // 15분
  });
  return response;
}

 

app/api/auth/login/route.ts 

import { NextResponse } from 'next/server';

export async function POST(request) {
  const { email, password } = await request.json();
  
  // 로그인 검증 후 토큰 발급
  const accessToken = 'access-token';
  const refreshToken = 'refresh-token';

  const response = NextResponse.json({ success: true });

  // AccessToken 설정
  response.cookies.set('accessToken', accessToken, {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'strict',
    path: '/',
    maxAge: 60 * 15, // 15분
  });

  // RefreshToken 설정
  response.cookies.set('refreshToken', refreshToken, {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'strict',
    path: '/',
    maxAge: 60 * 60 * 24 * 7, // 7일
  });

  return response;
}

 

 

환경변수 설정

 

NEXT_PUBLIC_API_BASE_URL=https://your-api-url.com

 

728x90
반응형