Node.js のアプリケーションで、以下のような処理を実装してみました:
- 認証(ログイン)用の API には誰でもアクセスできる
- 認証 API では ID とパスワードを与えて認証し、正しいユーザーにはトークンを発行する
- 認証以外の主な API はこの発行されたトークンを使ってアクセスした時だけ実行を許可する
- API 呼び出し時にトークンがなかったり、正しくなかった場合は実行せずにエラー
この仕組を実現するために JSON Web Tokens (以下 "JWT")を使いました:
https://jwt.io/

JWT はオープンかつ Node.js ではスタンダードなトークンベースの認証ライブラリです。以下で紹介するサンプルでは Web フレームワークである Express や、POST データを扱う body-parser も合わせて使うので、まとめてインストールしておきます:
これらのライブラリを使って以下のようなアプリケーションを作成します:
肝になるのはルーティングに認証フィルタを定義している箇所です。ここよりも前(上)で定義した内容には認証フィルタは有効にならないので、認証なしで使える API となります(つまり GET / と POST /authenticate は認証していなくても使えます)。
この POST /api/authenticate API でポストデータ user, password を受取り、その値が変数 users の中で定義されているいずれかの組み合わせと一致していれば 24H 有効なトークンが発行されます(これを以下の API 実行時にパラメータ指定します)。
一方、ここよりも後(下)で定義する内容には認証フィルタが有効になり、GET /api と GET /api/users は上記方法で取得したトークンがパラメータに設定されていないと正しく処理されない API となります。
実際にこのアプリケーションを実行し($ node app)、curl コマンドで挙動を確認してみましょう。まずは問題なく実行できるはずの GET / を実行します:
問題なく実行できました。次は認証フィルタをかけた GET /api を実行してみます:
API を実行した結果、「トークンがない」時のエラーメッセージが表示されました。ここも期待通りに動いています(試しませんが、ユーザー一覧を取得する GET /api/users も同様のエラーになります)。
では POST /api/authenticate で認証してトークンを取得してみますが最初はわざとパスワードを間違えてみます:
先程同様にエラーになりましたが、エラーメッセージが「認証失敗」に変わりました。では改めて正しいパスワードを指定して実行してみます:
今度は認証が成功しました。レスポンスにも "token" が含まれています。最後にここで返ってきた token の値を指定して、先程アクセスできなかった GET /api を実行してみます:
今度は API が実行できました。同様にして GET /api/users も実行できるはずです:
こんな感じで API の実行可否をトークンで制御できるようになりました。
今回の例ではソースコード内に静的に用意されたユーザー一覧を使って認証を行いましたが、実際の運用ではデータベース内に定義されたテーブル情報などを使うことになると思います。ただ JWT の基本的な考え方はこの1つのソースファイルだけで実現できているので、応用しやすいと思っています。
- 認証(ログイン)用の API には誰でもアクセスできる
- 認証 API では ID とパスワードを与えて認証し、正しいユーザーにはトークンを発行する
- 認証以外の主な API はこの発行されたトークンを使ってアクセスした時だけ実行を許可する
- API 呼び出し時にトークンがなかったり、正しくなかった場合は実行せずにエラー
この仕組を実現するために JSON Web Tokens (以下 "JWT")を使いました:
https://jwt.io/

JWT はオープンかつ Node.js ではスタンダードなトークンベースの認証ライブラリです。以下で紹介するサンプルでは Web フレームワークである Express や、POST データを扱う body-parser も合わせて使うので、まとめてインストールしておきます:
$ npm install express body-parser jsonwebtoken
これらのライブラリを使って以下のようなアプリケーションを作成します:
//. app.js var express = require( 'express' ); var app = express(); var bodyParser = require( 'body-parser' ); var jwt = require( 'jsonwebtoken' ); //. アプリケーションサーバーの稼働ポート番号 var port = process.env.PORT || 8080 //. 任意のシークレット文字列を登録 app.set( 'superSecret', 'welovenodejs' ); //. 任意の文字列 app.use( bodyParser.urlencoded( { extended: false } ) ); app.use( bodyParser.json() ); //. ユーザー情報(本来は DB などに格納された情報を使う) var users = [ { name: 'user0', password: 'pass0', admin: true }, { name: 'user1', password: 'pass1', admin: false }, { name: 'user2', password: 'pass2', admin: false }, { name: 'user3', password: 'pass3', admin: false } ]; //. ドキュメントルートへの GET は許可 app.get( '/', function( req, res ){ res.send( 'Hello. The API is at http://localhost:' + port + '/api' ); }); //. API ROUTES var apiRoutes = express.Router(); //. トークンなしでアクセスを許可する API を先に定義する //. POST(http://localhost:8080/api/authenticate) apiRoutes.post( '/authenticate', function( req, res ){ for( var i = 0; i < users.length; i ++ ){ if( users[i].name == req.body.name && users[i].password == req.body.password ){ //. 認証したユーザーの情報を使ってトークンを生成 var token= jwt.sign( users[i], app.get( 'superSecret' ), { expiresIn: '24h' }); res.json( { success: true, message: 'Authentication successfully finished.', token: token } ); return; } } res.json( { success: false, message: 'Authentication failed.' } ); return; });
//. ここより上で定義した API には認証フィルタはかけていない(そのまま使える) //. 認証フィルタ apiRoutes.use( function( req, res, next ){
//. ポスト本体、URLパラメータ、HTTPヘッダいずれかにトークンがセットされているか調べる var token = req.body.token || req.query.token || req.headers['x-access-token']; if( !token ){
//. トークンが設定されていなかった場合は無条件に 403 エラー return res.status(403).send( { success: false, message: 'No token provided.' } ); } //. 設定されていたトークンの値の正当性を確認 jwt.verify( token, app.get( 'superSecret' ), function( err, decoded ){ if( err ){ //. 正当な値ではなかった場合はエラーメッセージを返す return res.json( { success: false, message: 'Invalid token.' } ); } //. 正当な値が設定されていた場合は処理を続ける req.decoded = decoded; next(); }); }); //. 以下はトークンがないと使えない API //. GET(http://localhost:8080/api/) apiRoutes.get( '/', function( req, res ){ res.json( { message: 'Welcome to API routing.' } ); }); //. GET(http://localhost:8080/api/users) apiRoutes.get( '/users', function( req, res ){ res.json( users ); }); //. /api 以下に API をルーティング app.use( '/api', apiRoutes ); app.listen( port ); console.log( 'server started http://localhost:' + port + '/' );
肝になるのはルーティングに認証フィルタを定義している箇所です。ここよりも前(上)で定義した内容には認証フィルタは有効にならないので、認証なしで使える API となります(つまり GET / と POST /authenticate は認証していなくても使えます)。
この POST /api/authenticate API でポストデータ user, password を受取り、その値が変数 users の中で定義されているいずれかの組み合わせと一致していれば 24H 有効なトークンが発行されます(これを以下の API 実行時にパラメータ指定します)。
一方、ここよりも後(下)で定義する内容には認証フィルタが有効になり、GET /api と GET /api/users は上記方法で取得したトークンがパラメータに設定されていないと正しく処理されない API となります。
実際にこのアプリケーションを実行し($ node app)、curl コマンドで挙動を確認してみましょう。まずは問題なく実行できるはずの GET / を実行します:
$ curl -XGET 'http://localhost:8080/'
Hello. The API is at http://localhost:8080/api
問題なく実行できました。次は認証フィルタをかけた GET /api を実行してみます:
$ curl -XGET 'http://localhost:8080/api'
{"success":false,"message":"No token provided."}
API を実行した結果、「トークンがない」時のエラーメッセージが表示されました。ここも期待通りに動いています(試しませんが、ユーザー一覧を取得する GET /api/users も同様のエラーになります)。
では POST /api/authenticate で認証してトークンを取得してみますが最初はわざとパスワードを間違えてみます:
$ curl -XPOST -H 'Content-Type:application/json' 'http://localhost:8080/api/authenticate' -d '{"name":"user1","password":"pass0"}' {"success":false,"message":"Authentication failed."}
先程同様にエラーになりましたが、エラーメッセージが「認証失敗」に変わりました。では改めて正しいパスワードを指定して実行してみます:
$ curl -XPOST -H 'Content-Type:application/json' 'http://localhost:8080/api/authenticate' -d '{"name":"user1","password":"pass1"}' {"success":true,"message":"Authentication successfully finished.","token":"XXXXXX...XXXXXX"}
今度は認証が成功しました。レスポンスにも "token" が含まれています。最後にここで返ってきた token の値を指定して、先程アクセスできなかった GET /api を実行してみます:
$ curl -XGET 'http://localhost:8080/api?token=XXXXXX...XXXXXX' {"message":"Welcome to API routing."}
今度は API が実行できました。同様にして GET /api/users も実行できるはずです:
$ curl -XGET 'http://localhost:8080/api/users?token=XXXXXX...XXXXXX'
[{"name":"user0","password":"pass0","admin":true},{"name":"user1","password":"pass1","admin":false},{"name":"user2","password":"pass2","admin":false},{"name":"user3","password":"pass3","admin":false}]
こんな感じで API の実行可否をトークンで制御できるようになりました。
今回の例ではソースコード内に静的に用意されたユーザー一覧を使って認証を行いましたが、実際の運用ではデータベース内に定義されたテーブル情報などを使うことになると思います。ただ JWT の基本的な考え方はこの1つのソースファイルだけで実現できているので、応用しやすいと思っています。
コメント