// 인증, Authentication
- 보호가 필요하고, 아무나 접근해서는 안 될 때 사용한다.
- 특정 백엔드 resources에 접근할 때, 접근권한이 주어지기 전에 인증(허가)을 받아야 한다.
- 사용자 자격 증명을 server로 보내면 server에서 검증 후 결과를 반환한다.
~ Server-sede Sessions / Authentication Tokens
- 프론트엔드와 백엔드를 분리하는 경우에는 Authentication Token을 사용한다.
- 클라이언트에서 서버에 사용자 자격 증명을 전송한다.
- 서버에서 사용자 자격 증명을 받은 후 Authentication Token을 생성한다.
- Token을 생성한 백엔드만이 해당 Token의 유효성을 확인하고 검증할 수 있다. - 생성한 Token을 다시 클라이언트에 전송한다.
- 클라이언트는 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('/');
}
'클라이언트 > React' 카테고리의 다른 글
[리액트(React)] Tanstack Query(React Query) (0) | 2024.02.24 |
---|---|
[리액트(React)] 배포(Deploying) (0) | 2024.02.23 |
[리액트(React)] Router - useFetcher (0) | 2024.02.21 |
[리액트(React)] Router - action (0) | 2024.02.20 |
[리액트(React)] Router - loader (0) | 2024.02.19 |