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.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(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)으로 인증받은 계정으로 로그인 시에만 새로운 데이터를 생성하고 관리하는 것이 가능해 지도록 하는 작업을 할 차례입니다.
잠시 뒤에 뵐게요!
아스팔트 한판 하고 와야겠군요