##はじめに
@ochiochi さんの記事に触発され、Node.jsでWebAPIの認証付きのMockWebAPI(JSON Server)を作成してみました。
##JSON Server
JSON Serverは、Node.jsのモジュールで、簡単にREST APIサーバが導入できます。
(@ochiochi さんの紹介)
JSON Serverは、Express.jsをベースにしているので、認証機能等を追加する場合には、Express.js用のプログラムを作成します。
JSON Server導入
1. node module
$ npm install json-server -g
2. Mock データベースファイルの作成
REST API によって、操作するデータベースとなるファイルをJSON形式で作成します。
{
"books": [
{
"id": 1,
"title": "Math",
"price": 1000
},
{
"id": 2,
"title": "Science",
"price": 1200
},
{
"id": 3,
"title": "Chemical",
"price": 1400
}
]
}
3. REST APIサーバ起動
$ json-server --watch ./books.json
デフォルトポート3000で、Webサーバが起動します。
method | url | request content type |
---|---|---|
GET | http://localhost:3000/books | |
GET | http://localhost:3000/books/1 | |
POST | http://localhost:3000/books | application/json |
PATCH | http://localhost:3000/books/1 | application/json |
PUT | http://localhost:3000/books/1 | application/json |
DELETE | http://localhost:3000/books/1 |
Webサーバサーバを停止する場合には、Ctrl + c
認証機能導入
1. node module
認証トークンの作成と、検証するためのモジュールを導入します。
$ npm install jsonwebtoken
2. Mock ユーザ情報データファイルの作成
ログインに必要なユーザ情報を用意します。このファイルは、認証時に使用する目的以外で公開しません。
{
"users": [
{
"id": 1,
"name": "hoge",
"email": "hoge@email.com",
"password": "hoge"
},
{
"id": 2,
"name": "foo",
"email": "foo@email.com",
"password": "foo"
},
{
"id": 3,
"name": "bar",
"email": "bar@email.com",
"password": "bar"
}
]
}
2. 認証REST APIプログラムの作成
プログラム内部で、JSON Serverを利用してREST APIサーバを利用します。
//モジュール参照
const fs = require('fs')
const bodyParser = require('body-parser')
const jsonServer = require('json-server')
const jwt = require('jsonwebtoken')
//JSON Serverで、利用するJSONファイルを設定
const server = jsonServer.create()
const router = jsonServer.router('./books.json')
//JSONリクエスト対応
server.use(bodyParser.urlencoded({extended: true}))
server.use(bodyParser.json())
//署名作成ワードと有効期限(1時間)
const SECRET_WORD = 'SECRET1234'
const expiresIn = '1h'
//署名作成関数
const createToken = payload => jwt.sign(payload, SECRET_WORD, {expiresIn})
//署名検証関数(非同期)
const verifyToken = token =>
new Promise((resolve, reject) =>
jwt.verify(token, SECRET_WORD, (err, decode) =>
decode !== undefined ? resolve(decode) : reject(err)
)
)
//ユーザDBファイル読み込み
const userdb = JSON.parse(fs.readFileSync('./users.json', 'UTF-8'))
//ログイン関数 true:ok false:ng
const isAuth = ({email, password}) =>
userdb.users.findIndex(user => user.email === email && user.password === password) !== -1
//ログインRouter
server.post('/auth/login', (req, res) => {
const {email, password} = req.body
//ログイン検証
if (isAuth({email, password}) === false) {
const status = 401
const message = 'Incorrect email or password'
res.status(status).json({status, message})
return
}
//ログイン成功時に認証トークンを発行
const access_token = createToken({email, password})
res.status(200).json({access_token})
})
//認証が必要なRouter(ログイン以外全て)
server.use(/^(?!\/auth).*$/, async (req, res, next) => {
//認証ヘッダー形式検証
if (req.headers.authorization === undefined || req.headers.authorization.split(' ')[0] !== 'Bearer') {
const status = 401
const message = 'Error in authorization format'
res.status(status).json({status, message})
return
}
//認証トークンの検証
try {
await verifyToken(req.headers.authorization.split(' ')[1])
next()
} catch (err) {
//失効している認証トークン
const status = 401
const message = 'Error access_token is revoked'
res.status(status).json({status, message})
}
})
//認証機能付きのREST APIサーバ起動
server.use(router)
server.listen(3000, () => {
console.log('Run Auth API Server')
})
3. 認証REST APIプログラムの起動
$ node auth-api-server.js
4. ログインAPI
ログインAPIを使用して、認証トークンを発行します。以降のREST APIアクセスは、認証トークンをヘッダーに付加します。
| method | url | request content type |
|:-:|:-:|:-:|:-:|
| POST | http://localhost:3000/auth/login | application/json |
- Request Body
{
"email": "hoge@email.com",
"password":"hoge"
}
- Response Body (200 OK)
{
"access_token": "XXXXXXXXXXXXXXXX"
}
- Response Body (401 Unauthorized)
{
"status": 401,
"message": "Incorrect email or password"
}
5. 認証付きのAPI
REST APIアクセスは、認証トークンをヘッダーに付加します。
- Request Header (required access_token)
Authorization: Bearer XXXXXXXXXXXXXXXX
認証トークンが失効していた場合
- Response Body (401 Unauthorized)
{
"status": 401,
"message": "Error access_token is revoked"
}
認証ヘッダー形式エラーの場合
- Response Body (401 Unauthorized)
{
"status": 401,
"message": "Error in authorization format"
}