본문 바로가기

Python/django

[#. Django] Django(Rest Framework) + React로 Authentication 로그인/회원가입 구현하기 2

반응형

 

 

 

 

 

developer0809.tistory.com/98

 

[#. Django] Django(Rest Framework) + React로 Authentication 로그인/회원가입 구현하기 1

developer0809.tistory.com/92?category=895002 [#. Django] Django(Rest Framework) + React로 프로젝트 시작하기1 데이터 시각화를 위해 Python을 사용해야 하는데 그 중에서도 Django framework를 사용하고 싶..

developer0809.tistory.com

이전 글에서 backend에서 세팅은 했으니 이제 React에서 로그인/회원가입 페이지와 로그아웃을 구현해 보자

 

 

 

 

 

 

현재 구조는 이렇다

 

 

 

 

 

 

 

 

① frontend/src/App.js 

 

import {Route, Switch} from 'react-router-dom';
import React, {Suspense} from 'react';
import './App.css';
import NavBar from './components/NavBar/NavBar';	// 추가
import LoginPage from './components/UserPage/LoginPage';	// 추가
import SignupPage from './components/UserPage/SignupPage';	// 추가

function App() {
  return (
    <Suspense fallback={(<div>...</div>)}>
      <NavBar />
      <div className="App">
          <Switch>
            <Route exact path="/login" component={LoginPage}></Route>	// 추가
            <Route exact path="/signup" component={SignupPage}></Route>	// 추가
          </Switch>
        </div>
    </Suspense>
    );
}

export default App;

 

 

 

 

 

② frontend/src/components/NavBar/NavBar.js

 

import React, { useState, useEffect } from 'react';
import { Menu, Button } from 'antd';
import styled from 'styled-components';
import { Link } from 'react-router-dom';

const MenuList = styled.div`
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  .ant-menu {
    display: flex;
    justify-content: flex-end;
    width: 100%;
  }
`;

function NavBar() {

  const [auth, setAuth] = useState('')

  useEffect(() => {
    if (localStorage.getItem('token') !== null) {
      setAuth(true);
    }
  }, [])

  const handleLogout = () => {

   fetch('http://127.0.0.1:8000/api/v1/mall/auth/logout/', {
     method: 'POST',
     headers: {
       'Content-Type': 'application/json',
       Authorization: `Token ${localStorage.getItem('token')}`
     }
   })
     .then(res => res.json())
     .then(data => {
       console.log(data);
       localStorage.clear();
       window.location.replace('http://localhost:3000/login');
     });
  };

  return(
    <div>
      <MenuList>
        <Menu>
          { auth ?
            <Menu.Item key="logout" onClick={handleLogout}>
              로그아웃
            </Menu.Item>
            :
            <Menu.Item key="signin">
              <Link to="/login">
              로그인
              </Link>
            </Menu.Item>
          }
          { auth ?
            <></>
          :
            <Menu.Item key="signup">
              <Link to="/signup">
              회원가입
              </Link>
            </Menu.Item>
          }
        </Menu>
      </MenuList>
    </div>
  )
}

export default NavBar;

 

styled-components, antd를 설치해야 한다

 

npm install --save styled-components
npm install --save antd

 

네비게이션 바에서 로그인하면 회원가입/로그인 메뉴는 노출되지 않고 로그아웃만 노츨되도록 했다

반대로 로그아웃 하면 다시 회원가입/로그인 메뉴가 노출된다

 

 

 

 

 

 

frontend/src/components/UserPage/LoginPage.js

 

import React, { useState, useEffect } from 'react';

const LoginPage = () => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [errors, setErrors] = useState(false);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    if (localStorage.getItem('token') !== null) {
      window.location.replace('http://localhost:3000/dashboard');
    } else {
      setLoading(false);
    }
  }, []);

  const onSubmit = e => {
    e.preventDefault();

    const user = {
      email: email,
      password: password
    };

    fetch('http://127.0.0.1:8000/api/v1/mall/auth/login/', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(user)
    })
      .then(res => res.json())
      .then(data => {
        if (data.key) {
          localStorage.clear();
          localStorage.setItem('token', data.key);
          window.location.replace('http://localhost:3000/dashboard');
        } else {
          setEmail('');
          setPassword('');
          localStorage.clear();
          setErrors(true);
        }
      });
  };

  return (
    <div>
      {loading === false && <h1>Login</h1>}
      {errors === true && <h2>Cannot log in with provided credentials</h2>}
      {loading === false && (
        <form onSubmit={onSubmit}>
          <label htmlFor='email'>Email address:</label> <br />
          <input
            name='email'
            type='email'
            value={email}
            required
            onChange={e => setEmail(e.target.value)}
          />{' '}
          <br />
          <label htmlFor='password'>Password:</label> <br />
          <input
            name='password'
            type='password'
            value={password}
            required
            onChange={e => setPassword(e.target.value)}
          />{' '}
          <br />
          <input type='submit' value='Login' />
        </form>
      )}
    </div>
  );
};

export default LoginPage;

 

 

 

localhost:3000/login을 입력하거나 NavBar에 로그인을 클릭하면 아래처럼 페이지가 뜨는 것을 확인할 수 있다

 

 

 

 

 

 

 

 

④ frontend/src/components/UserPage/SignupPage.js

 

import React, { useState, useEffect } from 'react';

const SignupPage = () => {
  const [email, setEmail] = useState('');
  const [password1, setPassword1] = useState('');
  const [password2, setPassword2] = useState('');
  const [errors, setErrors] = useState(false);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    if (localStorage.getItem('token') !== null) {
      window.location.replace('http://localhost:3000/dashboard');
    } else {
      setLoading(false);
    }
  }, []);

  const onSubmit = e => {
    e.preventDefault();

    const user = {
      email: email,
      password1: password1,
      password2: password2
    };

    fetch('http://127.0.0.1:8000/api/v1/mall/auth/register/', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(user)
    })
      .then(res => res.json())
      .then(data => {
        if (data.key) {
          localStorage.clear();
          localStorage.setItem('token', data.key);
          window.location.replace('http://localhost:3000/dashboard');
        } else {
          setEmail('');
          setPassword1('');
          setPassword2('');
          localStorage.clear();
          setErrors(true);
        }
      });
  };

  return (
    <div>
      {loading === false && <h1>Signup</h1>}
      {errors === true && <h2>Cannot signup with provided credentials</h2>}
      <form onSubmit={onSubmit}>
        <label htmlFor='email'>Email address:</label> <br />
        <input
          name='email'
          type='email'
          value={email}
          onChange={e => setEmail(e.target.value)}
          required
        />{' '}
        <br />
        <label htmlFor='password1'>Password:</label> <br />
        <input
          name='password1'
          type='password'
          value={password1}
          onChange={e => setPassword1(e.target.value)}
          required
        />{' '}
        <br />
        <label htmlFor='password2'>Confirm password:</label> <br />
        <input
          name='password2'
          type='password'
          value={password2}
          onChange={e => setPassword2(e.target.value)}
          required
        />{' '}
        <br />
        <input type='submit' value='Signup' />
      </form>
    </div>
  );
};

export default SignupPage;

 

 

localhost:3000/signup을 입력하거나 NavBar에서 회원가입을 클릭하면 아래처럼 페이지가 뜨는 것을 확인할 수 있다

 

 

 

 

 

 

 

 

 

나는 axios를 사용할 것이기 때문에 fetch로 로그인/회원가입 하는 부분을 axios를 사용하도록 수정할 것이다

 

 

 

 

 

 

frontend/src/components/UserPage/LoginPage.js 수정

 

import React, { useState } from 'react';
import Axios from 'axios';
import { Input } from 'antd';
import styled from 'styled-components';

const LoginDiv = styled.div`
  padding: 3rem;
    form {
    width: 320px;
    display: inline-block;
    label {
      margin-bottom: 1rem;
    }
    input {
      margin-bottom: 1.5rem;
      &[type=submit] {
        background: black;
        color: white;
        margin-top: 1rem;
      }
    }
  }
`;

const LoginPage = () => {

  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const [errors, setErrors] = useState(false)

  const onSubmit = (e) => {
    e.preventDefault()

    const user = {
      email: email,
      password: password
    }

    Axios.post('/api/v1/mall/auth/login/', user)
      .then(res => {
        if (res.data.key) {
          localStorage.clear()
          localStorage.setItem('token', res.data.key)
          // 사용하려면 App.js에서 /로 라우팅해야 한다
          window.location.replace('/')
        } else {
          setEmail('')
          setPassword('')
          localStorage.clear()
          setErrors(true)
        }
      })
      .catch(err => {
        console.clear()
        alert('아이디 또는 비밀번호가 일치하지 않습니다')
        setEmail('')
        setPassword('')
      })
  }

  return (
    <LoginDiv>
      <h1>로그인</h1>
      <br />
      {errors === true && <h2>Cannot log in with provided credentials</h2>}
        <form onSubmit={onSubmit}>
          <label>이메일 주소:</label>
          <Input
            type='email'
            value={email}
            required
            onChange={e => setEmail(e.target.value)}
          />
          <label>비밀번호:</label>
          <Input
            type='password'
            value={password}
            required
            onChange={e => setPassword(e.target.value)}
          />
          <Input type='submit' size="large" value='로그인' />
        </form>
    </LoginDiv>
  )
}

export default LoginPage;

 

 

 

 

 

frontend/src/components/UserPage/SignupPage.js 수정

 

import React, { useState } from 'react';
import Axios from 'axios';
import { Input } from 'antd';
import styled from 'styled-components';

const SignupDiv = styled.div`
  padding: 3rem;
    form {
    width: 320px;
    display: inline-block;
    label {
      margin-bottom: 1rem;
    }
    input {
      margin-bottom: 1.5rem;
      &[type=submit] {
        background: black;
        color: white;
        margin-top: 1rem;
      }
    }
  }
`;

const SignupPage = () => {
  const [email, setEmail] = useState('')
  const [password1, setPassword1] = useState('')
  const [password2, setPassword2] = useState('')
  const [errors, setErrors] = useState(false)

  const onChangeEmail = (e) => {
    setEmail(e.target.value)
  }

  const onChangePwd1 = (e) => {
    setPassword1(e.target.value)
  }

  const onChangePwd2 = (e) => {
    setPassword2(e.target.value)
  }

  const onSubmit = (e) => {
    e.preventDefault()

    const user = {
      email: email,
      password1: password1,
      password2: password2
    }

    // 유효성 검사
    if(password1 !== password2) {
      alert('비밀번호와 비밀번호 확인이 일치하지 않습니다')
      return false
    }

    Axios.post('/api/v1/mall/auth/register/', user)
      .then(res => {
        if (res.data.key) {
          localStorage.clear()
          localStorage.setItem('token', res.data.key)
          // 사용하려면 App.js에서 /로 라우팅해야 한다
          window.location.replace('/')
        } else {
          setEmail('')
          setPassword1('')
          setPassword2('')
          localStorage.clear()
          setErrors(true)
        }
      })
      .catch(err => {
        console.clear()
        alert('아이디 혹은 비밀번호가 일치하지 않습니다')
      })
  }

  return (
    <SignupDiv>
      <h1>회원가입</h1>
      <br />
      {errors === true && <h2>Cannot signup with provided credentials</h2>}
      <form onSubmit={onSubmit}>
        <label htmlFor='email'>이메일 주소:</label>
        <Input
          type='email'
          value={email}
          onChange={onChangeEmail}
          required
        />
        <label htmlFor='password1'>비밀번호(소문자, 숫자, 특수문자 포함 8~16자):</label>
        <Input
          type='password'
          value={password1}
          onChange={onChangePwd1}
          minLength='8'
          pattern='^(?=.*[a-z])(?=.*\d)(?=.*[$@$!%*#?&])[a-z\d$@$!%*#?&]{8,16}$'
          required
        />
        <br />
        <label htmlFor='password2'>비밀번호 확인(소문자, 숫자, 특수문자 포함 8~16자):</label>
        <Input
          type='password'
          value={password2}
          onChange={onChangePwd2}
          minLength='8'
          pattern='^(?=.*[a-z])(?=.*\d)(?=.*[$@$!%*#?&])[a-z\d$@$!%*#?&]{8,16}$'
          required
        />
        <Input type='submit' size="large" value='가입하기' />
      </form>
    </SignupDiv>
  )
}

export default SignupPage;

 

 

 

 

 

frontend/src/components/NavBar/NavBar.js

 

import React, { useState, useEffect } from 'react';
import { Menu, Button } from 'antd';
import styled from 'styled-components';
import { Link } from 'react-router-dom';
import Axios from 'axios';	// 추가

const MenuList = styled.div`
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  .ant-menu {
    display: flex;
    justify-content: flex-end;
    width: 100%;
  }
`;

function NavBar() {

  const [auth, setAuth] = useState('')

  useEffect(() => {
    if (localStorage.getItem('token') !== null) {
      setAuth(true)
    }
  }, [])

  // fetch to axios 수정 
  const handleLogout = () => {
    let token = localStorage.getItem('token')

    Axios.post('/api/v1/mall/auth/logout/', token)
      .then(res => {
        localStorage.clear()
        // 사용하려면 App.js에서 /로 라우팅해야 한다
        window.location.replace('/')
      });
  }

  return(
    <div>
      <MenuList>
        <Menu>
          { auth ?
            <Menu.Item key="logout" onClick={handleLogout}>
              로그아웃
            </Menu.Item>
            :
            <Menu.Item key="signin">
              <Link to="/login">
              로그인
              </Link>
            </Menu.Item>
          }
          { auth ?
            <></>
          :
            <Menu.Item key="signup">
              <Link to="/signup">
              회원가입
              </Link>
            </Menu.Item>
          }
        </Menu>
      </MenuList>
    </div>
  )
}

export default NavBar;

 

 

 

 

 

 

 

 

완료했다

 

 

 

 

 

 

 

 

 

 

 

 

 

 

아래 사이트를 참고했다

 

medium.com/dev-genius/django-react-authentication-part-2-ea626688165e

 

Django + React Authentication: Part 2

Using React to create a frontend to the Django API

medium.com

 

 

반응형