본문 바로가기

클라이언트/React
[리액트(React)] 인증(Authentication)

// 인증, Authentication

- 보호가 필요하고, 아무나 접근해서는 안 될 때 사용한다.

- 특정 백엔드 resources에 접근할 때, 접근권한이 주어지기 전에 인증(허가)을 받아야 한다.

- 사용자 자격 증명을 server로 보내면 server에서 검증 후 결과를 반환한다.
   ~ Server-sede Sessions / Authentication Tokens

- 프론트엔드와 백엔드를 분리하는 경우에는 Authentication Token을 사용한다.

  1. 클라이언트에서 서버에 사용자 자격 증명을 전송한다.
  2. 서버에서 사용자 자격 증명을 받은 후 Authentication Token을 생성한다.
    - Token을 생성한 백엔드만이 해당 Token의 유효성을 확인하고 검증할 수 있다.
  3. 생성한 Token을 다시 클라이언트에 전송한다.
  4. 클라이언트는 Token을 받은 후 다시 서버에 request를 보낼 때, Token을 함께 보낸다.

1. 받아온 token을 client 측에 저장한다.

//저장
localStorage.setItem('token', token);

//꺼내기
const token = localStorage.getItem('token');


- 받아온 token을 저장하는 방법 중 가장 간단한 것은 local storage에 저장하는 것이다.


2. server에 request를 전송한다.

headers: {
  'Authorization': `Bearer ${token}`,
},


- 서버에 request를 보낼 때 token을 함께 보낸다.


3. token을 제거한다.

 

localStorage.removeItem('token');


- local storage에서 token을 지운다.


* 특정 시간이 지나면 만료되도록 할 수도 있다.

 


1. 유효 시간 저장

const expiration = new Date();
expiration.setHours(expiration.getHours() + 1);
localStorage.setItem('expiration', expiration.toISOString());


- token을 local storage에 저장할 때 유효 시간도 저장한다.

2. 유효 시간 확인

const storedExpirationDate = localStorage.getItem('expiration');
const expirationDate = new Date(storedExpirationDate);
const now = new Date();

return expirationDate.getTime() - now.getTime();


3. 유효 시간이 지났으면 token 제거

useEffect(() => {
  if (!token) {
    return;
  }
  
  const tokenDuration = getTokenDuration();
  if (tokenDuration < 0) {
  localStorage.removeItem('token');
  }

  setTimeout(() => {
    logout();
  }, tokenDuration);
}, [token]);

import { createBrowserRouter, RouterProvider } from 'react-router-dom';

import EditEventPage from './pages/EditEvent';
import ErrorPage from './pages/Error';
import EventDetailPage, {
  action as deleteEventAction,
  loader as eventDetailLoader,
} from './pages/EventDetail';
import EventsPage, { loader as eventsLoader } from './pages/Events';
import EventsRootLayout from './pages/EventsRoot';
import HomePage from './pages/Home';
import NewEventPage from './pages/NewEvent';
import RootLayout from './pages/Root';
import { action as manipulateEventAction } from './components/EventForm';
import NewsletterPage, { action as newsletterAction } from './pages/Newsletter';
import AuthenticationPage, {
  action as authAction,
} from './pages/Authentication';
import { checkAuthLoader, tokenLoader } from './util/auth';

const router = createBrowserRouter([
  {
    path: '/',
    element: <RootLayout />,
    errorElement: <ErrorPage />,
    loader: tokenLoader,
    id: 'token',
    children: [
      { index: true, element: <HomePage /> },
      {
        path: 'auth',
        element: <AuthenticationPage />,
        action: authAction,
      },
      {
        path: 'events',
        element: <EventsRootLayout />,
        children: [
          {
            index: true,
            element: <EventsPage />,
            loader: eventsLoader,
          },
          {
            path: ':eventId',
            id: 'event-detail',
            loader: eventDetailLoader,
            children: [
              {
                index: true,
                element: <EventDetailPage />,
                action: deleteEventAction,
              },
              {
                path: 'edit',
                element: <EditEventPage />,
                action: manipulateEventAction,
                loader: checkAuthLoader,
              },
            ],
          },
          {
            path: 'new',
            element: <NewEventPage />,
            action: manipulateEventAction,
            loader: checkAuthLoader,
          },
        ],
      },
      {
        path: 'newsletter',
        element: <NewsletterPage />,
        action: newsletterAction,
      },
    ],
  },
]);

function App() {
  return <RouterProvider router={router} />;
}

export default App;

 

import AuthForm from '../components/AuthForm';
import { json, redirect } from 'react-router-dom';
import axios from 'axios';

function AuthenticationPage() {
  return <AuthForm />;
}

export default AuthenticationPage;

// export async function action({ request }) {
//   const searchParams = new URL(request.url).searchParams;
//   const mode = searchParams.get('mode') || 'login';
//
//   if (mode !== 'login' && mode !== 'signup') {
//     throw json({ message: 'Unsupported mode.' }, { status: 422 });
//   }
//
//   const data = await request.formData();
//   const authData = {
//     email: data.get('email'),
//     password: data.get('password'),
//   };
//
//   const result = await requestAuth(authData, mode);
//
//   if (result.result.status === 422 || result.result.status === 401) {
//     return result.result.data;
//   }
//
//   if (result.status === 'FAIL') {
//     throw json({ message: 'Could not authenticate user.' }, { status: 500 });
//   }
//
//   return redirect('/');
// }

export async function action({ request }) {
  const searchParams = new URL(request.url).searchParams;
  const mode = searchParams.get('mode') || 'login';

  if (mode !== 'login' && mode !== 'signup') {
    throw new Error('Unsupported mode.', { status: 422 });
  }

  const data = await request.formData();
  const authData = {
    email: data.get('email'),
    password: data.get('password'),
  };

  try {
    const response = await axios.post(
      `http://localhost:8080/${mode}`,
      authData,
    );

    const token = response.data.token;
    localStorage.setItem('token', token);

    const expiration = new Date();
    expiration.setHours(expiration.getHours() + 1);
    localStorage.setItem('expiration', expiration.toISOString());

    return redirect('/');
  } catch (error) {
    if (error.response.status === 422 || error.response.status === 401) {
      return error.response.data;
    } else {
      throw json({ message: 'Could not authenticate user.' }, { status: 500 });
    }
  }
}

 

import { redirect } from 'react-router-dom';

export function getTokenDuration() {
  const storedExpirationDate = localStorage.getItem('expiration');
  const expirationDate = new Date(storedExpirationDate);
  const now = new Date();

  return expirationDate.getTime() - now.getTime();
}

export function getAuthToken() {
  return localStorage.getItem('token');
}

export function tokenLoader() {
  return getAuthToken();
}

export function checkAuthLoader() {
  const token = getAuthToken();

  if (!token) {
    window.alert('You need to login.');
    return redirect('/auth');
  }

  return null;
}

 

import { redirect } from 'react-router-dom';

export function logout() {
  localStorage.removeItem('token');
  localStorage.removeItem('expiration');
  return redirect('/');
}