序章

多くのWebアプリケーションとAPIは、認証形式を使用してリソースを保護し、検証済みのユーザーのみにアクセスを制限します。

JSON Web Token(JWT)は、当事者間で情報をJSONオブジェクトとして安全に送信するためのコンパクトで自己完結型の方法を定義するオープンスタンダードです。

このガイドでは、JWTと Passport Nodeの認証ミドルウェアを使用してAPIの認証を実装する方法について説明します。

ここに、構築するアプリケーションの概要を示します。

  • ユーザーがサインアップすると、ユーザーアカウントが作成されます。
  • ユーザーがログインすると、JSONWebトークンがユーザーに割り当てられます。
  • このトークンは、特定の安全なルートにアクセスしようとしたときにユーザーによって送信されます。
  • トークンが確認されると、ユーザーはルートにアクセスできるようになります。

前提条件

このチュートリアルを完了するには、次のものが必要です。

このチュートリアルは、Nodev14.2.0で検証されました。 npm v6.14.5、および mongodb-community v4.2.6。

ステップ1—プロジェクトの設定

プロジェクトを設定することから始めましょう。 ターミナルウィンドウで、プロジェクトのディレクトリを作成します。

  1. mkdir jwt-and-passport-auth

そして、その新しいディレクトリに移動します。

  1. cd jwt-and-passport-auth

次に、新しいを初期化します package.json:

  1. npm init -y

プロジェクトの依存関係をインストールします。

  1. npm install --save bcrypt@4.0.1 body-parser@1.19.0 express@4.17.1 jsonwebtoken@8.5.1 mongoose@5.9.15 passport@0.4.1 passport-jwt@4.0.0 passport-local@1.0.0

必要になるだろう bcrypt ユーザーパスワードをハッシュするために、 jsonwebtoken トークンに署名するため、 passport-local ローカル戦略を実施するため、そして passport-jwt JWTを取得および検証するため。

警告:インストールを実行すると、次の問題が発生する場合があります。 bcrypt 実行しているノードのバージョンによって異なります。

ご使用の環境との互換性については、READMEを参照してください。

この時点で、プロジェクトは初期化され、すべての依存関係がインストールされています。 次に、ユーザー情報を保存するデータベースを追加します。

ステップ2—データベースのセットアップ

データベーススキーマは、データのタイプとデータベースの構造を確立します。 データベースには、ユーザー用のスキーマが必要です。

作成する model ディレクトリ:

  1. mkdir model

作成する model.js この新しいディレクトリのファイル:

  1. nano model/model.js

The mongoose ライブラリは、MongoDBコレクションにマップされるスキーマを定義するために使用されます。 スキーマでは、ユーザーに電子メールとパスワードが必要になります。 The mongoose ライブラリはスキーマを取得し、それをモデルに変換します。

model / model.js
const mongoose = require('mongoose');

const Schema = mongoose.Schema;

const UserSchema = new Schema({
  email: {
    type: String,
    required: true,
    unique: true
  },
  password: {
    type: String,
    required: true
  }
});

const UserModel = mongoose.model('user', UserSchema);

module.exports = UserModel;

攻撃者がデータベースにアクセスできた場合、パスワードが読み取られる可能性があるため、パスワードをプレーンテキストで保存することは避けてください。

これを回避するには、次のパッケージを使用します bcrypt ユーザーパスワードをハッシュして安全に保存します。 ライブラリと次のコード行を追加します。

model / model.js
// ...

const bcrypt = require('bcrypt');

// ...

const UserSchema = new Schema({
  // ...
});

UserSchema.pre(
  'save',
  async function(next) {
    const user = this;
    const hash = await bcrypt.hash(this.password, 10);

    this.password = hash;
    next();
  }
);

// ...

module.exports = UserModel;

のコード UserScheme.pre() 関数はプリフックと呼ばれます。 ユーザー情報がデータベースに保存される前に、この関数が呼び出され、プレーンテキストのパスワードを取得してハッシュし、保存します。

this 保存しようとしている現在のドキュメントを指します。

await bcrypt.hash(this.password, 10) パスワードとsaltround (または cost )の値をに渡します 10. コストが高くなると、より多くの反復でハッシュが実行され、より安全になります。 これには、アプリケーションのパフォーマンスに影響を与える可能性があるという点で、より計算集約的であるというトレードオフがあります。

次に、プレーンテキストのパスワードをハッシュに置き換えて保存します。

最後に、完了したことを示し、次のミドルウェアに進む必要があります。 next().

また、ログインしようとしているユーザーが正しい資格情報を持っていることを確認する必要があります。 次の新しいメソッドを追加します。

model / model.js
// ...

const UserSchema = new Schema({
  // ...
});

UserSchema.pre(
  // ...
});

UserSchema.methods.isValidPassword = async function(password) {
  const user = this;
  const compare = await bcrypt.compare(password, user.password);

  return compare;
}

// ...

module.exports = UserModel;

bcrypt ログインのためにユーザーから送信されたパスワードをハッシュし、データベースに保存されているハッシュされたパスワードが送信されたパスワードと一致するかどうかを確認します。 戻ります true 一致する場合。 それ以外の場合は、 false 一致するものがない場合。

この時点で、MongoDBコレクション用に定義されたスキーマとモデルがあります。

ステップ3—登録およびログインミドルウェアの設定

Passportは、リクエストの認証に使用される認証ミドルウェアです。

これにより、開発者は、ローカルデータベースの使用や、APIを介したソーシャルネットワークへの接続など、ユーザーを認証するためのさまざまな戦略を使用できます。

このステップでは、ローカル(電子メールとパスワード)戦略を使用します。

を使用します passport-local ユーザー登録とログインを処理するミドルウェアを作成するための戦略。 これは特定のルートに接続され、認証に使用されます。

作成する auth ディレクトリ:

  1. mkdir auth

作成する auth.js この新しいディレクトリのファイル:

  1. nano auth/auth.js

要求することから始めます passport, passport-local、 そしてその UserModel 前のステップで作成されたもの:

auth / auth.js
const passport = require('passport');
const localStrategy = require('passport-local').Strategy;
const UserModel = require('../model/model');

まず、ユーザー登録を処理するPassportミドルウェアを追加します。

auth / auth.js
// ...

passport.use(
  'signup',
  new localStrategy(
    {
      usernameField: 'email',
      passwordField: 'password'
    },
    async (email, password, done) => {
      try {
        const user = await UserModel.create({ email, password });

        return done(null, user);
      } catch (error) {
        done(error);
      }
    }
  )
);

このコードは、ユーザーから提供された情報をデータベースに保存し、成功した場合はユーザー情報を次のミドルウェアに送信します。

それ以外の場合は、エラーを報告します。

次に、ユーザーログインを処理するPassportミドルウェアを追加します。

auth / auth.js
// ...

passport.use(
  'login',
  new localStrategy(
    {
      usernameField: 'email',
      passwordField: 'password'
    },
    async (email, password, done) => {
      try {
        const user = await UserModel.findOne({ email });

        if (!user) {
          return done(null, false, { message: 'User not found' });
        }

        const validate = await user.isValidPassword(password);

        if (!validate) {
          return done(null, false, { message: 'Wrong Password' });
        }

        return done(null, user, { message: 'Logged in Successfully' });
      } catch (error) {
        return done(error);
      }
    }
  )
);

このコードは、提供された電子メールに関連付けられている1人のユーザーを検索します。

  • ユーザーがデータベース内のどのユーザーとも一致しない場合は、 "User not found" エラー。
  • パスワードがデータベース内のユーザーに関連付けられているパスワードと一致しない場合は、 "Wrong Password" エラー。
  • ユーザーとパスワードが一致する場合は、 "Logged in Successfully" メッセージが表示され、ユーザー情報が次のミドルウェアに送信されます。

それ以外の場合は、エラーを報告します。

この時点で、サインアップとログインを処理するためのミドルウェアがあります。

ステップ4—サインアップエンドポイントを作成する

Express は、ルーティングを提供するWebフレームワークです。 このステップでは、 signup 終点。

作成する routes ディレクトリ:

  1. mkdir routes

作成する routes.js この新しいディレクトリのファイル:

  1. nano routes/routes.js

要求することから始めます expresspassport:

ルート/routes.js
const express = require('express');
const passport = require('passport');

const router = express.Router();

module.exports = router;

次に、POSTリクエストの処理を追加します signup:

ルート/routes.js
// ...

const router = express.Router();

router.post(
  '/signup',
  passport.authenticate('signup', { session: false }),
  async (req, res, next) => {
    res.json({
      message: 'Signup successful',
      user: req.user
    });
  }
);

module.exports = router;

ユーザーがこのルートにPOSTリクエストを送信すると、Passportは以前に作成されたミドルウェアに基づいてユーザーを認証します。

あなたは今持っています signup 終点。 次に、 login 終点。

ステップ5—ログインエンドポイントの作成とJWTへの署名

ユーザーがログインすると、ユーザー情報がカスタムコールバックに渡され、カスタムコールバックがその情報を使用して安全なトークンを作成します。

このステップでは、 login 終点。

まず、 jsonwebtoken:

ルート/routes.js
const express = require('express');
const passport = require('passport');
const jwt = require('jsonwebtoken');

// ...

次に、POSTリクエストの処理を追加します login:

ルート/routes.js
// ...

const router = express.Router();

// ...

router.post(
  '/login',
  async (req, res, next) => {
    passport.authenticate(
      'login',
      async (err, user, info) => {
        try {
          if (err || !user) {
            const error = new Error('An error occurred.');

            return next(error);
          }

          req.login(
            user,
            { session: false },
            async (error) => {
              if (error) return next(error);

              const body = { _id: user._id, email: user.email };
              const token = jwt.sign({ user: body }, 'TOP_SECRET');

              return res.json({ token });
            }
          );
        } catch (error) {
          return next(error);
        }
      }
    )(req, res, next);
  }
);

module.exports = router;

ユーザーのパスワードなどの機密情報をトークンに保存しないでください。

あなたは idemail JWTのペイロードで。 次に、秘密またはキーを使用してトークンに署名します(TOP_SECRET). 最後に、トークンをユーザーに送り返します。

注:設定しました { session: false } ユーザーの詳細をセッションに保存したくないためです。 ユーザーは、リクエストごとにトークンを安全なルートに送信する必要があります。

これはAPIに特に役立ちますが、パフォーマンス上の理由からWebアプリケーションには推奨されないアプローチです。

あなたは今持っています login 終点。 正常にログインしたユーザーはトークンを生成します。 ただし、アプリケーションはまだトークンに対して何もしません。

ステップ6—JWTを確認する

これで、ユーザーのサインアップとログインを処理できました。次のステップは、トークンを持つユーザーが特定の安全なルートにアクセスできるようにすることです。

このステップでは、トークンが操作されておらず、有効であることを確認します。

再訪 auth.js ファイル:

  1. nano auth/auth.js

次のコード行を追加します。

auth / auth.js
// ...

const JWTstrategy = require('passport-jwt').Strategy;
const ExtractJWT = require('passport-jwt').ExtractJwt;

passport.use(
  new JWTstrategy(
    {
      secretOrKey: 'TOP_SECRET',
      jwtFromRequest: ExtractJWT.fromUrlQueryParameter('secret_token')
    },
    async (token, done) => {
      try {
        return done(null, token.user);
      } catch (error) {
        done(error);
      }
    }
  )
);

このコードは passport-jwt クエリパラメータからJWTを抽出します。 次に、このトークンがログイン中に設定されたシークレットまたはキーで署名されていることを確認します(TOP_SECRET). トークンが有効な場合、ユーザーの詳細は次のミドルウェアに渡されます。

注:トークンで利用できないユーザーに関する追加の詳細または機密情報が必要な場合は、 _id データベースからそれらを取得するためにトークンで利用可能です。

これで、アプリケーションはトークンへの署名とトークンの検証の両方が可能になります。

ステップ7—安全なルートを作成する

それでは、検証済みのトークンを持つユーザーだけがアクセスできる安全なルートをいくつか作成しましょう。

新しいを作成します secure-routes.js ファイル:

  1. nano routes/secure-routes.js

次に、次のコード行を追加します。

ルート/secure-routes.js
const express = require('express');
const router = express.Router();

router.get(
  '/profile',
  (req, res, next) => {
    res.json({
      message: 'You made it to the secure route',
      user: req.user,
      token: req.query.secret_token
    })
  }
);

module.exports = router;

このコードは、次のGETリクエストを処理します profile. それは "You made it to the secure route" メッセージ。 また、に関する情報を返します usertoken.

目標は、検証済みのトークンを持つユーザーのみにこの応答が表示されるようにすることです。

ステップ8—すべてをまとめる

ルートと認証ミドルウェアの作成がすべて完了したので、すべてをまとめることができます。

新しいを作成します app.js ファイル:

  1. nano app.js

次に、次のコードを追加します。

app.js
const express = require('express');
const mongoose = require('mongoose');
const passport = require('passport');
const bodyParser = require('body-parser');

const UserModel = require('./model/model');

mongoose.connect('mongodb://127.0.0.1:27017/passport-jwt', { useMongoClient: true });
mongoose.connection.on('error', error => console.log(error) );
mongoose.Promise = global.Promise;

require('./auth/auth');

const routes = require('./routes/routes');
const secureRoute = require('./routes/secure-routes');

const app = express();

app.use(bodyParser.urlencoded({ extended: false }));

app.use('/', routes);

// Plug in the JWT strategy as a middleware so only verified users can access this route.
app.use('/user', passport.authenticate('jwt', { session: false }), secureRoute);

// Handle errors.
app.use(function(err, req, res, next) {
  res.status(err.status || 500);
  res.json({ error: err });
});

app.listen(3000, () => {
  console.log('Server started.')
});

注:のバージョンによって異なります mongoose、次のメッセージが表示される場合があります。 WARNING: The 'useMongoClient' option is no longer necessary in mongoose 5.x, please remove it..

また、次の廃止通知が表示される場合があります useNewUrlParser, useUnifiedTopology、 と ensureIndex (createIndexes).

トラブルシューティング中に、これらを変更することでこれらを解決することができました mongoose.connect メソッド呼び出しと追加 mongoose.set メソッド呼び出し:

mongoose.connect("mongodb://127.0.0.1:27017/passport-jwt", {
  useNewUrlParser: true,
  useUnifiedTopology: true,
});
mongoose.set("useCreateIndex", true);

次のコマンドを使用してアプリケーションを実行します。

  1. node app.js

が表示されます "Server started." メッセージ。 アプリケーションを実行したままにして、テストします。

ステップ9—Postmanでのテスト

すべてをまとめたので、Postmanを使用してAPI認証をテストできます。

注:リクエストのためにPostmanインターフェースをナビゲートするための支援が必要な場合は、公式ドキュメントを参照してください。

まず、電子メールとパスワードを使用して、アプリケーションに新しいユーザーを登録する必要があります。

Postmanで、リクエストを設定します signup で作成したエンドポイント routes.js:

POST localhost:3000/signup
Body
x-www-form-urlencoded

そして、これらの詳細を Body あなたの要求の:

価値
Eメール [email protected]
パスワード password

それが終わったら、送信ボタンをクリックして開始します POST リクエスト:

Output
{ "message": "Signup successful", "user": { "_id": "[a long string of characters representing a unique id]", "email": "[email protected]", "password": "[a long string of characters representing an encrypted password]", "__v": 0 } }

パスワードはデータベースに保存される方法であるため、暗号化された文字列として表示されます。 これはあなたが書いたプレフックの結果です model.js 使用する bcrypt パスワードをハッシュします。

次に、資格情報を使用してログインし、トークンを取得します。

Postmanで、リクエストを設定します login で作成したエンドポイント routes.js:

POST localhost:3000/login
Body
x-www-form-urlencoded

そして、これらの詳細を Body あなたの要求の:

価値
Eメール [email protected]
パスワード password

それが終わったら、送信ボタンをクリックして開始します POST リクエスト:

Output
{ "token": "[a long string of characters representing a token]" }

トークンを取得したので、安全なルートにアクセスするときはいつでもこのトークンを送信します。 後で使用するためにコピーして貼り付けます。

アクセスすることで、アプリケーションがトークンの検証をどのように処理するかをテストできます /user/profile.

Postmanで、リクエストを設定します profile で作成したエンドポイント secure-routes.js:

GET localhost:3000/user/profile
Params

そして、と呼ばれるクエリパラメータでトークンを渡します secret_token:

価値
secret_token [a long string of characters representing a token]

それが終わったら、送信ボタンをクリックして開始します GET リクエスト:

Output
{ "message": "You made it to the secure route", "user": { "_id": "[a long string of characters representing a unique id]", "email": "[email protected]" }, "token": "[a long string of characters representing a token]" }

トークンが収集され、検証されます。 トークンが有効な場合、安全なルートへのアクセスが許可されます。 これは、で作成した応答の結果です。 secure-routes.js.

このルートにアクセスすることもできますが、トークンが無効な場合、リクエストは Unauthorized エラー。

結論

このチュートリアルでは、JWTを使用してAPI認証を設定し、Postmanを使用してテストしました。

JSON Webトークンは、APIの認証を作成するための安全な方法を提供します。 トークン内のすべての情報を暗号化することでセキュリティの層を追加できるため、トークンの安全性がさらに高まります。

JWTのより深い知識が必要な場合は、次の追加リソースを使用できます。