본문 바로가기
javascript

React + RESTful API로 이메일 인증 구현

by it-square 2022. 1. 19.
반응형

안녕하세요! 이 튜토리얼에서는 전자 메일 + 암호에 의존하는 인증 시스템을 설정하는 방법에 대해 알아보겠습니다. 프런트엔 리액트, 백엔드엔 Node.js를 사용할 예정입니다.

지원되는 워크플로우는 다음과 같습니다.

가입:

  • 사용자가 로그인에 사용할 자격 증명을 제출합니다.
  • 사용자가 전자 메일을 확인합니다.
  • 이제 로그인할 수 있습니다.

로그인:

 
  • 사용자가 전자 메일/암호 제출
  • 사용자가 존재하는 경우 세션이 작성되고 기본 정보가 표시된 페이지로 이동합니다.
  • 사용자가 페이지를 새로 고칠 수 있으며 여전히 로그인되어 있습니다.

로그아웃:

  • 사용자가 세션을 취소하여 로그아웃을 요청할 수 있습니다.

우리의 디렉토리를 설정하자.

다음을 실행합니다.

 
mkdir email_auth_tutorial && cd email_auth_tutorial

프런트엔드 코드를 위한 디렉토리와 백엔드 코드를 위한 디렉토리를 만들어야 합니다. 프런트 엔드 코드부터 시작하죠 실행:

npx create-react-app email_auth_tutorial_frontend --template typescript

보다시피, 우리는 이 예제의 타이프스크립트로 작업할 것입니다. Typescript에 대해 잘 모르더라도 걱정하지 마십시오. 저희도 간단히 언급하겠습니다. 내가 데이터 타입을 실수할 때 컴파일러가 문제를 빠르게 해결할 수 있도록 충분한 정보를 알려주기 때문에 나는 Typoscript를 즐겨 사용한다.

백엔드 디렉터리를 추가합니다. 실행

 
mkdir email_auth_tutorial_backend

프런트엔드

이제 프런트 엔드에 유저 인터페이스를 추가하겠습니다.

프런트엔드 디렉토리로 이동하여 서버를 시작합니다.

cd email_auth_tutorial_frontend && npm start
 

참고로, VSCode를 사용하고 있으며 설정은 다음과 같습니다.

우리가 놓치고 있는 유일한 패키지 의존성은 리액트-라우터-돔입니다. 라우팅할 때 그게 필요해 별도의 터미널에서 다음을 실행합니다.

npm i --save react-router-dom

우리가 해야 할 첫 번째 일은 리액트 라우터 시스템을 우리의 앱에 통합하는 것입니다. 다음과 같이 보이도록 index.tsx 파일을 업데이트하십시오.

 
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
ReactDOM.render(
    <React.StrictMode>
      <BrowserRouter>
        <Routes>
          <Route path="/" element={<App />} />
      </Routes>
    </BrowserRouter>
  </React.StrictMode>,
  document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

보시다시피 아직 새로운 경로가 추가되지 않았습니다. 회원가입과 로그인 페이지에는 조금 있다가 필요할 것 같아요.

이제 App.tsx를 다음과 같이 업데이트하겠습니다.

import React from 'react';
import './App.css';
import { Link } from 'react-router-dom';
function App() {
    return (
          <div>
            <div>
              <div>
                <Link to="/login">Log in</Link>
              </div>
              <div>
                <Link to="/signup">Sign up</Link>
              </div>
              <div>
                You are not logged in.
              </div>
            </div>
          </div>
        );
}
export default App;

이 튜토리얼에서 우리는 스타일에 대해 신경쓰지 않는다. 보시다시피, 이제 로그인 및 가입 페이지로 이동할 수 있는 링크가 있습니다. 또한 로그인하지 않았다는 플레이스홀더를 표시합니다.

 

해당 링크 중 하나를 클릭하면 빈 페이지가 표시됩니다. 아직 그 노선들을 세우지 못했기 때문입니다. src 디렉터리에 4개의 새 파일을 생성합니다.

  • 로그인.tsx
  • 로그인.tsx
  • CheckYourEmail.tsx
  • VerifyEmail.tsx

다음 텍스트를 Login.tsx에 추가합니다.

import React from 'react';
function Login() {
    return (
          <div>
            log in
          </div>
        );
}
export default Login;

Signup.tsx에 다음과 같이 문의합니다.

 
import React from 'react';
function Signup() {
    return (
          <div>
            sign up
          </div>
        );
}
export default Signup;
  • CheckYourEmail.tsx:
import React from 'react';
function CheckYourEmail() {
    return (
          <div>
            Please check your email for a verification link.
          </div>
        );
}
export default CheckYourEmail;

VerifyEmail.tsx:

import React from 'react';
function VerifyEmail() {
    return (
          <div>
            Verifying...
          </div>
        );
}
export default VerifyEmail;
 

페이지가 작동하는지 확인하기 위해 최소한의 코드를 추가합니다. index.tsx로 돌아가서 적절한 경로를 추가하십시오. 이제 다음과 같이 표시됩니다.

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import Login from './Login';
import Signup from './Signup';
import CheckYourEmail from './CheckYourEmail';
import VerifyEmail from './VerifyEmail';
ReactDOM.render(
    <React.StrictMode>
      <BrowserRouter>
        <Routes>
          <Route path="/login" element={<Login />} />
        <Route path="/signup" element={<Signup />} />
                  <Route path="/check-your-email" element={<CheckYourEmail />} />
                            <Route path="/verify-email" element={<VerifyEmail />} />
                                      <Route path="/" element={<App />} />
                                              </Routes>
    </BrowserRouter>
  </React.StrictMode>,
  document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

다음 경로가 렌더링하는지 확인합니다.

인터페이스 등록:

 

당사 가입 페이지에는 다음과 같은 특성이 필요합니다.

  • 홈 또는 로그인 페이지로 라우팅할 수 있는 기능
  • 이름, 이메일, 비밀번호를 통한 가입 기능
  • 사용자의 입력을 확인하는 기능
  • 사용자의 입력을 백엔드에 제출하는 버튼

저는 계속해서 기본 페이지를 작성했습니다.

import React from "react";
import { Link, useNavigate } from "react-router-dom";
function SignUp() {
  const navigate = useNavigate();
  const [formState, setFormState] = React.useState({
        username: "",
        email: "",
        password: ""
  })
  const onChange = (e: React.ChangeEvent<HTMLInputElement>, type: string) => {
        e.preventDefault();
        setFormState({
                ...formState,
                [type]: e.target.value
        })
  }
  const getErrors = (): string[] => {
        const errors = [];
        if (!formState.username) errors.push("Name required");
        if (!formState.email) {
                errors.push("Email required");
        } else if (!/^\S+@\S+\.\S+$/.test(formState.email)) {
                errors.push("Invalid email");
        }
        if (!formState.password) errors.push("Password required");
        return errors;
  }
  const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();
        const errors = getErrors();
        for (let error of errors) {
                alert(error);
        }
        if (errors.length) return;
        const response = await fetch('http://localhost:8000/api/v1/signup', {
                method: "post",
                headers: {
                          // needed so express parser says the body is OK to read
                          'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                          username: formState.username,
                          email: formState.email,
                          password: formState.password
                })
        })
            if (response.status !== 200) {
                    // TODO: Add more detailed error handling.
                    return alert("Something went wrong.");
            }
        navigate("/check-your-email");
  }
  return (
        <div>
          <h1>Sign up</h1>
          <form onSubmit={onSubmit}>
            <div>
              <label>Name</label>
          <input onChange={(e) => onChange(e, "username")} type="text" value={formState.username} />
                    </div>
        <div>
                      <label>Email</label>
          <input onChange={(e) => onChange(e, "email")} type="email" value={formState.email} />
                    </div>
        <div>
                      <label>Password</label>
          <input onChange={(e) => onChange(e, "password")} type="password" value={formState.password} />
                    </div>
        <button type="submit">Submit</button>
      </form>
      <div>
                  <Link to="/login">Already have an account? Log in</Link>
      </div>
      <div>
                            <Link to="/">Back to home</Link>
                        </div>
                      </div>
                    );
}
export default SignUp;

저는 당신이 이 앱 이상으로 확장한다면 좀 더 자세한 에러 처리를 추가하는 것을 강력히 추천합니다.

 

그것을 Signup.tsx 파일에 추가하고 브라우저에서 그것을 가지고 놀아라. 그리고 코드를 살펴보세요. 모두 꽤 상용구야. 가장 흥미로운 코드는 onSubmit 함수에 있습니다. 보시다시피 현재 존재하지 않는 엔드포인트에 함수 호출을 하고 있습니다.

다음으로 Login.tsx 파일을 업데이트하겠습니다.

import React from "react";
import { Link, useNavigate } from "react-router-dom";
function Login() {
  const navigate = useNavigate();
  const [formState, setFormState] = React.useState({
        email: "",
        password: ""
  })

    const onChange = (e: React.ChangeEvent<HTMLInputElement>, type: string) => {
          e.preventDefault();
          setFormState({
                  ...formState,
                  [type]: e.target.value
          })
    }
    const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
          e.preventDefault();
          const response = await fetch('http://localhost:8000/api/v1/login', {
                  method: "post",
                  credentials: "include",
                  headers: {
                            // needed so express parser says OK to read
                            'Content-Type': 'application/json'
                  },
                  body: JSON.stringify({
                            username: formState.email,
                            email: formState.email,
                            password: formState.password
                  })
          })
              if (response.status !== 200) {
                      return alert("Something went wrong");
              }
          navigate("/");
    }
    return (
          <div>
            <h1>Log in</h1>
            <form onSubmit={onSubmit}>
              <div>
                <label>Email</label>
          <input onChange={(e) => onChange(e, "email")} type="email" value={formState.email} />
                    </div>
        <div>
                      <label>Password</label>
          <input onChange={(e) => onChange(e, "password")} type="password" value={formState.password} />
                    </div>
        <button>Submit</button>
      </form>
      <div>
                    <Link to="/signup">Don't have an account? Sign up</Link>
      </div>
      <div>
                              <Link to="/">Back to home</Link>
      </div>
    </div>
  );
}
export default Login;

다시 한 번 말씀드리지만, 상당히 보일러 플레이트입니다. onSubmit 기능에서는 다음 자격 증명도 전달합니다. "include". 이것은 인증된 세션을 만드는 데 유용할 것입니다. 기본적으로 api/v1/로그인 끝점에서 쿠키를 검색하여 브라우저에 저장합니다. 이 매개 변수를 사용하면 백엔드에서 전송할 때 무시하지 않고 가져올 수 있습니다.

이와 같이 VerifyEmail.tsx 업데이트

 
import React from "react";
import { Link } from "react-router-dom";
import { useSearchParams } from "react-router-dom";
enum VerificationState {
    pending = "pending",
        invalid = "invalid",
        already_verified = "already_verified",
        success = "success"
}
function VerifyEmail() {
  const [searchParams] = useSearchParams();
  const [verificationState, setVerificationState] = React.useState(VerificationState.pending);
  const attemptToVerify = async () => {
        const code = searchParams.get("code");
        const email = searchParams.get("email");
        if (!code || !email) {
                return setVerificationState(VerificationState.invalid)
        }
        const response = await fetch('http://localhost:8000/api/v1/verify-email', {
                method: "post",
                headers: {
                          // needed so express parser says OK to read
                          'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                          code,
                          email
                })
        })
        const result = await response.json();
        if (result.data === "already verified") {
                return setVerificationState(VerificationState.already_verified)
        }
        setVerificationState(VerificationState.success);
  }

    React.useEffect(() => {
          attemptToVerify()
    }, []);
  switch (verificationState) {
    case VerificationState.success: {
            return (
                      <div>
                        Email verified. Please <Link to="/login">sign in</Link>.
                      </div>
                    )
    }
    case VerificationState.already_verified: {
            return (
                      <div>
                        This link is expired. Please <Link to="/login">log in</Link>.
                      </div>
                    )
    }
    case VerificationState.invalid: {
            return (
                      <div>
                        This link is invalid link.
                      </div>
                    )
    }
    default: {
            return (
                      <div>
                        Verifying...
                      </div>
                    );
    }
  }
}
export default VerifyEmail;

App.tsx를 다음으로 업데이트합니다.

import React from "react";
import { Link } from "react-router-dom";
import './App.css';
function App() {
  const [user, setUser] = React.useState(null);
  React.useEffect(() => {
        fetch('http://localhost:8000/api/v1/user', {
                method: "get",
                credentials: "include",
                headers: {
                          // needed so express parser says OK to read
                          'Content-Type': 'application/json'
                },
        })
          .then(response => response.json())
          .then(result => {
                  console.log(result, 'result')
                  if (result.data) {
                              setUser(result.data)
                  }
        });
  }, []);
  const logout = async () => {
        const response = await fetch('http://localhost:8000/api/v1/logout', {
                method: "get",
                credentials: "include",
                headers: {
                          // needed so express parser says OK to read
                          'Content-Type': 'application/json'
                },
        })
            const result = await response.json();
        if (result.data) {
                setUser(null)
        }
  }
  return (
        <div className="App">
    {
            !user && (
              <div>
                <div>
                  <Link to="/login">Log in</Link>
                </div>
                <div>
                  <Link to="/signup">Sign up</Link>
                </div>
                <p>Not logged in</p>
              </div>
            )
}
{
          user && <div>{user}<button onClick={logout}>logout</button></div>
}
              </div>
    );
}
export default App;

백엔드

우리 뒷부분을 살찌우기 시작하자. 별도의 터미널에서 현재 작업 디렉토리가 e-메일_auth_tutorial_backend가 되도록 탐색합니다. 달려.

 
npm init -y

-y 플래그는 모든 질문을 빠르게 수락하기 위한 것입니다. 새 파일 Index.js 만들기:

touch index.js

지금 우리가 하고자 하는 것은 기본 서버를 실행하는 것입니다. 다음을 실행합니다.

npm i --save express
 

다음을 index.js에 추가합니다.

const express = require('express');
const app = express();
const port = 8000;
app.listen(port, () => console.log('App listening on port ' + port));

다음 실행:

node index.js

"포트 8000에서 앱 듣기"라는 텍스트가 표시되면 모든 것이 정상입니다. Ctrl + C를 누른 상태에서 서버 연결을 끊습니다.

 

경로

프런트엔드에서 루트가 있었던 것처럼 이제 백엔드 루트가 필요합니다. 여기가 우리가 프런트엔드에서 공격한 끝점이 될 거야 기본 인프라를 구축합시다. 루트 디렉터리에서 새 routes.js 파일을 생성하고 다음을 추가하십시오.

var express = require('express');
var router = express.Router();
router.route('/ping')
.get((req, res) => {
    res.status(200).send({
          data: "pong"
    })
});
module.exports = router;

보시다시피 끝점 /ping을 누르면 "퐁"이 반환됩니다. 이는 시스템이 일반적으로 작동하는지 확인하기 위한 것입니다.

routes.js 파일을 사용하도록 index.js를 업데이트합니다. 다음과 같이 보여야 합니다.

 
const express = require('express');
const routes = require("./routes");
const app = express();
app.use('/api/v1/', routes);
const port = 8000;
app.listen(port, () => console.log('App listening on port ' + port));

서버를 재시작하고 브라우저에서 http://localhost:8000/api/v1/ping으로 이동합니다.

다음 항목이 보이면 이동해도 좋습니다. {"data":pong"}

데이터베이스

우리가 다음으로 하고 싶은 것은 데이터베이스를 설정하는 것입니다. 컴퓨터에 Mongo를 설정하지 않은 경우, 다음 지침에 따라 다운로드하십시오.

 

문서의 지침에 따라 MongoDB 서비스를 시작하십시오.

MacOS의 경우 터미널에서 다음을 실행합니다.

brew services start mongodb-community@5.0

Windows에서는 확인할 수 없지만 다음을 실행해야 합니다.

"C:\Program Files\MongoDB\Server\5.0\bin\mongod.exe" --dbpath="c:\data\db"
 

성공한 경우 터미널에서 다음을 실행할 수 있습니다.

mongo

그러면 몽고쉘 안에 들어가게 됩니다. 여기서 데이터베이스의 데이터를 탐색할 수 있습니다. 셸 내에서 다음을 실행하여 새 데이터스토어를 시작합니다.

use SampleDatabase;

e-메일_auth_tutorial_backend 폴더의 루트에 있는 터미널에서 다음을 실행합니다.

 
npm i --save body-parser cors express-session mongoose nodemailer passport passport-local passport-local-mongoose passport-mongoose

이러한 각 라이브러리의 일반적인 다운은 다음과 같습니다.

body-parser: 수신 요청에서 본문에서 JSON을 구문 분석하는 데 도움이 됩니다.

코르스: CORS 관리에 도움이 됩니다.

express-discome: 인증된 후 사용자 세션을 관리하는 데 도움이 됩니다.

 

Mongoose: MongoDB 데이터 관리에 도움이 됩니다.

nodemailer: 이메일 전송(이 경우 이메일 인증)을 지원합니다.

여권: 인증/허가 세부 정보를 제공합니다.

passport-local: 전자 메일/암호 인증과 관련된 세부 정보를 제공합니다.

여권-local-mongoose: 당사의 MongoDB 데이터가 당사의 이메일 기반 인증 시스템과 상호 작용할 수 있도록 지원합니다.

 

여권-mongoose: MongoDB 데이터가 인증 시스템과 상호 작용할 수 있도록 지원했습니다.

성공적으로 실행되면 다음과 같이 index.js를 업데이트하십시오.

/*  EXPRESS SETUP  */
const express = require('express');
const app = express();
app.use(express.static(__dirname));
const session = require('express-session');
const mongoose = require('mongoose');
const passport = require('passport');
// helps parse the body so the server can understand JSON.
const bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// sets up session management
const expressSession = session({
    secret: 'this_is_a_secret_that_should_not_be_disclosed',
    resave: false,
    saveUninitialized: false,
});
app.use(expressSession);
// Lots of behind-the-scenes configurations
// happen here to reduce the amount of code
// you have to write in order for express and passport to communicate with each other.
app.use(passport.initialize());
app.use(passport.session());
// cross-origin resource sharing set up
var cors = require('cors');
var corsOption = {
    origin: true,
    methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
    credentials: true,
    exposedHeaders: ['x-auth-token', "Access-Control-Allow-Credentials"]
};
app.use(cors(corsOption));
const port = 8000;
app.listen(port, () => console.log('App listening on port ' + port));
const routes = require("./routes");
// database setup
mongoose.connect("mongodb://localhost/SampleDatabase",
                 { useNewUrlParser: true, useUnifiedTopology: true });
app.use('/api/v1/', routes);

방금 추가한 요소는 다음과 같습니다.

  • Body parser: Express는 기본적으로 JSON을 구문 분석할 수 없으므로 JSON 형식으로 보내는 데이터를 인식할 수 없습니다.
  • Passport.js: 이것은 인증/허가 로직 대부분을 대행할 수 있는 표준 타사 라이브러리입니다.
  • 데이터베이스: 사용자 데이터를 저장할 수 있는 공간이 필요합니다. 이 튜토리얼에서는 몽고를 구성하는 것이 가장 쉽기 때문에 몽고를 사용할 것입니다.
  • 세션 관리자: 사용자가 로그인한 후 페이지를 새로 고치면, 사용자가 정확한 사람인지 어떻게 확인할 수 있습니까? 이 튜토리얼에서는 Passport의 기본 세션 전략인 쿠키를 사용할 것입니다. 우리가 직접 쿠키에 손을 대기는 싫으니까, 그건 세션 매니저에게 위임하자.
  • 상호 출처 리소스 공유(CORS): 웹 사이트는 localhost:3000에 있고 서버는 localhost:8000에 있으므로 기본적으로 두 페이지가 상호 작용할 수 없습니다. 이것은 브라우저에서 제정한 기본 보안 조치입니다. localhost:3000에서 발생한 요청을 수락하도록 서버를 구성해야 합니다.
 

잠시 시간을 내어 그 코드를 살펴보십시오. 인라인 코멘트를 메모하여 현재 상황을 더 잘 파악하십시오. 이것은 서버가 node index.js를 실행하여 계속 실행 중인지 확인하는 좋은 순간입니다.

MongoDB를 패스포트로 바인딩

다음으로 우리가 해야 할 일은 몽고DB에 있는 우리의 데이터 저장소의 데이터를 우리의 passport.js 시스템에 연결하는 조치를 취하는 것입니다. 새 user.js 파일 생성

이 파일에서는 다음을 수행합니다.

  • 사용자 스키마 추가
  • 사용자 스키마와 여권 바인딩
  • 다른 곳에서 MongoDB의 사용자 컬렉션과 상호 작용하기 위해 사용할 수 있는 내보내기 가능한 UserDetails 모델을 만듭니다.
  • 전자 메일/암호 인증 전략 구성
  • 사용자를 위한 역직렬화/직렬화를 추가합니다. 사용자는 처음 로그인하고 데이터베이스에서 사용자 데이터를 검색할 때 직렬화됩니다. 인증된 API 호출 시 사용자는 역직렬화됩니다. 우리는 역직렬화된 버전의 사용자를 원하므로 메소드에 액세스할 수 있습니다.
 

추가 작업 없이 user.js에 다음을 추가합니다.

const mongoose = require('mongoose');
const passportLocalMongoose = require('passport-local-mongoose');
const passport = require('passport');
const PassportLocalStrategy = require('passport-local');
const UserDetail = new mongoose.Schema({
    username: String,
    email: String,
    password: String,
    emailVerified: Boolean,
    emailVerificationHash: String
});
UserDetail.plugin(passportLocalMongoose);
const UserDetails = mongoose.model('user', UserDetail, 'user');
passport.use(new PassportLocalStrategy(
    function(username, password, done) {
          UserDetails.findOne({ email: username }, function (err, user) {
                  if (err) { return done(err); }
                  if (!user) { return done(null, false); }
                  user.authenticate(password, function(err,model,passwordError){
                            if(passwordError){
                                          console.log(err)
                                          done(null, false);
                            } else if(model) {
                                          done(null, user);
                            }
                  })
          });
    }
  ));
passport.serializeUser(function(user, done) {
    console.log('serializing user: ');
    console.log(user);
    done(null, user._id);
});
passport.deserializeUser(function(id, done) {
    UserDetails.findById(id, function(err, user) {
          done(err, user);
    });
});
module.exports = { UserDetails };

우편물이 왔습니다.

우리는 메일을 보낼 수 있는 서비스가 필요합니다. 기본 데모에 불과하지만 모든 서비스를 저장할 공간을 설정하는 프로세스를 살펴보겠습니다.

mkdir services && touch services/mail.service.js
 

우편으로.service.js, 다음을 추가합니다.

const nodemailer = require("nodemailer");
const sendMail = async ({
    subject,
    to,
    html,
    text
}) => {
    let transporter = nodemailer.createTransport({
          service: 'gmail',
          auth: {
                  user: '<YOUR_GMAIL_USERNAME<@gmail.com',
                  pass: '<YOUR_PASSWORD_DONT_SAVE_THIS_TO_GITHUB>' // naturally, replace both with your real credentials or an application-specific password
          }
    });
    return await transporter.sendMail({
          from: '"The Demo App Team" <foo@example.com>', // sender address
          to, // list of receivers
          subject, // Subject line
          text, // plain text body
          html, // html body
    });
}
module.exports = {
    sendMail
}

우리는 앞으로 앱에서 메일을 보내야 할 때마다 이 sendMail 기능을 호출할 수 있도록 특정 메일 서비스를 원합니다.

중요: 전자 메일이 작동하려면 gmail 계정에서 보안 수준이 낮은 앱을 켜짐으로 설정해야 합니다. 여기에서 지시사항을 찾으십시오.

다음과 같이 표시된 링크를 따라갑니다. Google 계정의 보안 수준이 낮은 앱 액세스 섹션으로 이동합니다. 로그인해야 할 수도 있습니다.

 

인증 엔드포인트

이제 다음에 대한 엔드포인트를 추가하려고 합니다.

  • 등록하다
  • 로그 인.
  • 로그아웃
  • 전자 메일 확인

이제 Routes.js는 다음과 같이 표시됩니다.

var express = require('express');
var router = express.Router();
const { UserDetails } = require("./user");
const { sendMail } = require('./services/mail.service');
const passport = require('passport');
const generateHash = () => {
    return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
}
const sendVerificationEmail = async ({
    to,
    verificationHash,
    email
}) => {
    const linkAddress = `https://localhost:3000/verify-email?code=${verificationHash}&email=${email}`
      return await sendMail({
            to,
            subject: "Please verify your email",
            text: `Hi John,
                \nPlease click on the following link to verify your email:
                    \n${linkAddress}
                        \n
                            \nSincerely,
                                The Demo App Team`,
            html: `Hi John,
                <br>Please click on the following link to verify your email:
                    <br><a href="${linkAddress}">${linkAddress}</a>
                        <br><br>Sincerely,
                            <br>The Demo App Team`
      }); 
}
router.route('/ping')
  .get((req, res) => {
      res.send({
              data: "pong"
      })
});
router.route('/verify-email')
  .post(async (req, res) => {
      UserDetails.findOne({
              email: req.body.email,
              emailVerificationHash: req.body.code
      }, (err, account) => {
              if (err) {
                        console.log(err);
                        return res.status(400).send({ error: err.message });
              }
              if (account.emailVerified) {
                        return res.status(200).send({data: "already verified"});
              }
              UserDetails.updateOne({_id: account.id}, {
                        $set: {
                                    emailVerified: true
                        }
              }, (_err) => {
                        if (_err) {
                                    console.log(_err);
                                    return res.status(400).send({ error: _err.message });
                        }
                        return res.status(200).send({data: "done"})
              })

      })
});
function checkAuthentication(req,res,next){
    if(req.isAuthenticated()) {
            next();
    } else{
            res.status(403).send({reason: "unauthenticated"});
    }
}
router.get('/user', checkAuthentication, (req, res) => {
    console.log(req.session, 'sesion', req.user)
    res.status(200).send({data: req.user.email})
})
router.route('/logout')
  .get((req, res) => {
      req.logout()
      res.status(200).send({data: "OK"})
})
router.route('/login')
  .post((req, res, next) => {
      passport.authenticate('local',
                                (err, user, info) => {
              if (err) {
                        return next(err);
              }
        if (!user) { 
                  return res.status(400).send({data: "no user"})
        }
        req.logIn(user, function(err) {
                  if (err) {
                              return res.status(400).send({data: "error"})
                  }
          return res.status(200).send({data: "ok"})
        });
      })(req, res, next);
})
router.route('/signup')
  .post((req, res) => {
      const emailVerificationHash = generateHash();
      UserDetails.register({
              username: req.body.username,
              email: req.body.email,
              emailVerified: false,
              emailVerificationHash
      }, req.body.password, async (err, account) => {
              if (err) {
                        console.log(err);
                        return res.status(400).send({ error: err.message });
              }
              await sendVerificationEmail({
                        to: req.body.email,
                        verificationHash: emailVerificationHash,
                        email: req.body.email
              })
              res.status(200).send({ data: "ok" });
      })
});
module.exports = router;
 

서버를 재시작하고 localhost:3000을 방문하십시오. 워크플로를 사용해 보십시오.

  • 계정 등록
  • 이메일에서 링크를 확인하고 이메일을 확인하십시오.
  • 계정에 로그인합니다.
  • 홈 페이지에서 페이지를 새로 고치고 이메일이 페이지에 나타납니다(이것은 백엔드에서 전송한 사용자 정보입니다).
  • 로그아웃

여기까지! 여기서 소스 코드를 찾으십시오.

댓글