React에서 JWT(Json 웹 토큰)로 인증 - MERN 인증

49866 단어 webdevjavascriptreact
안녕하세요 여러분, 오늘 우리는 React에서 인증을 구현할 것입니다. 아름다운 양식을 만들고 API를 처리할 것입니다. 이 프로젝트의 백엔드는 이미 빌드되어 있으므로 아래 게시물에서 다운로드하십시오.

더 나은 이해를 위해 데모 비디오 및 지원 보기

Source code of this project

코딩을 시작하자...

React 앱 생성 및 의존성 설치

$    npx create-react-app react-auth-jwt
$    cd react-auth-jwt
$    npm install react-router-dom axios @material-ui/core jwt-decode
$    npm start


react-router-dom : 웹 앱에서 경로를 처리할 수 있는 도구
axios : 브라우저와 Node.js를 위한 약속 기반 HTTP 클라이언트입니다.
@material-ui/core (선택 사항) : 아름다운 UI 작성을 쉽게 해줍니다.
jwt-decode : JWT 토큰을 디코딩하는 데 도움이 됩니다.

환경 변수 구성
/.env

REACT_APP_API_URL = http://localhost:8080/api


인증 서비스 구성
/서비스/authServices.js

import axios from "axios";
import jwtDecode from "jwt-decode";
const apiUrl = process.env.REACT_APP_API_URL;

export function login(data) {
    return axios.post(`${apiUrl}/auth`, data);
}

export function getCurrentUser() {
    try {
        const token = localStorage.getItem("token");
        return jwtDecode(token);
    } catch (error) {
        return null;
    }
}

export function logout() {
    localStorage.removeItem("token");
}


사용자 서비스 구성
/서비스/userServices.js

import axios from "axios";
const apiUrl = process.env.REACT_APP_API_URL;

export function register(data) {
    return axios.post(`${apiUrl}/users`, data);
}


App.css

a {
    text-decoration: none;
}

.flex {
    display: flex;
    justify-content: center;
    align-items: center;
}

.column {
    flex-direction: column;
}

.full_screen {
    width: 100vw;
    height: 100vh;
}

.form {
    display: flex;
    flex-direction: column;
    width: 300px;
    padding: 20px;
}

.form_heading {
    font-size: 25px;
    font-weight: bold;
    text-align: center;
    margin-bottom: 15px;
}

.input {
    width: 100% !important;
    margin: 5px 0 !important;
}


홈 구성 요소
/src/components/Home.jsx

import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import { getCurrentUser } from "../services/authServices";
import { AppBar, Toolbar, Button } from "@material-ui/core";

const Home = () => {
    const [user, setUser] = useState("");

    useEffect(() => {
        setUser(getCurrentUser());
    }, []);

    return (
        <AppBar color="default">
            <Toolbar>
                <h3 style={{ flexGrow: "1" }}>Domain</h3>
                {!user && (
                    <React.Fragment>
                        <Link to="/login">
                            <Button
                                style={{ marginRight: "10px" }}
                                variant="outlined"
                                color="secondary"
                            >
                                Login
                            </Button>
                        </Link>
                        <Link to="/signup">
                            <Button variant="outlined" color="secondary">
                                Signup
                            </Button>
                        </Link>
                    </React.Fragment>
                )}
                {user && (
                    <React.Fragment>
                        <h4 style={{ marginRight: "15px" }}>{user.name}</h4>
                        <Link to="/logout">
                            <Button variant="outlined" color="secondary">
                                Logout
                            </Button>
                        </Link>
                    </React.Fragment>
                )}
            </Toolbar>
        </AppBar>
    );
};

export default Home;


입력 구성 요소
/src/components/common/Input.jsx

import React from "react";
import { TextField } from "@material-ui/core";

const Input = ({ error, ...rest }) => {
    return (
        <React.Fragment>
            {error ? (
                <TextField
                    {...rest}
                    error
                    helperText={error}
                    size="small"
                    variant="outlined"
                    className="input"
                />
            ) : (
                <TextField
                    {...rest}
                    variant="outlined"
                    size="small"
                    className="input"
                />
            )}
        </React.Fragment>
    );
};

export default Input;


양식 구성 요소
/src/components/common/Form.js

import React, { Component } from "react";
import { Button } from "@material-ui/core";
import Input from "./Input";

class Form extends Component {
    state = { data: {}, errors: {} };

    handleChange = ({ currentTarget: input }) => {
        const data = { ...this.state.data };
        data[input.name] = input.value;
        this.setState({ data });
    };

    handleSubmit = (event) => {
        event.preventDefault();
        this.doSubmit();
    };

    renderInput(name, label, type = "text", required = true) {
        const { data, errors } = this.state;
        return (
            <Input
                name={name}
                label={label}
                type={type}
                required={required}
                value={data[name]}
                error={errors[name]}
                onChange={this.handleChange}
            />
        );
    }

    renderSubmitBtn(name) {
        return (
            <Button
                type="submit"
                style={{ marginLeft: "auto" }}
                variant="outlined"
                size="medium"
                color="secondary"
            >
                {name}
            </Button>
        );
    }
}

export default Form;


가입 구성 요소
/src/components/Signup.jsx

import React from "react";
import { Link } from "react-router-dom";
import { register } from "../services/userServices";
import { Paper } from "@material-ui/core";
import Form from "./common/Form";

class Signup extends Form {
    state = { data: { name: "", email: "", password: "" }, errors: {} };

    doSubmit = async () => {
        try {
            await register(this.state.data);
            this.props.history.push("/login");
        } catch (error) {
            console.log(error);
        }
    };

    render() {
        return (
            <form
                onSubmit={this.handleSubmit}
                className="full_screen flex column"
            >
                <Paper elevation={3} className="form">
                    <div className="form_heading">Signup</div>
                    {this.renderInput("name", "Name")}
                    {this.renderInput("email", "Email", "email")}
                    {this.renderInput("password", "Password", "password")}
                    {this.renderSubmitBtn("Signup")}
                </Paper>
                <div style={{ margin: "10px 0" }}>
                    Already have an account? <Link to="/login">Login</Link>
                </div>
            </form>
        );
    }
}

export default Signup;


로그인 구성 요소
/src/components/Login.jsx

import React from "react";
import { Link } from "react-router-dom";
import { login } from "../services/authServices";
import { Paper } from "@material-ui/core";
import Form from "./common/Form";

class Login extends Form {
    state = { data: { email: "", password: "" }, errors: {} };

    doSubmit = async () => {
        try {
            const { data } = await login(this.state.data);
            window.localStorage.setItem("token", data);
            window.location = "/";
        } catch (error) {
            const errors = { ...this.state.errors };
            errors.email = error.response.data;
            errors.password = error.response.data;
            this.setState({ errors });
        }
    };

    render() {
        return (
            <form
                onSubmit={this.handleSubmit}
                className="full_screen flex column"
            >
                <Paper className="form" elevation={3}>
                    <div className="form_heading">Login</div>
                    {this.renderInput("email", "Email", "email")}
                    {this.renderInput("password", "Password", "password")}
                    {this.renderSubmitBtn("Login")}
                </Paper>
                <div style={{ margin: "10px 0" }}>
                    Don't have an account? <Link to="/signup">Signup</Link>
                </div>
            </form>
        );
    }
}

export default Login;


로그아웃 구성 요소
/src/components/Logout.jsx

import { useEffect } from "react";
import { logout } from "../services/authServices";

const Logout = () => {
    useEffect(() => {
        logout();
        window.location = "/";
    }, []);
    return null;
};

export default Logout;


라우터 돔 반응
Index.js에서

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import "./index.css";
import App from "./App";

ReactDOM.render(
    <React.StrictMode>
        <BrowserRouter>
            <App />
        </BrowserRouter>
    </React.StrictMode>,
    document.getElementById("root")
);


App.js에서

import {Switch, Route} from 'react-router-dom';
import Home from './components/Home';
import Signup from './components/Signup';
import Login from './components/Login';
import Logout from './components/Logout';
import "./App.css";

function App() {
    return (
        <Switch>
            <Route path="/" exact component={Home} />
            <Route path="/signup" component={Signup} />
            <Route path="/login" component={Login} />
            <Route path="/logout" component={Logout} />
        </Switch>
    );
}

export default App;


그게 다야 로컬 서버에서 실행하고 앱을 테스트하십시오. 실수를 발견했거나 코드를 개선한 경우 댓글로 알려주세요. 나는 당신이 무언가를 배웠기를 바랍니다.

고맙습니다...

좋은 웹페이지 즐겨찾기