ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 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

    댓글

Designed by Tistory.