본문 바로가기

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

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

반응형

7장 Frontend Authentication

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

이번 7.2 에선 지난 포스팅에 진행했던 로그인에 이어 로그아웃을 하도록 합니다.


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

- 먼저 UI를 보시면 로그아웃을 하는 링크는 보이지 않습니다.

- 로그인 상태라면 로그아웃 버튼이 있어야 하고, 로그아웃 상태라면 로그인 버튼을 제공해 줍니다.

- react redux와 proptypes를 가져옵니다.

- 로그아웃을 구현하기 위한 코드 수정 및 추가 작성을 진행합니다.

- 다음은 Register와 Login에 대해 작성되어 있던 <ul></ul> 의 코드를 모두 잘라냅니다.(ctrl + x)

- 그리고 그 잘라냈던 코드는 다음에 붙여줍니다.
- const guestLinks 를 새로 만들어 guest 즉, 로그인을 하지 않은 상태에 제공해줍니다.

- authLinks(로그인 상태)에 제공해주는 로그아웃 기능입니다. 

- 그리고 원래 <ul> 이 있던 자리엔 다음의 삼항연산자를 사용하여 작성합니다.

- 위의 삼항연산자를 설명하면 다음과 같습니다.
- isAuthenticated ? 는 인증이 되었는지를 묻는 것 입니다. 해당 조건에서 참(true)면 authLinks코드를 발동시키고, 거짓(false)이라면 guestLinks를 발동시킨다는 뜻 입니다.
- 삼항연산자는 주로 C언어 초보 단계에서 많이들 학습하는 내용입니다. 다음 줄에 관련 링크를 걸어두겠습니다.
- C 언어 코딩도장 20.2 삼항연산자 사용하기

- 그리고 새로고침을 하면 다음과 같이 인증을 받은 상태에선 로그아웃 버튼을 제공하는 결과를 얻습니다.


(6) src/actions/types.js

- Logout success 추가


(7) /src/actions/auth.js

- 방금까지는 로그아웃 UI를 만들었다면 이제는 실질적인 logout 동작을 부여해 줍니다.

- 다음의 코드를 auth.js 파일 가장 아랫줄에 작성해 줍니다.


(8) /src/reducers/auth.js

- Logout success 가져오기

- 로그아웃을 했을때도 발급받았던 토큰을 제거해 줍니다. 
- 그렇기 때문에 다음과 같이 기존에 auth,js 파일에 있던 코드에 가지고 왔던 LOGOUT_SUCCESS 도 추가해 줍니다.


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

- Header.js 파일에 해당 로그아웃 동작을 가지고 와 넣을 것 입니다.


(10) 다음 추가사항 알아두기

- 이번엔 인증받지 않은 계정 정보를 입력해 로그인을 시도할 때 사용자에게 Interface 상으로 오류 메세지를 제공도록 합니다.

- 다음은 인증받지 않은 정보를 입력해 로그인을 시도할 때 입니다. 
- Login 버튼을 눌러도 아무런 동작을 하지 않는 것으로 보입니다.

인증받지 않은 계정으로 로그인 시도

- Redux Tool로 살펴보면 다음과 같은 메세지가 발생했음을 알 수 있습니다.

- 하지만 일반 사용자 실제로 어떤 일이 일어나고 있는지 알 수 없습니다.
- 그렇기 때문에 이제 해당 에러 메세지를 UI에 출력해 주도록 합니다.


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

- 다음과 같이 non_field_errors 에 대한 코드를 추가 작성합니다.

- 로그인 오류 결과 테스트를 합니다.

- 위의 화면과 같이 인증받지 않은 계정 정보를 입력해 로그인을 시도하면 "Incorrect credentials" 라는 메세지를 받게 됩니다.


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

/src/actions/types.js

1
2
3
4
5
6
7
8
9
10
11
12
// /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";
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
// /src/actions/auth.js
import axios from 'axios';
import { returnErrors } from './messages';
 
import {
    USER_LOADED,
    USER_LOADING,
    AUTH_ERROR
} from './typs';
 
// CHECK TOKEN & LOAD USER
export const loader = () => (dispatch, getState) => {
    // User Loading
    dispatch({ type: USER_LOADING });
 
    // 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}`;
    }
    
    axios.get('/api/auth/user', config)
        .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
        });        
    });
};
 
// LOGOUT USER
export const logout = () => (dispatch, 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}`;
    }
    
    axios.post("/api/auth/logout/",null, config)
        .then(res => {
        dispatch({
            type: LOGOUT_SUCCESS
        });
    }).catch(err => {
        dispatch(returnErrors(err.response.data,
        erro.response.state));   
    });
};
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
// src/reducers/auth.js
import {
    USER_LOADING,
    USER_LOADING,
    AUTH_ERROR,
    LOGIN_SUCCESS,
    LOGIN_FAIL,
    LOGOUT_SUCCESS    
} 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:
            localStorage.setItem("token", action.payload.token);
            return {
                ...state
                ...action.payload,
                isAuthenticated: true,
                isLoading: false
            };
 
        case AUTH_ERROR:
        case LOGIN_FAIL:
        case LOGOUT_SUCCESS:
            localStorage.removeItem("token");
            return {
                ...state,
                token: null,
                user: null,
                isAuthenticated: false,
                isLoading: false
            };
        default:
            return state;
    }
}
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
// /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(message !== prevProps.message) {
            if(message.deleteLead) alert.success(message.deleteLead);
            if(message.addLead) alert.success(message.addLead);
        }
    }
 
    render() {
        return <Fragment />;
    }
}
 
const mapStateToProps = state => ({
    error: state.errors,
    message: state.messages
});
 
export default connect(mapStateToProps)(withAlert(Alerts));
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
// /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">
                            <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.3 으로 넘기려고 합니다.
- 지금까진 로그인, 로그아웃, 로그인 실패시 메세지 출력을 하였습니다.
- 이젠 로그인을 할 시에 환영합니다. 등의 메세지를 출력할 수 있도록 작업을 하는 것 부터 시작하려 합니다.

반응형