-
9. Auth프론트엔드/React 2020. 2. 5. 23:29반응형SMALL
# What? 이게 뭔데?
- 인증
# Why? 이걸 왜 배우는데?
- 화원가입, 로그인 등 회원인증 관련 작업에 필요함.
- SPA(Single Page Application)에서는 주로 JWT(Json Web Token) 방식으로 구현.
* 주로 쓰이는 로그인 정보 인증 방식
-> 클라이언트에서 사용자 로그인
-> 서버에서 사용자 정보를 토대로 확인
-> 서버에서 access_token(인증토큰)을 발급
-> 클라이언트에서 access_token을 local storage, store에 저장
-> API 요청을 보낼 때 header에 access_token을 담아 로그인 된 유저임을 증명
# How? 어떻게 쓰는데?
1. 가상서버 세팅
- json-server-auth라는 패키지를 사용하면 API로 가상 회원가입, 로그인을 진행해볼 수 있음.
* 로그인, 회원가입 시 인증토큰을 리턴해줌(임시 토큰 만료기간 1시간). 그걸 받아 local storage, store에 저장해놓고 api 요청 시 header에 담아 로그인한 유저임을 인증해야함.
$ npm install -g json-server-auth json-server express
METHOD URL 의미 POST /register 회원가입 POST /login 로그인 @ db.json
{ "users": [] }
2. proxy 설정(가상 서버는 port 5000으로 띄울 예정)
@ package.json
{ "name": "react-course", "version": "0.1.0", "private": true, "dependencies": { "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.4.0", "@testing-library/user-event": "^7.2.1", "react": "^16.12.0", "react-dom": "^16.12.0", "react-redux": "^7.1.3", "react-router-dom": "^5.1.2", "react-scripts": "3.3.0", "redux": "^4.0.5", "redux-devtools-extension": "^2.13.8", "redux-thunk": "^2.3.0" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, "eslintConfig": { "extends": "react-app" }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] }, "proxy": "http://localhost:5000" }
3. 프론트, 백엔드 서버 각각 띄우기(콘솔창 2개 열어서)
$ npm start
$ json-server-auth db.json --port=5000
4. 회원가입 생성
* 회원가입 절차
1) 정보를 입력받아 회원가입 요청
2) 회원가입 정보 및 인증토큰을 store 및 localStorage에 저장
=> localStorage에는 왜 저장해? store에만 저장해놓으면 새로고침 눌렀을 때 인증정보가 다 날라감. 그래서 localStorage에 저장해뒀다가 새로고침 시 이 데이터를 store에 세팅해주는 작업이 필요
3) 메인페이지로 이동시켜주기
@ utils/auth.js
export function getLocalUser() { const user = localStorage.getItem("user"); if(!user) return null; return JSON.parse(user); } export function getLocalToken() { const token = localStorage.getItem("token"); if(!token) return null; return JSON.parse(token); }
@ redux/reducer.js
import {getLocalToken, getLocalUser} from "../utils/auth"; let user = getLocalUser(); let token = getLocalToken(); const initialState = { // 초기값 user: user, token: token }; const reducer = export default (state = initialState, action) => { switch (action.type) { default: return state; case "SET_USER": return { ...state, user: action.payload }; case "SET_TOKEN": return { ...state, token: action.payload } } } export default reducer;
@ commonActions.js
import axios from 'axios'; export const login = (user, token, onLogin) => { return (dispatch) => { dispatch({ type: "SET_USER", payload: user }); dispatch({ type: "SET_TOKEN", payload: token }); localStorage.setItem("user", JSON.stringify(user)); localStorage.setItem("token", JSON.stringify(token)); onLogin(); } }; export const logout = () => { return (dispatch) => { axios.post("/logout").then(response => { dispatch({ type: "SET_USER", payload: null }); dispatch({ type: "SET_TOKEN", payload: null }); localStorage.removeItem("user"); localStorage.removeItem("token"); }); } };
@ Register.jsx
import React, {useState} from 'react'; import axios from "axios"; import {connect} from "react-redux"; import {login} from "./actions/commonActions"; const Register = ({login, history}) => { let [form, setForm] = useState({ email: "", password: "" }); const changeForm = (event) => { setForm({ ...form, [event.target.name] : event.target.value }); }; const submit = (event) => { event.preventDefault(); axios.post("/register", form) .then(response => { login({email: form.email}, response.data.accessToken, onLogin); }) }; const onLogin = () => { history.push("/"); }; return ( <div id={"login"}> <p className="title">회원가입</p> <form onSubmit={submit}> <input type="email" placeholder={"이메일 아이디를 입력해주세요."} name={"email"} onChange={changeForm}/> <input type="password" placeholder={"비밀번호를 입력해주세요."} name={"password"} onChange={changeForm}/> <button>회원가입</button> </form> </div> ); }; const mapDispatchToProps = (dispatch) => { return { login: (user, token, onLogin) => { dispatch(login(user, token, onLogin)); } } }; export default connect(null, mapDispatchToProps)(Register);
5. 로그인 생성
import React, {useState} from 'react'; import axios from 'axios'; import {connect} from "react-redux"; import {login} from "./actions/commonActions"; const Login = ({login, history}) => { let [form, setForm] = useState({ email: "", password: "" }); const changeForm = (event) => { setForm({ ...form, [event.target.name] : event.target.value }); }; const submit = (event) => { event.preventDefault(); axios.post("/login", form) .then(response => { console.log(response); login({email: form.email}, response.data.accessToken, onLogin); }) }; const onLogin = () => { history.push("/"); }; return ( <div id={"login"}> <p className="title">로그인</p> <form onSubmit={submit}> <input type="email" placeholder={"이메일 아이디를 입력해주세요."} name={"email"} onChange={changeForm}/> <input type="password" placeholder={"비밀번호를 입력해주세요."} name={"password"} onChange={changeForm}/> <button>로그인</button> </form> </div> ); }; const mapDispatchToProps = (dispatch) => { return { login: (user, token, onLogin) => { return dispatch(login(user, token, onLogin)); } } }; export default connect(null, mapDispatchToProps)(Login);
6. 회원인증 미들웨어 역할을 할 AuthRoute 생성
@App.jsx
import React from 'react'; import {Provider} from 'react-redux'; import store from './store'; import Login from './Login'; import Register from './Register'; import Example from './Example'; import {BrowserRouter, Route, Switch} from "react-router-dom"; import AuthRoute from "./AuthRoute"; import UserInfo from './UserInfo'; function App() { return ( <Provider store={store}> <BrowserRouter> <Switch> <Route exact path={"/"} component={Example} /> <AuthRoute path={"/userInfo"} component={UserInfo} /> <Route exact path={"/login"} component={Login} /> <Route exact path={"/register"} component={Register} /> </Switch> </BrowserRouter> </Provider> ); } export default App;
@ AuthRoute.jsx
import React, {} from 'react'; import {Route, Redirect} from 'react-router-dom'; import {connect} from 'react-redux'; const AuthRoute = ({user, ...rest}) => { return user ? <Route exact {...rest}/> : <Redirect to="/login" />; }; const mapState = (state) => { return { user: state.commonStates.user, } }; export default connect(mapState, null)(AuthRoute);
7. 인터셉터 설정(토큰 담아서 요청 보내기 & 토큰만료 시 로그아웃 처리)
- axios 요청을 보낼 때 인증토큰을 받아서 보내도록 처리하고, 토큰이 만료됐다는 응답을 받았을 때 로그아웃 처리시키기
@ interceptors.js
import axios from 'axios'; import {logout} from "../actions/commonActions"; import store from '../store'; import {getLocalToken} from "./auth"; export const setUp = () => { // 요청 보낼 때 axios.interceptors.request.use((config) => { const token = getLocalToken(); if (token) config.headers.Authorization = `${token.token_type} ${token.access_token}`; return config; }, (error) => { return Promise.reject(error); }); // 응답 받을 때 axios.interceptors.response.use((response) => { return response; }, error => { if (error.response && error.response.status === 401) store.dispatch(logout()); return Promise.reject(error); }); };
@ index.js
import {setUp} from "./utils/interceptors"; setUp();
이렇게 세팅하고나면 axios 요청을 보낼 때 알아서 인증토큰을 삽입해서 넣어주고, 토큰이 만료되면 로그아웃시켜줌
# Problem 과제
1. 로그인, 회원가입 페이지 만들기
2. 기존 할일목록 페이지를 로그인된 사용자만 볼 수 있게 만들기
* 코드참고 : github.com/ShinHyungJune/react-course/tree/course-9-auth
LIST'프론트엔드 > React' 카테고리의 다른 글
PWA (0) 2020.08.25 SWR (0) 2020.08.17 08. Redux(리덕스) (0) 2020.02.04 07. 리액트 라우터(React Router) (0) 2020.02.04 06. API 통신 (0) 2020.01.25