こうこく
作 ▸

Express 特定のパス以下でのみ認証が必要なWebAPIのサンプル

適当にライブラリとか使おうとしたら、そもそもミドルウェアとかルーティングとか全然分かってなくてダメだったので、勉強しました

Node.js v10.15.3express 4.17.1

以下のようなサンプルを作りました

  • ポート 8080 で待ち受ける
  • レスポンスはJSON文字列
  • GET /api/current は認証なしで、現在のタイムスタンプを返す
  • GET /api/auth/user は認証ありで、認証されてるユーザー情報を返す
  • 認証がダメならステータスコード401を返す

認証と言ってもここではヘッダ x-api-key の値が abc であるかチェックしてるだけだし、ユーザー情報もただの固定値です

必要なものインストール

npm install express body-parser

ソースコード

無理やり1つのファイルに収めてます

app.js
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const port = process.env.PORT || 8080;

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

// /api 以下アクセス時のルーター
const apiRouter = express.Router();

/**
 * GET /api/current
 * 現在のタイムスタンプを取得
 */
apiRouter.get('/current', function(req, res) {
  res.json({ current: (new Date()).getTime() });
});

// /api/auth 以下アクセス時のルーター
const closedApiRouter = express.Router();

/**
 * GET /api/auth/user
 * ログイン中のユーザー情報を取得
 */
closedApiRouter.get('/user', function(req, res) {
  res.json({ user: res.locals.user });
});

/**
 * 認証情報を検証するミドルウェア
 */
const authChecker = function(req, res, next) {
  // これはただのサンプルなのでリクエストヘッダの x-api-key == abc なら認証OKとする
  const apiKey = req.headers['x-api-key'];
  if (apiKey != 'abc') {
    // 認証NG
    res.status(401).json({ message: 'dame-' });
  } else {
    // 認証OK、レスポンスのローカル変数にユーザー情報を追加
    res.locals.user = {
      id: 111,
      name: 'kiriukun'
    };
    next();  // 次へ
  }
};

// /api 以下アクセス時は apiRouter に行くように
app.use('/api', apiRouter);

// /api/auth 以下アクセス時は authChecker を通ってから closedApiRouter に行くように
apiRouter.use('/auth', authChecker);
apiRouter.use('/auth', closedApiRouter);

// エラーハンドラ
app.use(function(err, req, res, next) {
  res.status(500).json({ error: err });
});

app.listen(port);
console.log('Listening on port ' + port);

動かしてみる

以下コマンドで起動します

node app.js

curlで叩いてみます

GET /api/current

コマンド
curl -i http://localhost:8080/api/current
レスポンス
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 25
ETag: W/"19-L2BRp/mt8WwkDPT2isR4T7Gkygk"
Date: Mon, 21 Oct 2019 12:46:02 GMT
Connection: keep-alive

{"current":1571661962948}

GET /api/auth/user (x-api-keyが正しい)

コマンド
curl -i -H "x-api-key: abc" http://localhost:8080/api/auth/user
レスポンス
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 37
ETag: W/"25-XOSwVlRNfVTdgRAWEbXKNWPByMw"
Date: Mon, 21 Oct 2019 13:40:45 GMT
Connection: keep-alive

{"user":{"id":111,"name":"kiriukun"}}

GET /api/auth/user (x-api-keyなし)

コマンド
curl -i http://localhost:8080/api/auth/user
レスポンス
HTTP/1.1 401 Unauthorized
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 19
ETag: W/"13-scK9zgQpAQgMGcEigkbAl+qwPqE"
Date: Mon, 21 Oct 2019 13:41:21 GMT
Connection: keep-alive

{"message":"dame-"}

ちゃんと401でエラーになってます

備考

特定のパス以下とかじゃなくて、単純に特定のメソッドだけ認証したかったら、Router.get() とかの第二引数にミドルウェアを渡せばいいらしい

/**
 * GET /api/auth/user
 * ログイン中のユーザー情報を取得
 */
closedApiRouter.get('/user', authChecker, function(req, res) {
  res.json({ user: res.locals.user });
});

参考にさせていただいた記事など

サルでも分かるExpressでのjsonAPIサーバーの作り方 - Qiita

Express のレスポンス関連メソッド「res.end()」「res.send()」「res.json()」の違い - Corredor

Express 4 への移行

この記事に何かあればこちらまで (非公開)