最新のNode.js Koaフレームワーク用の最新のJWT認証

画像

承認タスクはほぼすべてのNode.jsプロジェクトで発生しますが、正しく構成するには、多数のモジュールを接続し、さまざまなソースから多くの情報を収集する必要があります。



この記事では、MongoDBに保存されたパスワードハッシュを使用したNode.jsおよびKoa向けの完全なJSON Web Token(JWT)承認ソリューションについて説明します。 Node.jsの基本的な知識と、Mongooseを介してMongoDBを操作するための原則が読者から期待されています。



具体的に何が議論されるのか、そしてその理由についていくつかの言葉。



なぜコア。 Expressフレームワークの人気が非常に高いにもかかわらず、 Koaは最新の非同期/待機構文を使用してアプリケーションを作成する機能を提供します。 コールバックの代わりにasync / awaitを使用することは、このフレームワークを詳しく調べるのに十分なインセンティブです。



なぜJWT。 セッションを使用した認証へのアプローチは、モバイルアプリケーションでの使用が許可されておらず、Cookieがサポートされていないため、すでに時代遅れと呼ばれています。 また、クラスタシステムでセッションの問題が発生する可能性があります。 JWT認証にはこれらの欠点はなく、多くの追加の利点があります。 JWTの詳細については、 こちらをご覧ください。



この記事では、以下を使用した完全な承認ソリューションを検討します。



  1. passport.js。 Node.jsプロジェクトで認証を操作するための事実上の標準
  2. パスワードをハッシュし、ハッシュをMongoDBデータベースに保存する
  3. REST APIの認証
  4. 通常、ポイント3よりも複雑なトピックであるsocket.ioの認証


コード内の記事の教育的価値を維持するために、多くの場合、コードの明確性を低下させるエラーや例外の拡張チェックは行われません。 したがって、本番環境でコード例を使用する前に、エラー処理とクライアントからの入力の制御に取り組む必要があります。



それでは始めましょう



1. コア接続します。 Expressとは異なり、Koaは軽量のフレームワークであるため、通常は多くの追加モジュールとともに使用されます。



const Koa = require('koa'); //  const Router = require('koa-router'); //  const bodyParser = require('koa-bodyparser'); //   POST  const serve = require('koa-static'); // ,      index.html    const logger = require('koa-logger'); //      .   . const app = new Koa(); const router = new Router(); app.use(serve('public')); app.use(logger()); app.use(bodyParser());
      
      





2. Passport.js接続します。 Passport.jsを使用すると、戦略(ローカル、ソーシャルネットワークなど)と呼ばれるさまざまなメカニズムを使用して、柔軟に承認を構成できます。 ライブラリには現在、300を超える戦略オプションがあります。



 const passport = require('koa-passport'); // passport  Koa const LocalStrategy = require('passport-local'); //   const JwtStrategy = require('passport-jwt').Strategy; //   JWT const ExtractJwt = require('passport-jwt').ExtractJwt; //   JWT app.use(passport.initialize()); //  passport app.use(router.routes()); //   const server = app.listen(3000);//     3000
      
      





3. 作業をJWTに接続します。 簡単に言えば、JWTは単なるJSONであり、たとえば、ユーザーの電子メールを格納できます。 このJSONは秘密鍵で署名されているため、この電子メールは変更できませんが、読み取ることはできます。



したがって、クライアントからJWTを受信するとき、彼があなたのところに来たと主張するユーザーは確実です(彼のJWTが誰かに盗まれたわけではないが、これは全く異なる話です)。



 const jwtsecret = "mysecretkey"; //    JWT const jwt = require('jsonwebtoken'); //   JWT  hhtp const socketioJwt = require('socketio-jwt'); //   JWT  socket.io
      
      





4. socket.io接続します。 一言で言えば、socket.ioは、サーバーで発生した変更に応答するアプリケーションを操作するためのモジュールです。たとえば、チャットに使用できます。 サーバーとブラウザーがWebSocketsプロトコルをサポートしている場合、socket.ioはそれを使用します。そうでない場合は、ブラウザーとサーバー間の双方向通信を実装するための他のメカニズムを探します。



 const socketIO = require('socket.io');
      
      





5. MongoDB接続して 、ユーザーのオブジェクトを保存します。



 const mongoose = require('mongoose'); //      MongoDB const crypto = require('crypto'); //  node.js     ,  ..   .
      
      





すべて一緒に実行します



ユーザーオブジェクト( user )は、名前、電子メール、およびパスワードハッシュで構成されます。



POSTリクエストから受け取ったパスワードをハッシュに変換し、データベースに保存するために、仮想フィールドの概念が使用されます。 仮想フィールドとは、MongooseモデルにはあるがMongoDBデータベースにはないフィールドです。



 mongoose.Promise = Promise; //  Mongoose    mongoose.set('debug', true); //  Mongoose       .     mongoose.connect('mongodb://localhost/test'); //    test   .   ,    .
      
      





ユーザーのスキームとモデルを作成します。



 const userSchema = new mongoose.Schema({ displayName: String, email: { type: String, required: ' e-mail', unique: ' e-mail  ' }, passwordHash: String, salt: String, }, { timestamps: true }); userSchema.virtual('password') .set(function (password) { this._plainPassword = password; if (password) { this.salt = crypto.randomBytes(128).toString('base64'); this.passwordHash = crypto.pbkdf2Sync(password, this.salt, 1, 128, 'sha1'); } else { this.salt = undefined; this.passwordHash = undefined; } }) .get(function () { return this._plainPassword; }); userSchema.methods.checkPassword = function (password) { if (!password) return false; if (!this.passwordHash) return false; return crypto.pbkdf2Sync(password, this.salt, 1, 128, 'sha1') == this.passwordHash; }; const User = mongoose.model('User', userSchema);
      
      





パスワードハッシュを操作するメカニズムをより深く理解するには、Node.jsのドックにあるpbkdf2Syncコマンドについて読むことができます。



Passport.jsで作業を構成します



ユーザー認証プロセスは次のとおりです。



手順1.新しいユーザーが登録され、MongoDBデータベースにそのユーザーに関するレコードが作成されます。

ステップ2.ユーザーはサイトにパスワードを使用してログインし、ログインとパスワードが成功すると、JWTを受け取ります。

ステップ3。 ユーザーは任意のリソースを入力し、JWTを送信します。これにより、パスワードを入力せずにログインします。



Passport.js構成メカニズムは、2つの段階で構成されています。



ステージ1.戦略の構成。 認可が成功すると、戦略はuserSchemaスキームで前述したユーザーオブジェクトを返します。

ステージ2。ステージ1で取得したユーザーオブジェクトを使用して、後続のアクション(JWTの作成など)に使用します。



ステージ1



Passport Local Strategyを構成します。 戦略の仕組みの詳細については、 こちらをご覧ください



 passport.use(new LocalStrategy({ usernameField: 'email', passwordField: 'password', session: false }, function (email, password, done) { User.findOne({email}, (err, user) => { if (err) { return done(err); } if (!user || !user.checkPassword(password)) { return done(null, false, {message: '     .'}); } return done(null, user); }); } ) );
      
      





Passport JWT戦略を構成します。 戦略の仕組みの詳細については、 こちらをご覧ください



 //  JWT  Header const jwtOptions = { jwtFromRequest: ExtractJwt.fromAuthHeader(), secretOrKey: jwtsecret }; passport.use(new JwtStrategy(jwtOptions, function (payload, done) { User.findById(payload.id, (err, user) => { if (err) { return done(err) } if (user) { done(null, user) } else { done(null, false) } }) }) );
      
      





ステージ2



ユーザーオブジェクトと連携するREST APIを作成します。



APIは、上記の承認プロセスの3つのステップに対応する3つのエンドポイントで構成されます。



/ユーザーへのリクエストの投稿-新しいユーザーを作成します。 通常、このAPIは、新しいユーザーが登録されるときに呼び出されます。 リクエストの本文には、ユーザー名、メール、パスワードを含むJSONが必要です。



 router.post('/user', async(ctx, next) => { try { ctx.body = await User.create(ctx.request.body); } catch (err) { ctx.status = 400; ctx.body = err; } });
      
      





/ログインへのポストリクエストは、使用するJWTを作成します。 リクエストの本文では、メールとユーザーパスワードが含まれるJSONを受信する予定です。 本番環境では、ユーザー登録中にもJWTを発行することが論理的です。



 router.post('/login', async(ctx, next) => { await passport.authenticate('local', function (err, user) { if (user == false) { ctx.body = "Login failed"; } else { //--payload -            const payload = { id: user.id, displayName: user.displayName, email: user.email }; const token = jwt.sign(payload, jwtsecret); //  JWT ctx.body = {user: user.displayName, token: 'JWT ' + token}; } })(ctx, next); });
      
      





有効なJWTのGET要求/カスタムチェック。



 router.get('/custom', async(ctx, next) => { await passport.authenticate('jwt', function (err, user) { if (user) { ctx.body = "hello " + user.displayName; } else { ctx.body = "No such user"; console.log("err", err) } } )(ctx, next) });
      
      





次に、socket.ioの承認を設定する最後のコードを作成しましょう。 ここでの問題は、WebSocketsプロトコルがhttpではなくtcpの上で実行され、REST APIメカニズムがそれに適用されないことです。 幸いなことに、そのためのsocketio-jwtモジュールがあり、JWTを介して許可を簡潔に記述することができます。



 let io = socketIO(server); io.on('connection', socketioJwt.authorize({ secret: jwtsecret, timeout: 15000 })).on('authenticated', function (socket) { console.log('    : ' + socket.decoded_token.displayName); socket.on("clientEvent", (data) => { console.log(data); }) });
      
      





socket.ioのJWTを介した承認については、 こちらをご覧ください



おわりに



上記のコードを使用すると、最新の承認アプローチを使用して、有効なNode.jsアプリケーションを構築できます。 もちろん、実稼働環境では、この種のアプリケーションの標準である多くのチェックを追加する必要があります。



テスト方法の説明を含むコードの完全版は、 GitHubで表示できます



All Articles