티스토리 뷰

728x90
반응형

아래는 React와 TypeScript를 사용하여 로그인 후 인증 정보를 관리하는 예시 코드입니다. 이 예제에서는 Context API와 커스텀 훅을 사용해 전역 인증 상태를 관리하며, localStorage에 토큰을 저장해 새로고침 시에도 인증 정보를 유지할 수 있도록 합니다.


1. 인증 Context 및 Provider 생성

먼저, 인증 정보를 담을 Context와 Provider를 만듭니다.
파일: src/contexts/AuthContext.tsx

import React, { createContext, useState, useEffect, ReactNode } from 'react';

// 사용자 정보 타입 정의 (필요에 따라 확장 가능)
interface User {
  id: number;
  name: string;
  email: string;
}

// AuthContext에서 사용할 인터페이스
interface AuthContextType {
  user: User | null;
  token: string | null;
  login: (email: string, password: string) => Promise<void>;
  logout: () => void;
}

// Context 생성 (초기값은 undefined로 설정)
export const AuthContext = createContext<AuthContextType | undefined>(undefined);

interface AuthProviderProps {
  children: ReactNode;
}

export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
  const [user, setUser] = useState<User | null>(null);
  const [token, setToken] = useState<string | null>(null);

  // 컴포넌트가 마운트될 때 localStorage에서 토큰을 불러옵니다.
  useEffect(() => {
    const storedToken = localStorage.getItem('authToken');
    if (storedToken) {
      setToken(storedToken);
      // 토큰으로 사용자 정보를 불러오는 API 호출을 할 수도 있습니다.
      // 예: fetchUserInfo(storedToken).then(setUser).catch(() => logout());
    }
  }, []);

  // 로그인 함수 (실제 프로젝트에서는 API 호출로 대체)
  const login = async (email: string, password: string) => {
    try {
      // 실제 API 호출 대신 아래와 같이 가짜 API 함수를 사용합니다.
      const response = await fakeLoginApi(email, password);
      setToken(response.token);
      setUser(response.user);
      localStorage.setItem('authToken', response.token);
    } catch (error) {
      console.error('Login failed:', error);
      throw error;
    }
  };

  // 로그아웃 시 상태 초기화 및 localStorage 정리
  const logout = () => {
    setUser(null);
    setToken(null);
    localStorage.removeItem('authToken');
  };

  return (
    <AuthContext.Provider value={{ user, token, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
};

// 가짜 API 함수 (실제 프로젝트에서는 백엔드 API와 연동)
interface FakeLoginResponse {
  token: string;
  user: User;
}

const fakeLoginApi = async (email: string, password: string): Promise<FakeLoginResponse> => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // 예시: 올바른 이메일과 비밀번호인지 확인
      if (email === 'test@example.com' && password === 'password') {
        resolve({
          token: 'fake-token-123456',
          user: { id: 1, name: 'Test User', email }
        });
      } else {
        reject(new Error('Invalid credentials'));
      }
    }, 1000);
  });
};

2. 인증 상태를 쉽게 사용하기 위한 커스텀 훅 생성

Context를 사용하기 쉽게 하기 위해 커스텀 훅을 만들어 줍니다.
파일: src/hooks/useAuth.ts

import { useContext } from 'react';
import { AuthContext } from '../contexts/AuthContext';

export const useAuth = () => {
  const context = useContext(AuthContext);
  if (context === undefined) {
    throw new Error('useAuth must be used within an AuthProvider');
  }
  return context;
};

3. 로그인 컴포넌트 생성

로그인 폼을 구현하여 사용자가 이메일과 비밀번호를 입력하면 login 함수를 호출합니다.
파일: src/components/Login.tsx

import React, { useState } from 'react';
import { useAuth } from '../hooks/useAuth';

const Login: React.FC = () => {
  const { login } = useAuth();
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setLoading(true);
    setError(null);

    try {
      await login(email, password);
    } catch (err: any) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Email:</label>
        <input 
          type="email" 
          value={email} 
          onChange={(e) => setEmail(e.target.value)} 
          required
        />
      </div>
      <div>
        <label>Password:</label>
        <input 
          type="password" 
          value={password} 
          onChange={(e) => setPassword(e.target.value)} 
          required
        />
      </div>
      {error && <p style={{ color: 'red' }}>{error}</p>}
      <button type="submit" disabled={loading}>
        {loading ? 'Logging in...' : 'Login'}
      </button>
    </form>
  );
};

export default Login;

4. 인증된 사용자 전용 컴포넌트 (대시보드) 생성

로그인 상태에 따라 사용자 정보를 보여주고 로그아웃할 수 있도록 합니다.
파일: src/components/Dashboard.tsx

import React from 'react';
import { useAuth } from '../hooks/useAuth';

const Dashboard: React.FC = () => {
  const { user, logout } = useAuth();

  if (!user) {
    return <p>Please log in.</p>;
  }

  return (
    <div>
      <h2>Dashboard</h2>
      <p>Welcome, {user.name}!</p>
      <button onClick={logout}>Logout</button>
    </div>
  );
};

export default Dashboard;

5. App 컴포넌트에서 AuthProvider로 전체 애플리케이션 감싸기

최상위 컴포넌트에서 AuthProvider로 애플리케이션을 감싸면, 자식 컴포넌트들이 언제든지 인증 정보를 사용할 수 있습니다.
파일: src/App.tsx

import React from 'react';
import { AuthProvider } from './contexts/AuthContext';
import Login from './components/Login';
import Dashboard from './components/Dashboard';

const App: React.FC = () => {
  return (
    <AuthProvider>
      <div>
        <h1>My App</h1>
        {/* 로그인 폼과 대시보드를 모두 렌더링 (상황에 따라 라우팅을 적용할 수 있음) */}
        <Login />
        <Dashboard />
      </div>
    </AuthProvider>
  );
};

export default App;

요약

  1. AuthContext와 AuthProvider
    • 인증 정보를 저장하고, 로그인/로그아웃 기능을 구현합니다.
    • localStorage를 사용해 토큰을 보관하여 새로고침에도 인증 상태를 유지할 수 있습니다.
  2. 커스텀 훅 (useAuth)
    • Context를 간편하게 사용할 수 있도록 도와줍니다.
  3. Login 컴포넌트
    • 사용자의 입력을 받아 로그인 API(여기서는 가짜 API)를 호출하고, 인증 상태를 업데이트합니다.
  4. Dashboard 컴포넌트
    • 인증된 사용자 정보를 보여주고 로그아웃 기능을 제공합니다.
  5. App 컴포넌트
    • 애플리케이션 전체를 AuthProvider로 감싸 인증 정보에 전역적으로 접근할 수 있도록 합니다.

이 구조를 기반으로 실제 API 호출 및 라우팅 기능(예: react-router-dom의 PrivateRoute) 등을 추가하면 보다 완성도 있는 인증 시스템을 구현할 수 있습니다. 필요한 부분이나 추가 질문이 있으면 언제든지 말씀해주세요!

728x90
반응형