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.name) alert.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 으로 넘기려고 합니다.
- 지금까진 로그인, 로그아웃, 로그인 실패시 메세지 출력을 하였습니다.
- 이젠 로그인을 할 시에 환영합니다. 등의 메세지를 출력할 수 있도록 작업을 하는 것 부터 시작하려 합니다.