ゼロからMERNでJWT認証を設定します


ほぼすべてのWebプロジェクトは、ユーザー認証を必要とします.この記事では、Mern StackプロジェクトでAuth Flowを実装する方法を説明します.この実装は、メールやパスワードをユーザーに登録するすべてのプロジェクトで適用できます.

動作方法


まず第一にJSON Web Token は、ユーザーの現在のログインステータスのユニークな、暗号化されたトークンを作成する機能を提供し、トークンが無効であり、期限切れでないかどうかを確認する機能を提供する人気のライブラリです.
アプリケーションの認証フローを以下に示します.

ユーザーが登録またはログインをクリックすると、Correponding ExpressルートはJWTトークンを返します.トークンがブラウザのlocalstorageに格納され、ユーザーが3日後に再びログインすることができなくなります.
Expressのすべての保護されたルート(それはユーザーのログインステータスを必要とする)はAuthミドルウェアを持っています.反応は、これらの保護されたルートを呼ぶとき、LocalStorageトークンをX - Auer - Tokenヘッダーに置きます.
ミドルウェアでは、JWTはヘッダーのトークンが有効で、期限切れになっているかどうかを検証します.もしそうならば、それはルートに処理します;そうでなければ、403を表現してください、そして、反応はユーザーをログインページに促します.

エクスプレスレジスタルート


登録ルートはリクエスト本文にメールとパスワードを受け取る.電子メールを持つユーザーが存在しない場合は、パスワードをハッシュして新しいユーザーを作成しますbcrypt , そして、それをマングースユーザーモデルに保存してください.最後に、署名されたJWTトークンを返します.
const express = require('express');
const router = express.Router();
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const User = require('../models/User');

router.post('/user', async (req, res) => {
    const { email, password } = req.body;

    try {
      // check if the user already exists
      user = await User.findOne({ email });
      if (user) {
        return res.status(400).json({ msg: 'Email already exists' });
      }

      // create new user
      user = new User({
        email,
        password,
      });

      // hash user password
      const salt = await bcrypt.genSalt(10);
      user.password = await bcrypt.hash(password, salt);
      await user.save();

      // return jwt
      const payload = {
        user: {
          id: user.id,
        },
      };

      jwt.sign(
        payload,
        process.env.JWT_SECRET,
        { expiresIn: '7 days' },
        (err, token) => {
          if (err) throw err;
          res.json({ token });
        }
      );
    } catch (err) {
      console.error(err.message);
      res.status(500).send('Server error');
    }
  }
);

エクスプレスログインルート


ログイン経路は、電子メールとパスワードを受け取ります.メールがあるユーザが存在するならば、それはハッシュパスワードを比較して、成功するならば、署名されたトークンを返します.
router.post('/user/login', async (req, res) => {
    const { email, password } = req.body;

    try {
      // check if the user exists
      let user = await User.findOne({ email });
      if (!user) {
        return res.status(400).json({ msg: 'Email or password incorrect' });
      }

      // check is the encrypted password matches
      const isMatch = await bcrypt.compare(password, user.password);
      if (!isMatch) {
        return res.status(400).json({ msg: 'Email or password incorrect' });
      }

      // return jwt
      const payload = {
        user: {
          id: user.id,
        },
      };

      jwt.sign(
        payload,
        process.env.JWT_SECRET,
        { expiresIn: '30 days' },
        (err, token) => {
          if (err) throw err;
          res.json({ token });
        }
      );
    } catch (err) {
      console.error(err.message);
      res.status(500).send('Server error');
    }
  }
);

エクスプレス取得ユーザー情報ルート


loginとregisterはトークンを返すので、このルートはトークンを指定したユーザ情報を返します.
router.get('/user/info', auth, async (req, res) => {
  try {
    const user = await UserModel.findById(req.user.id).select('-password');
    res.status(200).json({ user });
  } catch (error) {
    res.status(500).json(error);
  }
};

ミドルエクスプレス


Authミドルウェアは、トークンが存在していることを検証し、先行する前に保護されたルートに有効です.
const jwt = require('jsonwebtoken');

module.exports = function (req, res, next) {
  // Get token from header
  const token = req.header('x-auth-token');

  // Check if no token
  if (!token) {
    return res.status(401).json({ msg: 'No token, authorization denied' });
  }

  // Verify token
  try {
    jwt.verify(token, process.env.JWT_SECRET, (error, decoded) => {
      if (error) {
        return res.status(401).json({ msg: 'Token is not valid' });
      } else {
        req.user = decoded.user;
        next();
      }
    });
  } catch (err) {
    console.error('something wrong with auth middleware');
    res.status(500).json({ msg: 'Server Error' });
  }
};
次に、保護されたすべてのルートで、次のようなAuthミドルウェアを追加します.
const auth = require('../middleware/auth');
router.post('/post', auth, async (req, res) => { ... }

Authコンテキストを反応させる


AutoEducerを使用してAuthステータスとユーザー情報を格納し、USECONTEXTを使用して、ログイン、レジスタ、およびログアウトを含む縮小状態とアクションを提供します.
loginおよびregisterアクションは、LocalStorage内のaxiosリクエストから返されたトークンを格納し、トークンでユーザ情報ルートを呼び出します.
還元された状態initか変化に関して、ユーザ情報ルートがユーザ情報が還元器にあることを確認するために呼ばれるでしょう、そして、ユーザーがログインされるならば、axos Authヘッダーはセットされます.
import { createContext, useEffect, useReducer } from 'react';
import axios from 'axios';

const initialState = {
  isAuthenticated: false,
  user: null,
};

const authReducer = (state, { type, payload }) => {
  switch (type) {
    case 'LOGIN':
      return {
        ...state,
        isAuthenticated: true,
        user: payload.user,
      };
    case 'LOGOUT':
      return {
        ...state,
        isAuthenticated: false,
        user: null,
      };
  }
};

const AuthContext = createContext({
  ...initialState,
  logIn: () => Promise.resolve(),
  register: () => Promise.resolve(),
  logOut: () => Promise.resolve(),
});

export const AuthProvider = ({ children }) => {
  const [state, dispatch] = useReducer(authReducer, initialState);

  const getUserInfo = async () => {
    const token = localStorage.getItem('token');

    if (token) {
      try {
        const res = await axios.get(`/api/user/info`);
        axios.defaults.headers.common['x-auth-token'] = token;

        dispatch({
          type: 'LOGIN',
          payload: {
            user: res.data.user,
          },
        });
      } catch (err) {
        console.error(err);
      }
    } else {
      delete axios.defaults.headers.common['x-auth-token'];
    }
  };

  // verify user on reducer state init or changes
  useEffect(async () => {
    if (!state.user) {
        await getUserInfo();
    }
  }, [state]);

  const logIn = async (email, password) => {
    const config = {
      headers: { 'Content-Type': 'application/json' },
    };
    const body = JSON.stringify({ email, password });

    try {
      const res = await axios.post(`/api/user/login`, body, config);
      localStorage.setItem('token', res.data.token);
      await getUserInfo();
    } catch (err) {
      console.error(err);
    }
  };

  const register = async (email, password) => {
    const config = {
      headers: { 'Content-Type': 'application/json' },
    };
    const body = JSON.stringify({ email, password });

    try {
      const res = await axios.post(`/api/user/register`, body, config);
      localStorage.setItem('token', res.data.token);
      await getUserInfo();
    } catch (err) {
      console.error(err);
    }
  };

  const logOut = async (name, email, password) => {
    try {
      localStorage.removeItem('token');
      dispatch({
        type: 'LOGOUT',
      });
    } catch (err) {
      console.error(err);
    }
  };

  return (
    <AuthContext.Provider value={{ ...state, logIn, register, logOut }}>
      {children}
    </AuthContext.Provider>
  );
};

export default AuthContext;
私はカスタマイズされたフックでusecontextを入れました-簡単に文脈にアクセスするだけの良い実行.
import { useContext } from 'react';
import AuthContext from '../contexts/FirebaseAuthContext';

const useAuth = () => useContext(AuthContext);

export default useAuth;

ゲスト&ユーザーガード


ガードコンポーネントは、他のコンポーネントを囲む単純なauthナビゲーションコンポーネントです.Authナビゲーションロジックが個々のコンポーネントから分離されるように、ガードコンポーネントを使用します.
ゲストガードはログインしていないユーザーをログインし、保護されたページの周りにラップされます.
import { Navigate } from 'react-router-dom';
import useAuth from '../hooks/useAuth';

const GuestGuard = ({ children }) => {
  const { isAuthenticated } = useAuth();

  if (!isAuthenticated) {
    return <Navigate to="/login" />;
  }
  return <>{children}</>;
};
<GuestGuard>
  <PostReview />
</GuestGuard>
ユーザーガードはログインしたユーザーをホームページに移動し、ログインと登録ページの周りにラップされます.
const UserGuard = ({ children }) => {
  const { isAuthenticated } = useAuth();

  if (isAuthenticated) {
    return <Navigate to="/dashboard" />;
  }
  return <>{children}</>;
};
<UserGuard>
  <Login />
</UserGuard>
これは、ゼロからMRNのJWT認証を設定する方法です.ユーザーと電子メール登録は小規模プロジェクトのためによく働きます、そして、私はウェブサイトスケールとしてOAuthを実行することを勧めます.