ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Node.js] Express와 jwt 토큰(Json Web Token)을 이용한 회원가입 / 로그인 구현하기 [2]
    Backend 2019. 9. 13. 18:10

    지난 번에 Express와 MongoDB로 간단하게 백엔드 서버를 만들었으니 

    이제 그걸 이용해서 프론트엔드 구현을 마저 해보려고 한다.

     

     

     

     

    client/src/index.js

    import React from 'react';
    import ReactDOM from 'react-dom';
    import './index.css';
    import App from './components/App';
    import * as serviceWorker from './serviceWorker';
    import { BrowserRouter as Router } from 'react-router-dom';
    import { CookiesProvider } from 'react-cookie';
    
    ReactDOM.render(
      <Router>
        <CookiesProvider>
          <App />
        </CookiesProvider>
      </Router>
      , document.getElementById('root'));

     

    react-cookie와 react-router 모듈을 사용하기위해

    npm install한 후 index.js에 꽂아넣었다.

     

     

     

     

    client/src/Components/App.js

    import React, { useState, useEffect } from 'react';
    import { Route, Switch, Redirect } from "react-router-dom";
    import { withCookies, useCookies } from 'react-cookie';
    import Login from './Login';
    import Join from './Join';
    import Todo from './Todo';
    import './css/App.css';
    
    const App = () => {
      
      const [ cookies, removeCookie ] = useCookies([ 'user' ]);
      const [ hasCookie, setHasCookie ] = useState(false);
    
      useEffect(() => {
        if (cookies.user && cookies.user !== 'undefined') {
          setHasCookie(true);
        }
      }, [ cookies ]);
    
      return (
        <div className="App">
          <h1>Todo App</h1>
    
          {!hasCookie ? <Redirect to="/login" /> : <Redirect to="/todo" />}
    
          <Switch>
            <Route
              exact path="/login"
              render={routerProps => {
                return (
                  <Login
                    {...routerProps}
                    setHasCookie={setHasCookie}
                  />
                );
              }}
            />
            
            <Route
              exact path="/join"
              component={Join}
            />
    
            <Route
              exact path="/todo"
              render={routerProps => {
                return (
                  <Todo
                    {...routerProps}
                    setHasCookie={setHasCookie}
                    removeCookie={() => {
                      removeCookie('user');
                      setHasCookie(false);
                    }}
                  />
                );
              }}
            />
          </Switch>
        </div>
      );
    };
    
    export default withCookies(App);
    

     

    서버로부터 받아온 토큰 정보가 쿠키에 저장되어있지 않다면 Login 컴포넌트를 띄우고,

    저장되어있다면 Todo 컴포넌트를 띄운다.

     

     

     

     

    client/src/Components/Login.js

    import React, { useState } from 'react';
    import { Link } from "react-router-dom";
    
    const Login = ({ setHasCookie }) => {
      const [ userId, setUserId ] = useState('');
      const [ userPw, setUserPw ] = useState('');
    
      const loginApi = (user) => {
        return fetch('/users/login', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json'
          },
          body: JSON.stringify(user)
        }).then(response => response.json());
      };
    
      const handleSubmit = async (e) => {
        e.preventDefault();
        
        if (!userId || !userPw) {
          return;
        }
    
        try {
          const response = await loginApi({
            user_id: userId,
            user_pw: userPw
          });
    
          if (response.result === 'ok') {
            setHasCookie(true);
          } else {
            throw new Error(response.error);
          }
        } catch (err) {
          alert('로그인에 실패했습니다.');
          setUserId('');
          setUserPw('');
          console.error('login error', err);
        }
      };
    
      return (
        <div>
          <h2>Login</h2>
          <form
            onSubmit={handleSubmit}
          >
            <input 
              type="text"
              name="user_id"
              value={userId}
              onChange={e => setUserId(e.target.value)}
              placeholder="id"
            />
            <input
              type="password"
              name="user_pw"
              value={userPw}
              onChange={e => setUserPw(e.target.value)}
              placeholder="pw"
            />
            <button
              type="submit"
            >
              Login
            </button>
          </form>
          <Link
            to="/join"
          >
            회원가입
          </Link>
        </div>
      );
    };
    
    export default Login;
    

     

     

    로그인 페이지에서 id와 pw를 사용자가 입력하고 Login 버튼을 클릭하면

    handleSubmit 메소드가 실행된다.

     

    이 때 서버에 HTTP POST 요청을 보내는데,

    만약 valid한 user라면 token을 생성한 후 쿠키에 저장된다.

     

    로그인에 실패할 경우 alert 메시지가 뜬다.

     

     

     

     

    client/src/Components/Join.js

    import React, { useState } from 'react';
    import { Link } from "react-router-dom";
    
    const Join = () => {
      const [ userId, setUserId ] = useState('');
      const [ userPw, setUserPw ] = useState('');
      const [ userName, setUserName ] = useState('');
      const [ isJoinSuccess, setJoinSuccess ] = useState(false);
    
      const createUserApi = (user) => {
        return fetch('/users/new', {
          method: 'POST',
          body: JSON.stringify(user),
          headers: {
            'Content-Type': 'application/json'
          }
        }).then(response => response.json());
      };
    
      const handleSubmit = async (e) => {
        e.preventDefault();
    
        try {
          const response = await createUserApi({
            user_id: userId,
            user_pw: userPw,
            user_name: userName
          });
    
          if (response.result === 'ok') {
            setJoinSuccess(true);
          }
        } catch (err) {
          console.error('login error', err);
          alert('회원가입에 실패하였습니다. 잠시 후 다시 시도해주세요.')
        }
      };
    
      return (
        <div>
          {!isJoinSuccess && (
            <>
              <h2>Join</h2>
              <form
                onSubmit={handleSubmit}
              >
                <input
                  type="text"
                  name="user_id"
                  value={userId}
                  onChange={e => setUserId(e.target.value)}
                  placeholder="id"
                />
                <input
                  type="password"
                  name="user_pw"
                  value={userPw}
                  onChange={e => setUserPw(e.target.value)}
                  placeholder="pw"
                />
                <input
                  type="text"
                  name="user_name"
                  value={userName}
                  onChange={e => setUserName(e.target.value)}
                  placeholder="name"
                />
                <button
                  type="submit"
                >
                  제출
                </button>
              </form>
            </>
          )}
          {isJoinSuccess && (
            <div>
              <p>회원가입을 축하합니다!</p>
              <Link to="/login">로그인</Link>
            </div>
          )}
        </div>
      );
    };
    
    export default Join;
    

     

     

     

    아주 아주 간단한 회원가입 페이지이다.

    /users/new 경로로 POST 요청을 보내 DB에 회원 정보를 새로 생성한다.

     

    성공하면 isJoinSuccess 변수를 true로 만들어주고

    회원가입을 축하한다는 메시지를 보여주는 페이지를 렌더링한다.

    실패하면 alert 메시지를 보여준다.

     

    회원가입이 완료된 후에 Robo 3T를 살펴보면

    아까 넣은 데이터가 저장되어있는 것을 확인할 수 있다.

     

     

     

     

     

     

    client/src/Components/Todo.js

    import React, { useState, useEffect } from 'react';
    
    const Todo = ({ setHasCookie, removeCookie }) => {
      const [ todos, setTodos ] = useState(null);
    
      useEffect(() => {
        const abortController = new AbortController();
        const signal = abortController.signal;
    
        const getAllTodoApi = () => {
          return new Promise((resolve, reject) => {
            fetch('/todos', {
              signal: signal,
              method: 'GET',
              headers: {
                'Content-Type': 'application/json'
              }
            })
              .then(res => resolve(res.json()))
              .catch(err => reject(err));
          });
        };
    
        const onTodoLoad = async () => {
          try {
            const response = await getAllTodoApi();
    
            if (response.error === 'token expired') {
              setHasCookie(false);
            } else {
              setTodos(response.todos);
            }
          } catch (err) {
            console.log(err);
          }
        };
    
        if (!todos) {
          onTodoLoad();
        }
    
        return () => {
          abortController.abort();
        }
      }, [ todos, setHasCookie ]);
    
      return (
        <div>
          <h2>
            Todo
            <button
              type="button"
              onClick={removeCookie}
            >
              logout
            </button>
          </h2>
          <ul>
            {todos && (
              todos.map(todo => (
                <li key={todo._id}>
                  <span>{todo.content}</span>
                  <span>{todo.created_at}</span>
                  <input
                    type="checkbox"
                    value={todo.complete}
                  />
                </li>
              ))
            )}
          </ul>
        </div>
      );
    };
    
    export default Todo;
    

     

     

    회원가입과 로그인이 모두 성공하면 todo 페이지로 이동한다.

     

     

     

     

    반응형

    COMMENT