본문 바로가기

영어쌤의 놀이방_ 수퍼.코드.파티/리액트와 장고로 회원가입 관리 만들어욤

(7.3) Frontend Authentication_ 영어쌤이 만드는 회원가입, 인증[CRUD] 만들어욤! 리액트와 파이썬 장고

반응형

7장 Frontend Authentication

안녕하세요? 죠니입니다.

이번 7.3은 로그인 시 메세지 출력 부터 시작하겠습니다.


(12) /src/components/layout/Header.js

- Header.js 파일에 다음과 같이 코드를 작성합니다.

- 음... 코드를 작성하기 전에 제가 예상했던 메세지와는 조금 다르지만, 이것도 확실히 쓸모가 있습니다.
- 다음의 결과화면과 같이 로그아웃 버튼의 좌측에 Welcome 메세지가 발생되어 있고 user name이 있어 어떤 계정으로 인증을 받았는지 알 수 있습니다.


(13) /src/actions/auth.js

- auth.js 액션 파일의 가장 아래에 다음과 같이 코드를 추가 작성합니다.

- logout user 부분의 다음의 블록지정된 코드를 제거합니다.

- 그리고 다음과 같이 코드를 수정해 줍니다.

- User Loading 도 같은 작업을 해 줍니다.

- 반복적인 코드를 제거해 간소화 되고, UI 및 기능 또한 정상 작동합니다.


- 이제 등록(registration)을 위해 두 개의 작업을 해야 합니다.

- 그리고 토큰을 쌓고, 추가하기 위해 leads endpoint로 보내는 작업을 합니다.


(14) /src/actions/types.js

- Register Success와 Register 을 추가합니다.


(15) /src/actions/auth.js

- auth.js 파일에 다음의 코드를 추가로 작성해 줍니다.

- Register 성공과 실패에 대한 dispatch코드(Action.동작)를 작성해 줍니다.


(16) /src/reducers/auth.js

- 동일한 동작을 하게 되는 login success case에 register success를 추가합니다.
- register fail 도 동일한 작업을 해 줍니다.


(17) /src/components/accounts/Register.js

- 작업이 거의 다 완료되었습니다. 이제 우리는 UI 상에서 상단부에 Register 버튼을 클릭했을 경우 register action이 동작하도록 기능을 호출해 주도록 해야 합니다.

- 다음과 같이 기능들을 가져옵니다.

- create message action // 이전까지 작성했던 Register.js 파일에 추가 작성을 진행합니다.

- onSubmit 부분에 실질적인 기능을 부여합니다.
- Register Interface 에서 입력해준 password가 서로 일치하지 않을때, 해당 오류 메세지를 출력할 수 있도록 해 줍니다.

- 가장 하단에 다음과 같이 코드를 추가하여 action이 작동할 수 있도록 해 줍니다.

- onSubmit 코드에 다음과 같이 콘솔창 메세지 발생 코드를 추가해 주어 정상 작동이 되는지 확인하는데 도움을 줍니다.


(18) /src/components/layout/Alerts.js

- 이제 위의 코드 대로 입력해준 두 패스워드가 서로 일치하지 않으면 알림이 발생하도록 Alerts.js 파일에 코드를 추가 작성해줌으로써 해당 알림을 catch하도록 해 줍니다.

- 메세지 발생 조건에 패스워드 불일치 조건을 추가합니다.

- 결과확인

- Register Interface 에서 Password 입력 시 확인 입력과 일치하지 않는 상황을 만들어 확인합니다.

- redux Tool로 결과를 확인합니다. // create message 가 동작하였습니다.

- Interface 상으로도 확인합니다.


(19) /src/components/accounts/Register.js

- Register 시 서로간의 password가 일치한다면 Register기능 즉, 등록이 완료되는 시나리오로 가야 합니다.

- 그러기 위해선 Register.js 에서 submit 시 콘솔창으로만 출력했던 것을 실질적으로 동작이 되도록 수정해 줍니다.

 


(20) /src/actions/auth.js

- 진해하다 보니 잠시 빼먹은 것이 있습니다.

- auth.js 파일에 import 항목에 register 성공, 실패를 가져옵니다.


(21) /src/components/accounts/Register.js

- 다시 Register.js 로 돌아가 마저 진행합니다.

- auth.js 파일에 입력해 준 패턴과 일치하게 수정해 줍니다.

- 결과화면 확인

 - 주어진 조건대로 이름, 이메일, 비밀번호, 비밀번호 확인 모두 정상 입력을 주었지만 다음과 같은 오류가 발생했습니다.

- 오류를 확인합니다.

- 단순히 이미 있던 계정을 입력해 줘서 그랬던 것 입니다.
- 예외처리를 따로 잡아 메세지를 발생시키도록 해야 겠습니다.


(22) /src/components/layout/Alerts.js

- Alerts.js에서 다음의 코드를 추가 작성해 금방 전에 발생했던 오류를 메세지로 발생시켜 사용자에게 알려줍니다.

- 결과화면 확인


(23) Registration Test 진행하기

- 본론으로 돌아와 등록되어 있지 않은 계정이름으로 신규 등록을 진행합니다.

- 등록이 완료되면 다음과 같이 해당 계정에 대한 welcome 메세지가 함께 출력되고 login 버튼이 있던 자리엔 logout 버튼이 나옵니다.

- 하지만 하나를 빼먹엇네요... 로그인을 했을 시에 페이지 리디렉션(동작과 동시에 특정 페이지로 이동하도록 연결)을 해 줘야 하는데 그것을 까먹었군요.


(24) /src/components/accounts/Register.js

- 다음과 같이 Register.js파일의 렌더링 코드에 인증을 받았을때의 조건을 주어 리디렉션 동작을 실행하도록 해 줍니다.


- 마지막 작업으로 가기 전 짧지만 코드가 시작할때에 비교해 많아져서 혼동이 오므로 이만 줄이고 코드 정리 후 잠시 쉬어가도록 해야겠습니다.

- 지금까지 작성된 코드는 다음과 같습니다.

/src/actions/types.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// /src/actions/types.js
export const GET_LEADS = "GET_LEADS";
export const DELETE_LEAD = "DELETE_LEAD";
export const ADD_LEAD = "ADD_LEAD";
export const GET_ERRORS = "GET_ERRORS";
export const CREATE_MESSAGE = "CREATE_MESSAGE";
export const USER_LOADING = "USER_LOADING";
export const USER_LOADED = "USER_LOADED";
export const AUTH_ERROR = "AUTH_ERROR";
export const LOGIN_SUCCESS = "LOGIN_SUCCESS";
export const LOGIN_FAIL = "LOGIN_FAIL";
export const LOGOUT_SUCCESS = "LOGOUT_SUCCESS";
export const REGISTER_SUCCESS = "REGISTER_SUCCESS";
export const REGISTER_FAIL = "REGISTER_FAIL";
cs

/src/components/accounts/Register.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
// /src/components/accounts/Register.js
import React, { Component } from 'react'
import { Link, Redirect } from "react-router-dom";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import { login } from "../../actions/auth";
import { createMessage } from "../../actions/messages";
 
export class Register extends Component {
    state = {
        username: "",
        email: "",
        password: "",
        password2: ""
    }
 
static propTypes = {
    register: PropTypes.func.isRequired,
    isAuthenticated: PropTypes.bool
};
 
    onSubmit = e => {
        e.preventDefault();
        const { username, email, password, password2 } = this.state;
        if(password !== password2) {
            this.props.createMessage({ passwordNotMatch:
                'Password do not match' });
        } else {
            const newUser = {
                username,
                password,
                email
            }
            this.props.register(newUser);
        }
    };
 
    onChange = e =>this.setState({ [e.target.name]: e.target.value });
    
    
    render() {
        if(this.props.isAuthenticated) {
            return <Redirect to="/" />;
        }
        const { username, email, password, password2 } = this.state;
        return (
         <div className="col-md-6 m-auto">
            <div className="card card-body mt-5">
              <h2 className="text-center">Register</h2>
              <form onSubmit={this.onSubmit}>
                <div className="form-group">
                      <label>Username</label>
                      <input
                    type="text"
                    className="form-control"
                        name="username"
                        onChange={this.onChange}
                        value={username}
                     />
            </div>
            <div className="form-group">
              <label>Email</label>
              <input
                type="email"
                className="form-control"
                name="email"
                onChange={this.onChange}
                value={email}
              />
            </div>
            <div className="form-group">
              <label>Password</label>
              <input
                type="password"
                className="form-control"
                name="password"
                onChange={this.onChange}
                value={password}
              />
            </div>
            <div className="form-group">
              <label>Confirm Password</label>
              <input
                type="password"
                className="form-control"
                name="password2"
                onChange={this.onChange}
                value={password2}
              />
            </div>
            <div className="form-group">
              <button type="submit" className="btn btn-primary">
                Register
              </button>
            </div>
            <p>
              Already have an account? <Link to="/login">Login</Link>
            </p>
          </form>
        </div>
      </div>            
 
        )
    }
}
 
const mapStateToProps = state => ({
    isAuthenticated: state.auth.isAuthenticated
});
 
export default connect(
    mapStateToProps, 
    { register, createMessage }
)(Register);
 
cs

/src/components/layout/Alerts.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// /src/components/layout/Alerts.js
import React, { Component, Fragment } from 'react';
import { withAlert } from 'react-alert';
import { connect } from 'react-redux';
import PropTypes from "prop-types";
 
export class Alerts extends Component {
    static propTypes = {
        error: PropTypes.object.isRequired,
        message: PropTypes.object.isRequired
    }
}
 
export class Alerts extends Component {
    componentDidMount(prevProps) {
        const { error, alert, message } = this.props;
        if(error !== prevProps.error) {
            if(error.msg.namealert.error(`Name: ${error.msg.name.join()}`);
            if(error.msg.email) alert.error(`Email: ${error.msg.email.join()}`);
            if(error.msg.message) alert.error(`Message: ${error.msg.message.join()}`);
            if(error.msg.non_field_errors) alert.error(error.msg.non_field_errors.join());
            if(error.msg.username) alert.error(error.msg.username.join());
        }
        if(message !== prevProps.message) {
            if(message.deleteLead) alert.success(message.deleteLead);
            if(message.addLead) alert.success(message.addLead);
            if(message.passwordNotMatch) alert.error(message.passwordNotMatch);
        }
    }
 
    render() {
        return <Fragment />;
    }
}
 
const mapStateToProps = state => ({
    error: state.errors,
    message: state.messages
});
 
export default connect(mapStateToProps)(withAlert(Alerts));
cs

/src/actions/auth.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
// /src/actions/auth.js
import axios from 'axios';
import { returnErrors } from './messages';
 
import {
    USER_LOADED,
    USER_LOADING,
    AUTH_ERROR,
    LOGIN_SUCCESS,
    LOGIN_FAIL,
    LOGOUT_SUCCESS,
    REGISTER_SUCCESS,
    REGISTER_FAIL
} from './typs';
 
// CHECK TOKEN & LOAD USER
export const loader = () => (dispatch, getState) => {
    // User Loading
    dispatch({ type: USER_LOADING });
    
    axios.get('/api/auth/user', tokenConfig(getState))
        .then(res => {
        dispatch({
            type: USER_LOADED,
            payload: res.data
        });
    }).catch(err => {
        dispatch(returnErrors(err.response.data,
        erro.response.state));
        dispatch({
            type: AUTH_ERROR
        });        
    });
};
 
// LOGIN USER
export const login = (username, password) => dispatch => {
 
    // Headers
    const config = {
        headers: {
            'Content-Type''application/json'
        }
    };
 
    // Request Body
    const body = JSON.stringify({ username, password });
 
    axios.post('/api/auth/login', body, config)
        .then(res => {
        dispatch({
            type: LOGIN_SUCCESS,
            payload: res.data
        });
    }).catch(err => {
        dispatch(returnErrors(err.response.data,
        erro.response.state));
        dispatch({
            type: LOGIN_FAIL
        });        
    });
};
 
// REGISTER USER
export const login = ({ username, password, email }) => dispatch => {
 
    // Headers
    const config = {
        headers: {
            'Content-Type''application/json'
        }
    };
 
    // Request Body
    const body = JSON.stringify({ username, email, password });
 
    axios.post('/api/auth/register', body, config)
        .then(res => {
        dispatch({
            type: REGISTER_SUCCESS,
            payload: res.data
        });
    }).catch(err => {
        dispatch(returnErrors(err.response.data,
        erro.response.state));
        dispatch({
            type: REGISTER_FAIL
        });        
    });
};
 
// LOGOUT USER
export const logout = () => (dispatch, getState) => {
    axios.post("/api/auth/logout/",null, tokenConfig(getState))
        .then(res => {
        dispatch({
            type: LOGOUT_SUCCESS
        });
    }).catch(err => {
        dispatch(returnErrors(err.response.data,
        erro.response.state));   
    });
};
 
 
// Setup config with token - helper function
export const tokenConfig = getState => {
        // Get token from state
    const token = getState().auth.token;
    // Headers
    const config = {
        headers: {
            'Content-Type''application/json'
        }
    }
    // If token, add to headers config
    if(token) {
        config.headers['Authorization'= `Toekn ${token}`;
    }    
    return config;
}
cs

/src/reducers/auth.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// /src/reducers/auth.js
import {
    USER_LOADING,
    USER_LOADING,
    AUTH_ERROR,
    LOGIN_SUCCESS,
    LOGIN_FAIL,
    LOGOUT_SUCCESS,
    REGISTER_SUCCESS,
    REGISTER_FAIL    
} from '../actions/types';
 
const initialState = {
    token: localStorage.getItem('token'),
    isAuthenticated: null,
    isLoading: false,
    user: null
}
 
export default function(state = initialState, action) {
    switch(action.type)    {
        case USER_LOADING:
            return {
                ...state,
                isLoading: true
            }
        case USER_LOADING:
            return {
                ...state,
                isAuthenticated: treu,
                isLoading: false,
                user: action.payload
            }
        case LOGIN_SUCCESS:
        case REGISTER_SUCCESS:
            localStorage.setItem("token", action.payload.token);
            return {
                ...state
                ...action.payload,
                isAuthenticated: true,
                isLoading: false
            };
 
        case AUTH_ERROR:
        case LOGIN_FAIL:
        case LOGOUT_SUCCESS:
        case REGISTER_FAIL:
            localStorage.removeItem("token");
            return {
                ...state,
                token: null,
                user: null,
                isAuthenticated: false,
                isLoading: false
            };
        default:
            return state;
    }
}
cs

/src/components/layout/Header.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// /src/components/layout/Header.js
import React, { Component } from "react";
import { Link } from "react-router-dom";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import { logout } from '../../actions/auth';
 
export class Header extends Component {
    static propTypes = {
        auth: PropTypes.object.isRequired,
        logout: PropTypes.func.isRequired
    };
 
 
render() {
    const { isAuthenticated, user } = this.props.auth;
 
    const authLinks = (
                        <ul className="navbar-nav mr-auto mt-2 mt-lg-0">
                            <span className="navbar-text mr-3">
                                <strong>
                                    {user ? `Welcom ${user.username}`: ""
                                </strong>
                            </span>
                            <li className="nav-item">
                                <button onClick={this.props.logout} 
className="nav-link btn btn-info btn-sm text-light">
                                    Logout
                                </button>
                            </li>
                           </ul>
 
    );
    
    const guestLinks = (
                        <ul className="navbar-nav mr-auto mt-2 mt-lg-0">
                            <li className="nav-item">
                                <Link to="/register" className="nav-link">Register</Link>
                            </li>
                            <li className="nav-item">
                                <Link to="/login" className="nav-link">Login</Link>
                            </li>
                           </ul>                        
    );
 
     return(
 
         <nav className="navbar navbar-expand-sm navbar-light bg-light">
            <div className="container">
                <button 
                    className="navbar-toggler" 
                    type="button" 
                    data-toggle="collapse" 
                    data-target="#navbarTogglerDemo01" 
                    aria-controls="navbarTogglerDemo01" 
                    aria-expanded="false" 
                    aria-label="Toggle navigation">
                          <span className="navbar-toggler-icon"></span>
                </button>
                <div className="collapse navbar-collapse" id="navbarTogglerDemo01">
                       <a className="navbar-brand" href="#">Lead Manager</a>
                  </div>
                {isAuthenticated ? authLinks : guestLinks}
            </div>
        </nav>
       );
   }
}
 
const mapStateToProps = state => ({
    auth: state.auth
});
 
export default connect(mapStateToProps, { logout })(Header);
  cs

- 마지막(7.4)으로 인증받은 계정으로 로그인 시에만 새로운 데이터를 생성하고 관리하는 것이 가능해 지도록 하는 작업을 할 차례입니다.

잠시 뒤에 뵐게요!

아스팔트 한판 하고 와야겠군요

반응형