JSONWebトークンとパスポートを使用してAPI認証を実装する方法
序章
多くのWebアプリケーションとAPIは、認証形式を使用してリソースを保護し、検証済みのユーザーのみにアクセスを制限します。
JSON Web Token(JWT)は、当事者間で情報をJSONオブジェクトとして安全に送信するためのコンパクトで自己完結型の方法を定義するオープンスタンダードです。
このガイドでは、JWTと Passport 、Nodeの認証ミドルウェアを使用してAPIの認証を実装する方法について説明します。
ここに、構築するアプリケーションの概要を示します。
- ユーザーがサインアップすると、ユーザーアカウントが作成されます。
- ユーザーがログインすると、JSONWebトークンがユーザーに割り当てられます。
- このトークンは、特定の安全なルートにアクセスしようとしたときにユーザーによって送信されます。
- トークンが確認されると、ユーザーはルートにアクセスできるようになります。
前提条件
このチュートリアルを完了するには、次のものが必要です。
- Node.jsはローカルにインストールされます。これは、Node.jsのインストール方法とローカル開発環境の作成に従って実行できます。
- MongoDB がローカルにインストールおよび実行されています。これは、公式ドキュメントに従うことで実行できます。
- APIエンドポイントをテストするには、Postmanなどのツールをダウンロードしてインストールする必要があります。
このチュートリアルは、Nodev14.2.0で検証されました。 npm
v6.14.5、および mongodb-community
v4.2.6。
ステップ1—プロジェクトの設定
プロジェクトを設定することから始めましょう。 ターミナルウィンドウで、プロジェクトのディレクトリを作成します。
- mkdir jwt-and-passport-auth
そして、その新しいディレクトリに移動します。
- cd jwt-and-passport-auth
次に、新しいを初期化します package.json
:
- npm init -y
プロジェクトの依存関係をインストールします。
- 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
ディレクトリ:
- mkdir model
作成する model.js
この新しいディレクトリのファイル:
- nano model/model.js
The mongoose
ライブラリは、MongoDBコレクションにマップされるスキーマを定義するために使用されます。 スキーマでは、ユーザーに電子メールとパスワードが必要になります。 The mongoose
ライブラリはスキーマを取得し、それをモデルに変換します。
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
ユーザーパスワードをハッシュして安全に保存します。 ライブラリと次のコード行を追加します。
// ...
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()
.
また、ログインしようとしているユーザーが正しい資格情報を持っていることを確認する必要があります。 次の新しいメソッドを追加します。
// ...
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
ディレクトリ:
- mkdir auth
作成する auth.js
この新しいディレクトリのファイル:
- nano auth/auth.js
要求することから始めます passport
, passport-local
、 そしてその UserModel
前のステップで作成されたもの:
const passport = require('passport');
const localStrategy = require('passport-local').Strategy;
const UserModel = require('../model/model');
まず、ユーザー登録を処理するPassportミドルウェアを追加します。
// ...
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ミドルウェアを追加します。
// ...
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
ディレクトリ:
- mkdir routes
作成する routes.js
この新しいディレクトリのファイル:
- nano routes/routes.js
要求することから始めます express
と passport
:
const express = require('express');
const passport = require('passport');
const router = express.Router();
module.exports = router;
次に、POSTリクエストの処理を追加します signup
:
// ...
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
:
const express = require('express');
const passport = require('passport');
const jwt = require('jsonwebtoken');
// ...
次に、POSTリクエストの処理を追加します login
:
// ...
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;
ユーザーのパスワードなどの機密情報をトークンに保存しないでください。
あなたは id
と email
JWTのペイロードで。 次に、秘密またはキーを使用してトークンに署名します(TOP_SECRET
). 最後に、トークンをユーザーに送り返します。
注:設定しました { session: false }
ユーザーの詳細をセッションに保存したくないためです。 ユーザーは、リクエストごとにトークンを安全なルートに送信する必要があります。
これはAPIに特に役立ちますが、パフォーマンス上の理由からWebアプリケーションには推奨されないアプローチです。
あなたは今持っています login
終点。 正常にログインしたユーザーはトークンを生成します。 ただし、アプリケーションはまだトークンに対して何もしません。
ステップ6—JWTを確認する
これで、ユーザーのサインアップとログインを処理できました。次のステップは、トークンを持つユーザーが特定の安全なルートにアクセスできるようにすることです。
このステップでは、トークンが操作されておらず、有効であることを確認します。
再訪 auth.js
ファイル:
- nano 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
ファイル:
- nano routes/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"
メッセージ。 また、に関する情報を返します user
と token
.
目標は、検証済みのトークンを持つユーザーのみにこの応答が表示されるようにすることです。
ステップ8—すべてをまとめる
ルートと認証ミドルウェアの作成がすべて完了したので、すべてをまとめることができます。
新しいを作成します app.js
ファイル:
- nano 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);
次のコマンドを使用してアプリケーションを実行します。
- 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のより深い知識が必要な場合は、次の追加リソースを使用できます。