이전에 서버를 했다면 이번엔 클라이언트쪽의 내용을 살펴보자.
이번에도 각각의 컴포넌트가 어떤일을 하고 있는지를 먼저 파악을 해보아야 할 것이다.
import React, { Component } from "react";
import Login from "./components/Login";
import Mypage from "./components/Mypage";
class App extends Component {
constructor(props) {
super(props);
this.state = {
isLogin: false,
accessToken: "",
};
this.loginHandler = this.loginHandler.bind(this);
this.issueAccessToken = this.issueAccessToken.bind(this);
}
loginHandler(data) {
}
issueAccessToken(token) {
}
render() {
const { isLogin} = this.state;
return (
<div className='App'>
{/*
로그인이 되었을 때와 안되었을 때를 구분하여 mypage를 구현하세요.
*/}
</div>
);
}
}
export default App;
제일먼제 App.js를 살펴보자. 현재 App.js를 보면 어떠한 역할을 하고 있을까?
setState를 보면 현재 isLogin와 accessToken을 가지고 업데이트 해주고 있다. 그리고 loginHandler와 issueAccessToken함수가 있는 것을 볼 수 있다. 그리고 render의 부분을 보면 로그인이 되었을 때와 로그인이 되지 않았을 경우로 Mypage를 나타나도록 해야한다.
그런데 생각해보니까 이상하다. 로그인은 로그인 컴포넌트에서 실행하여 들어오는 값일텐데 어떻게 App.js에서 그 값을 알고 Mypage로의 이동이 가능하게 되는 것일까?
Login 컴포넌트에서 데이터를 App.js로 끌어올려주어야 하는 것이다. Login이 되었을 때, state의 isLogin의 상태를 true로 변경해 Mypage의 컴포넌트를 불러오면 되겠다.
서버에서 login이되면 어떤 일이 일어 났을까? 유저는 유저의 아이디와 비밀번호를 입력하고, 그 데이터는 서버로 들어가게 된다. 서버에서는 데이터베이스에서 그 값과 일치하는 것이 있으면, AccessToken과 refreshToken을 만든다. AccessTken은 body에 담아서 보내주게 되고, refreshToken은 header에 담아서 보내주게 된다.
login컴포넌트에서는 위와 같은 일들이 일어나고, 그 결과로 나타나는 값을 App.js에서 관리하고 있어야하는 것이다. 그렇다면 해당 함수들에 어떠한 값들이 들어가야하는지 생각해보자.
loginHandler는 함수가 실행이 되면, isLogin의 상태와 AccessToken의 상태를 바꿔주어야 할 것이다. 로그인 실행시, AccessToken을 받아오기 때문에.
issueAccessToken은 로그인을 한 이후에 Mypage 에서 버튼을 누르면 AccessToken의 값을 다시 가지고와야할 함수인 것이다. 즉 AccessToken의 상태를 계속해서 변경해주는 함수인 것이다.
그렇다면 먼저, loginHandler함수를 먼저 채워보자.
loginHandler(data) {
this.setState({
isLogin : true
})
issueAcceessToken(data)
}
매개변수로 data가 들어오지만, login 컴포넌트에서 값이 어떻게 들어올지 모르니 data만 넣어주었다.
그 다음엔 issueAccessToken을 채워보자.
issueAccessToken(token){
this.setState({
accessToken : token
})
}
위처럼 매개변수인 token에는 서버에서 들어온 데이터인 AccessToken이 들어갈 것이다. 하여 그 값으로 변경해주기 위해 setState로 관리해주면 되겠다.
이후 각각의 컴포넌트가 어떻게 실행이 될지 설정해주어야 한다. render()라는 함수의 return의 값을 살펴보자.
render() {
const { isLogin} = this.state;
return (
<div className='App'>
{
isLogin ? <Mypage issueAccessToken={this.issueAccessToken}/>
: <Login loginHandler={this.loginHandler}/>
}
</div>
);
}
먼저 삼항 연산자를 통해서 Mypage 컴포넌트를 실행 시킬것인지 아닌지를 만들 수 있을 것이다. 또한 각각의 컴포넌트에 위에서 설명한 값들이 App.js로 데이터 끌어올리기가 수행되어야 하니, 함수들 또한 컴포넌트에 배치해주면 되겠다.
자, 이제 App.js에서는 어느정도의 구현이 된것 같다. 다음으로 login 컴포넌트를 살펴보자.
import axios from "axios";
import React, { Component } from "react";
class Login extends Component {
constructor(props) {
super(props);
this.state = {
userId: "",
password: ""
};
this.inputHandler = this.inputHandler.bind(this);
this.loginRequestHandler = this.loginRequestHandler.bind(this);
}
inputHandler(e) {
this.setState({ [e.target.name]: e.target.value });
}
loginRequestHandler() {
}
render() {
return (
<div className='loginContainer'>
<div className='inputField'>
<div>Username</div>
<input
name='userId'
onChange={(e) => this.inputHandler(e)}
value={this.state.userId}
type='text'
/>
</div>
<div className='inputField'>
<div>Password</div>
<input
name='password'
onChange={(e) => this.inputHandler(e)}
value={this.state.password}
type='password'
/>
</div>
<div className='loginBtnContainer'>
<button onClick={this.loginRequestHandler} className='loginBtn'>
JWT Login
</button>
</div>
</div>
);
}
}
export default Login;
Login 컴포넌트를 살펴보면 가장 먼저 눈에 띄는 것이 있다. inputHandler와 loginRequestHandler함수이다. inputHandler 현재 input에서 사용자가 입력하는 id와 password를 받고 있고 그 값을 state에 저장하여 관리하고 있는 것이다.
그렇다면 loginRequestHandler에서는 어떠한 값들을 처리해주어야 할까?
위에서 state로 id와 password로 저장된 값을 서버에게 보내주고 해당데이터를 잘 받아온다면, response로 AccessToken과 RefreshToken을 받게 될 것이다. 그렇다면 App.js에서 내려준 props를 통해서 accessToken을 state에 저장해주도록 하면 되겠다.
RefreshToken은 State에서 따로 저장되지 않는다. header에 cookie에 담아 들어오기 때문에 이 값은 브라우져에서 자동으로 저장되는 값이 될 것이기 때문에다. 우리가 해주어야 할것은 cookie를 받을 때의 설정만 해주면 될 것이다.
async loginRequestHandler() {
const result = await axios({
url : "https://localhost:4000/login",
method : "post",
headers : {"content-type":"application/json"},
data :{
userId : this.state.userId,
password : this.state.password
},
withCredentials : true
})
}
위와 같이 async await으로 비동기 함수를 처리하였다. 이 부분에서 try와 catch로 한번 더 감싸 오류를 방지할 수 있게 될 것이다.
자 그렇다면 result에 post의 결과들이 담겨져서 들어올 것이다. 어떠한 값들이 들어오는지 확인하기 위해서 console을 찍어보자.
commend
console.log(reuslt)
위와 같이 console을 찍으니 결과 값이 엄청나게 많은 값들이 쏟아져 나오는 것을 볼 수 있다. 하지만 코드스테이츠에서 우리에게 값이 나오는 것을 한 번에 잘 보일 수 있도록 맨 밑에 data의 객체가 보이도록 해두고 있다.
(사실, axios로 해당 값을 body에서 가지고 오는것이기 때문에 data안에서 찾아야하는것일 수도 있다.)
그렇다면 result.data로 다시 console을 찍어보자.
commend
console.log(reuslt.data)
result
{ data: { accessToken: 'fakeAccessToken' } }
위와 같이 accessToken을 잘 받아오는 걸 볼 수 있다. 하여, 이 accessToken을 App.js로 데이터를 끌어올리기 위해 props로 받았던 함수의 인자에 넣어줘 state의 값을 변경하고 isLogin의 상태를 변경 해보자.
this.props.loginHandler(result.data.data)
위와 같이 props를 설정해주면, App.js에서 받는 매개변수에도 위와 같이 들어갈테니, 바로 accessToekn에 접근 할 수 있도록 값을 정해주어야 한다.
loginHandler(data){
setState({
iLogin : true
})
this.issueAccessToken(data.accessToken)
}
위와 같이 data에 accessToken의 값을 바로 볼 수 있도록 하여 state를 변경하는데 문제가 없도록 한다.
이제 login.js는 마무리가 되어간다. 혹시모를 에러를 방지 하기 위하여 try catch를 이용해주면 되겠다.
async loginRequestHandler() {
try{
const result = await axios({
url : "https://localhost:4000/login",
method : "post",
headers : {"content-type":"application/json"},
data :{
userId : this.state.userId,
password : this.state.password
},
withCredentials : true
})
this.props.loginHandler(result.data.data)
}catch(err){
console.lig(err)
}
}
위에서 말했지만 login이 성공하게 되면 서버에서는 AccessToken과 RefreshToken을 전해주게 된다. RefreshToken은 쿠키에 담아져오며, 브라우져에 저장 될 것이다. 이러한 설정을 위해 axios를 사용할 때, withCredentials를 설정해 준것을 볼 수 있을 것이다. 해당 값이 false일 경우 쿠키를 받아올 수 없으므로 꼭 true로 설정하여 브라우져에 cookie가 잘 담아와질 수 있도록 하자.
import axios from "axios";
import React, { Component } from "react";
class Login extends Component {
constructor(props) {
super(props);
this.state = {
userId: "",
password: ""
};
this.inputHandler = this.inputHandler.bind(this);
this.loginRequestHandler = this.loginRequestHandler.bind(this);
}
inputHandler(e) {
this.setState({ [e.target.name]: e.target.value });
}
async loginRequestHandler() {
/*
TODO: Login 컴포넌트가 가지고 있는 state를 이용해 로그인을 구현합니다.
로그인을 담당하는 api endpoint에 요청을 보내고, 받은 데이터로 상위 컴포넌트 App의 state를 변경하세요.
초기 App:
state = { isLogin: false, accessToken: "" }
로그인 요청 후 App:
state = { isLogin: true, accessToken: 서버에_요청하여_받은_access_token }
*/
try{
const result = await axios({
url : "https://localhost:4000/login",
method : "post",
data :{
userId : this.state.userId,
password : this.state.password
},
headers : { "Content-Type": "application/json"},
withCredentials : true
},)
// console.log(result)
this.props.loginHandler(result.data.data)
console.log(result)
}catch(err){
console.log(err)
}
}
render() {
return (
<div className='loginContainer'>
<div className='inputField'>
<div>Username</div>
<input
name='userId'
onChange={(e) => this.inputHandler(e)}
value={this.state.userId}
type='text'
/>
</div>
<div className='inputField'>
<div>Password</div>
<input
name='password'
onChange={(e) => this.inputHandler(e)}
value={this.state.password}
type='password'
/>
</div>
<div className='loginBtnContainer'>
<button onClick={this.loginRequestHandler} className='loginBtn'>
JWT Login
</button>
</div>
</div>
);
}
}
export default Login;
이제 클라이언트의 마지막은 Mypage를 설정해보자.
import axios from "axios";
import React, { Component } from "react";
class Mypage extends Component {
constructor(props) {
super(props);
this.state = {
userId: "",
email: "",
createdAt: "",
};
this.accessTokenRequest = this.accessTokenRequest.bind(this);
this.refreshTokenRequest = this.refreshTokenRequest.bind(this);
}
accessTokenRequest() {
}
refreshTokenRequest() {
}
render() {
const { userId, email, createdAt } = this.state;
return (
<div className='mypageContainer'>
<div className='title'>Mypage</div>
<hr />
<br />
<br />
<div>
안녕하세요. <span className='name'>{userId ? userId : "Guest"}</span>님! jwt 로그인이
완료되었습니다.
</div>
<br />
<br />
<div className='item'>
<span className='item'>나의 이메일: </span> {email}
</div>
<div className='item'>
<span className='item'>나의 아이디 생성일: </span> {createdAt}
</div>
<br />
<br />
<div className='btnContainer'>
<button className='tokenBtn red' onClick={this.accessTokenRequest}>
access token request
</button>
<button className='tokenBtn navy' onClick={this.refreshTokenRequest}>
refresh token request
</button>
</div>
</div>
);
}
}
export default Mypage;
앞에서 했던것 처럼 먼저 각각의 함수들이 어떠한 역할을 하는지 생각해보자.
render함수를 보면, 2개의 버튼이 있고 이 버튼을 누르면 해당 함수가 실행되는 것이다. accessToken함수를 눌렀을 경우, 로그인한 유저가 가지고 있는 userId, email, createdAt을 가지고 와야한다. 그렇다면, login에서 구현한것 처럼 axios로 해당 데이터를 서버에 요청하여 가지고 오면 되겠다.
async accessTokenRequest() {
const result = axios({
url : 'https://localhost:4000/accesstokenrequest',
method : "get",
withCredentials : true,
headers : {
"content-type" : "applycation/json"
}
})
}
맨 처음엔 위와 같이 작성하였었다. 하지만 예상과 다르게, 요청이 들어가지 않았었다.
서버에서 데이터를 보내줄 경우에는 클라이언트에서 로그인을 한 후, 얻은 토큰을 기반으로 서버에서 마패와 같은 통행증을 받을 수 있게 되는 것이다. 현재 위의 코드를 보면, AccessToken이 들어가지 않은 것을 알 수 있다.
스프린트에서는 AccessToken은 headers로 전달이 되고, RefreshToken은 쿠키에 저장이 되어있으니, 해당 “get”요청을 보낼때, headers에 AccessToken을 담아 보내주면 되겠다. 현재 AccessToken은 state로 관리되고 있기 때문에 App.js에서 해당 Token을 받아와야한다. 먼저 props로 Mypage에 accessToken을 넘겨주자.
render() {
const { isLogin, accessToken } = this.state;
return (
<div className='App'>
{ isLogin ?
<Mypage accessToken={accessToken} issueAccessToken={this.issueAccessToken} />
:
<Login loginHandler={this.loginHandler}/>
}
</div>
);
}
App.js에서 위와 같이 state로 관리되고 있는 accessToken을 넘겨주었다. 이제, 서버로의 요청에 header에 담아 보내주면 되겠다.
async accessTokenRequest() {
const result = axios({
url : 'https://localhost:4000/accesstokenrequest',
method : "get",
withCredentials : true,
headers : {
"content-type" : "applycation/json",
accessToken : this.props.accessToken
}
})
}
위와 같은 명령어를 입력하였는도 서버에서 내가 원하는데이터를 못가져 오고 있다. 무엇이 문제일까?
참조
How to send the authorization header using Axios
[#. React] React에서 token이 필요한 API 요청 시 header Authorization에 Bearer token 담아서 보내기
위의 참조를 보면 알 수 있겠다. 그렇다 내가 계속 만지고 있는 accessToken이 말썽인것이다. accessToken이라는 키는 없고, header에 존재하는 authorization이라는 키에 해당 accessToken을 넣으면 되는 것이다.
headers : {
"content-type" : "application/json",
authorization : this.props.accessToken
}
하여 위의 방법을 사용하였는데도, 해당 값이 불러와지지 않았다. 뭐가 문제일까.
참조
Axios로 get할 때 headers에 어떤 양식으로 넣어야 할지 모르겠습니다
위의 참조를 이용하면 authorization에는 토근이 들어갈 때, 아래와 같은 형식으로 Template literals을 사용하여 accessToken을 넣어주는것이라고 한다.
headers : {
"content-type" : "application/json",
authorization : `Bearer ${this.props.accessToken}`
}
async accessTokenRequest() {
const result = axios({
url : 'https://localhost:4000/accesstokenrequest',
method : "get",
withCredentials : true,
headers : {
"content-type" : "applycation/json",
authorization : `Bearer ${this.props.accessToken}`
}
})
}
위와 같은 코드가 만들어졌다. 하면 현재 result가 잘 받아오고 있는지 확인해보자.
commend
console.log(result.data)
result
{
data: { userInfo: { createdAt: '2020', userId: '1', email: '2' } },
message: 'ok'
}
위의 데이터를 기반으로 state를 갱신해주면 되겠다.
const {userId, email, createdAt} = result.data.data.userInfo
this.setState({
userId,
email,
createdAt
})
accessTokenRequest가 마무리되었으니 refreshTokenRequest 함수를 채워넣어보자. 현재 이 함수는 refreshToken이 살아있다면 이 token으로 accessToken을 받아오는 함수가 되겠다.
const result = await axios({
url : "https://localhost:4000/refreshtokenrequest",
method : "GET",
headers : {
"Content-Type": "application/json",
},
withCredentials : true
})
앞서 accessToke으로 값을 받아온 것과 비슷하게 이번에는 route를 refreshTokenrequest로 설정하고 accessToken을 받을 수 있는 요청을 보낸다.
commend
console.log(result.data)
result
{
data: {
userInfo: { createdAt: '2020', userId: '1', email: '2' },
accessToken: 'new access token'
},
message: 'ok'
}
console로 찍어본 결과도 잘 받아오는 것을 볼 수 있다.
const {createdAt, userId, email} = result.data.data.userInfo
this.setState({userId, email, createdAt});
this.props.issueAccessToken(result.data.data.accessToken);
위의 console의 결과를 토대로 state와 props의 상태를 새롭게 바꾸어 주면되겠다.
import axios from "axios";
import React, { Component } from "react";
class Mypage extends Component {
constructor(props) {
super(props);
this.state = {
userId: "",
email: "",
createdAt: "",
};
this.accessTokenRequest = this.accessTokenRequest.bind(this);
this.refreshTokenRequest = this.refreshTokenRequest.bind(this);
}
async accessTokenRequest() {
try{
const result = await axios({
url : "https://localhost:4000/accesstokenrequest",
method: "get",
headers : {
"Content-Type": "application/json",
authorization : `Bearer ${this.props.accessToken}`,
withCredentials : true
// ! token은 header에 담아서 보내야 하며, header에는 authorization이라는 항목이 있음. 해당 항목은 위처럼 채워져야 함.
}}
)
if(result.data.message !== 'ok'){
return this.setState({
email : "access token이 만료되어 불러올 수 없습니다. refresh token을 사용해주시기 바랍니다.",
createdAt : "access token이 만료되어 불러올 수 없습니다. refresh token을 사용해주시기 바랍니다."
})
}
// console.log(result.data.data.userInfo)
const {createdAt, userId, email} = result.data.data.userInfo
this.setState({
userId,
email,
createdAt
})
}catch(err){
console.log(err)
}
}
async refreshTokenRequest() {
/*
TODO: 쿠키에 담겨져 있는 refreshToken을 이용하여 refreshTokenRequest 메소드를 구현합니다.
refresh token을 처리할 수 있는 api endpoint에 요청을 보내고, 받은 데이터로 2가지를 구현합니다.
1. Mypage 컴포넌트의 state(userId, email, createdAt)를 변경
2. 상위 컴포넌트 App의 state에 accessToken을 받은 새 토큰으로 교환
*/
try{
const result = await axios({
url : "https://localhost:4000/refreshtokenrequest",
method : "GET",
headers : {
"Content-Type": "application/json"
},
withCredentials : true
})
const {createdAt, userId, email} = result.data.data.userInfo
this.setState({
userId,
email,
createdAt
});
this.props.issueAccessToken(result.data.data.accessToken);
}catch(e){
console.log(e)
}
}
render() {
const { userId, email, createdAt } = this.state;
return (
<div className='mypageContainer'>
<div className='title'>Mypage</div>
<hr />
<br />
<br />
<div>
안녕하세요. <span className='name'>{userId ? userId : "Guest"}</span>님! jwt 로그인이
완료되었습니다.
</div>
<br />
<br />
<div className='item'>
<span className='item'>나의 이메일: </span> {email}
</div>
<div className='item'>
<span className='item'>나의 아이디 생성일: </span> {createdAt}
</div>
<br />
<br />
<div className='btnContainer'>
<button className='tokenBtn red' onClick={this.accessTokenRequest}>
access token request
</button>
<button className='tokenBtn navy' onClick={this.refreshTokenRequest}>
refresh token request
</button>
</div>
</div>
);
}
}
export default Mypage;