LoginSignup
29
26

More than 1 year has passed since last update.

node + expressでJWT (2021年1月)

Last updated at Posted at 2021-01-28

随分昔に同じ趣旨の記事を書いたのですが、再度書き直してみます(あまり変わってない)。

準備

作業場作成

ひとまず作業場所を確保し、必要なモジュールをインストール。body-parserはもういらない。

mkdir jwt-test
cd jwt-test
npm init -y
npm install express jsonwebtoken

実装前に検証

実装に入る前にJWTの生成と検証のコア部分をワンライナーで検証。

生成

なにやらややこしそうに思うが、[ヘッダ].[ペイロード].[署名]をそれぞれbase64でエンコードしているだけ。
署名部はsecret(ここではmy_secretという文字列)を利用してHS256で署名している。

node -e "console.log(require('jsonwebtoken').sign({username:'hoge'},'my_secret'))"

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImhvZ2UiLCJpYXQiOjE2MTE3OTA3Nzl9.MbcALpRUEu9KxGZ5S1qLoieb41_dr-i2o__QVnlVTow

サイン時に、 sign({username:'hoge'},'my_secret',, { expiresIn: '1h' })とすることで有効期限を設定可能。

検証

node -e "console.log(require('jsonwebtoken').verify('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImhvZ2UiLCJpYXQiOjE2MTE3OTA3Nzl9.MbcALpRUEu9KxGZ5S1qLoieb41_dr-i2o__QVnlVTow','my_secret'))"

{ username: 'hoge', iat: 1611790779 }

メモ

大規模?な利用シーンでは署名と検証に公開鍵技術を利用する場合もあると思うが、jsonwebtokenは対応しているよう。詳しくは本家サイトを見る。

実装

では、簡単なJWTを利用したAPI認証機能を実装する。主な機能は以下の感じ。

  • /loginにusername, passwordをPOSTで送信し、認証OKならtoken(JWT)を発行。
  • /protectedへのアクセスにはAuthorizationヘッダにBearer+tokenを付与し認証OKならコンテンツを戻す。
  • 認証機構はverifyToken()という外部関数とし実装し、app.get('/protecte')のミドルウエアとして適用する。

では実装します。

index.js
var express = require('express');
var app = express();
var jwt = require('jsonwebtoken');
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.listen(3000, function () {
    console.log("App start on port 3000");
})

//認証無しAPI
app.get('/', function (req, res) {
    res.json({ status: "OK" });
})

//認証+Tokenの発行
app.post('/login', function (req, res) {

    //ID,PW取得
    var username = req.body.username;
    var password = req.body.password;

    //認証
    //実際はDB等と連携
    if (username === "hoge" && password === "password") {
        //token生成(フォマットは適当だが、有効期限を設定)
        const token = jwt.sign({ username: username }, 'my_secret', { expiresIn: '1h' });
        res.json({
            token: token
        });
    } else {
        res.json({
            error: "auth error"
        });
    }

})

//認証有りAPI
app.get('/protected', verifyToken, function (req, res) {
    res.send("Protected Contents");
})

function verifyToken(req, res, next) {
    const authHeader = req.headers["authorization"];
    //HeaderにAuthorizationが定義されているか
    if (authHeader !== undefined) {
        //Bearerが正しく定義されているか
        if (authHeader.split(" ")[0] === "Bearer") {
            try {
                const token = jwt.verify(authHeader.split(" ")[1], 'my_secret');
                //tokenの内容に問題はないか?
                //ここでは、usernameのマッチと有効期限をチェックしているが必要に応じて発行元、その他の確認を追加
                //有効期限はverify()がやってくれるみたいだがいちおう・・・
                if (token.username === "hoge" && Date.now() < token.exp * 1000) {
                    console.log(token);
                    //問題がないので次へ
                    next();
                } else {
                    res.json({ error: "auth error" })
                }
            } catch (e) {
                //tokenエラー
                console.log(e.message);
                res.json({ error: e.message })
            }
        } else {
            res.json({ error: "header format error" });
        }
    } else {
        res.json({ error: "header error" });
    }
}

実装できたら実行。

node index.js

動作確認

curlを利用して動作確認をしてみます。

tokenの取得

まずは認証してtokenを取得します。

curl -s -X POST -H 'Content-Type: application/json' -d '{"username":"hoge","password":"password"}' http://localhost:3000/login

{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImhvZ2UiLCJpYXQiOjE2MTE3OTEyMDksImV4cCI6MTYxMTc5NDgwOX0.Gy_3r3T-AQG8iL28LE1xzVNMEJDKTtgTyMRiaSNpQiM"}

curl -X POST -d "username=hoge&password=password" http://localhost:3000/login でも可。

tokenの利用

取得したtokenを利用して/protectedにアクセスしてみます。

curl -X GET http://localhost:3000/protected -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImhvZ2UiLCJpYXQiOjE2MTE3OTEyMDksImV4cCI6MTYxMTc5NDgwOX0.Gy_3r3T-AQG8iL28LE1xzVNMEJDKTtgTyMRiaSNpQiM"

Protected Contents

うまく表示されました。

その他

リフレッシュはどうする?

調べ中。
expireの違う2つのtokenを発行し1つをrefresh tokenとして使うという実装例も。
https://medium.com/@had096705/build-authentication-with-refresh-token-using-nodejs-and-express-2b7aea567a3a

29
26
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
29
26