Login-Signup_React
react-typescript-login-example
비교적 최근인 2022년 6월 자료임을 확인하여 위 자료를 참고하여, 타입 스크립트 기반의 로그인 기능 구현을 익히기 위해 클론 프로젝트를 진행하였습니다.
전체 프로젝트의 컴포넌트 다이어그램은 아래와 같습니다.
App
: 리액트 라우터 () 컨테이너 컴포넌트로, state에 따라 navbar가 해당하는 아이템을 디스플레이한다.Login
& Register
: react상에서 auth.service를 호출한다. formik
과 yup
라이브러리를 사용한다.auth.service
: auth.service는 axios
를 통해 HTTP request를 만든다. 또한 Browser의 Local Storage로 부터 JWT토큰을 get한다.Home
: 모든 사용자가 접근가능한 public 컴포넌트Profile
: login이 성공적이라면, 해당 유저의 정보를 보여준다BoardUser
, BoardModerator
, BoardAdmin
: 컴포넌트들은 user.roles
state에 따라서 다른 UI와 정보를 디스플레이한다.user.service
: user.service는 auth-header()
라는 helper function을 이용해서 JWT를 HTTP header에 더할 수 있다. auth-header()
는 현재 유저의 Local Storage로부터 로그인한 유저의 JWT를 포함한 오브젝트를 리턴한다.💡 사용 모듈
React 17/16
typescript 4.3.5
react-router-dom 5.2.0
axios 0.72.2
formik 2.2.9
Bootstrap 4
yup 0.32.9
bash/ zsh 터미널에서 원하는 directory에서 아래 명령어를 통해 타입스크립트 프로젝트 생성한다.
npx create react-app react-typescript-login-example --template typescript
→ 여기서 ‘react-typescript-login-example’은 프로젝트 이름이므로, 자신이 원하는 이름을 지정해도 좋다.
이후 프로젝트 폴더에서 Bootstrap을 import하고, 프로젝트의 src/App.tsx 파일을 열어 코드를 수정한다.
npm install bootstrap@4.6.0
yarn add bootstrap@4.6.0
이제, App.tsx파일에서 부트스트랩을 import한다. import "bootstrap/dist/css/bootstrap.min.css"
함수형 선언과 클래스 선언 중 어떤 방식으로 프로젝트를 진행하느냐에 따라 메인 코드 부분은 다르게 작성할 수 있다.
import { Component } from "react";
import "bootstrap/dist/css/bootstrap.min.css";
class App extends Component<Props, State> {
render() {
// ...
}
}
export default App;
react-router-dom의 BrowserRouter를 사용할 것이므로 먼저 react-router-dom을 프로젝트에 설치해준다.
npm install react-router-dom @types/react-router-dom
yarn add react-router-dom @types/react-router-dom
여기까지 완료하였다면, 이제 src/index.tsx 파일에서 아래 코드와 같이 App 컴포넌트를 로 감싸준다.
import ReactDOM from 'react-dom';
import { BrowserRouter } from "react-router-dom";
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
);
reportWebVitals();
이제 프로젝트 내에 필요한 두가지 서비스를 위해 코드를 작성해봅시다
src폴더 하위에 ‘services’폴더를 만들고, ‘auth-header.ts’, ‘auth.service.ts’, ‘user.service.ts’파일을 생성합니다. 각각의 파일이 제공하는 서비스는 아래와 같습니다.
먼저, Authentication service를 제공하는 auth.service.ts 작성을 배워봅시다.
Authentication service는 Local Storage에 저장된 유저 정보와 JWT를 위해 Axios를 이용해 HTTP에 request를 사용합니다. 아래와 같은 method들을 사용합니다.
login()
: POST {username, password} 작동과 함께, 로컬 스토리지에 JWT를 저장합니다.logout()
: 로컬 스토리지로부터 JWT를 제거합니다register()
: POST {username, password, email}getCurrentUser()
: 스토리지에 저장된 JWT를 포함한 유저 정보를 get 해옵니다.각 method들을 선언한 class AuthService의 코드입니다.
import axios from "axios";
// 로그인 api 주소
const API_URL = "http://localhost:8080/api/auth/";
class AuthService {
//로컬스토리지에 유저 추가
login(username: string, password: string){
return axios
.post(API_URL + "signin",{
username,
password
})
.then(response =>{
//response 응답에 accessToken이 존재하면
if(response.data.accessToken) {
localStorage.setItem("user", JSON.stringify(response.data));
}
return response.data
});
}
//로컬스토리지에서 유저삭제
logout(){
localStorage.removeItem("user");
}
//API 서버에 signup POST
register(username: string, phonenumber: string, password:string){
return axios.post(API_URL + "signup", {
username,
phonenumber,
password
});
}
//로컬스토리지에 저장된 유저 확인해서 객체로 불러옴
getCurrentUser(){
const userStr = localStorage.getItem("user");
if (userStr) return JSON.parse(userStr);
return null;
}
}
export default new AuthService();
서버로부터 데이터를 받기위한 메소드또한 존재합니다. Protected resource들에 접근하기 위해선, HTTP request에 추가적으로 Authorization header가 필요합니다. 따라서, auth-header.ts 파일에 아래 코드와 같이 authHeader()
helper function을 선언합니다.
→ auth-header.ts
export default function authHeader() {
const userStr = localStorage.getItem("user");
let user = null;
// 로컬스토리지에 유저가 저장되어 있는지 검사
if (userStr)
user = JSON.parse(userStr);
//이미 로그인 한 경우
if (user && user.accessToken){
return {Authorization: 'Bearer' + user.accessToken}; // HTTP Authorization Header를 반환
} else {
return {Authorization: ''}; // 빈 오브젝트 반환
}
}
authHeader()의 동작은 단순하다. 아래 이미지는 아임웹에서 어떻게 JWT를 사용하는지에 대한 API 문서 중 일부이다.
user
아이템을 검사한다. accssToken
(JWT)을 가지고 로그인한 user
가 있다면, HTTP Authorization header를 반환한다. 여기서, ‘Bearer’는 REST API의 인증 방식 중 하나이다. 알파벳 첫글자 ‘B’를 대문자로 작성함에 주의하자.export default function authHeader() {
const userStr = localStorage.getItem("user");
let user = null;
if (userStr)
user = JSON.parse(userStr);
if (user && user.accessToken) {
// return { Authorization: 'Bearer ' + user.accessToken }; // for Spring Boot back-end
return { 'x-access-token': user.accessToken }; // for Node.js Express back-end
} else {
// return { Authorization: '' }; // for Spring Boot back-end
return { 'x-access-token': null }; // for Node Express back-end
}
}
액세스 토큰 사용에 대한 내용은 아래를 참고하면 자세히 나와있다.
Managing access tokens, bearer tokens, access_token, refresh_token - Machine Learning Server
여기까지 진행했다면 마지막으로 서버로부터 데이터를 받아오는 user.service.ts 파일을 작성해보자. 이 스크립트에서는 유저별 authorized resource 요청을 위해 authHelper()
헬퍼 함수로 HTTP 헤더를 추가한다.
import axios from "axios";
import authHeader from "./auth-header";
const API_URL= "http://localhost:8080/api/test/";
class UserService{
getPublicContent(){
return axios.get(API_URL + 'all');
}
// Autorizzation 여부와 JWT 반환해 headers에 쓰고, 해당하는 유저 data를 GET
getUserBoard(){
return axios.get(API_URL + 'user', {
headers: authHeader()
});
}
getModeratorBoard(){
return axios.get(API_URL + 'mod', {
headers: authHeader()
});
}
getAdminBoard(){
return axios.get(API_URL + 'admin', {
headers: authHeader()
});
}
}
export default new UserService();
src 폴더에서, components라는 이름의 새로운 폴더를 생성하고 다음 파일을 추가한다.
타입스크립트를 사용한 리액트 프로젝트의 간편한 Form Validation을 위해서 formik과 yup 라이브러리를 프로젝트에 추가한다.
npm install formik yup
yarn add formik yup
formik은 위 예제와 같이, Form 태그안에 type이 지정되어 편하게 쓸 수 있다.
남은 클론 프로젝트는 다음에 계속..