【備忘】firebase JWTをJavaScriptで検証してみた
Introduction
Googleの提供するmBaaS FirebaseのうちFirebase Authenticationは、クライアント側のコードだけでユーザー認証機能を実装できるサービスとして知られている。認証機能を自前で開発する手間・リスクを減らすことができ、個人的にはちょっとしたモノづくりの際に良くお世話になっています。
今回は、このツールを使ってクライアント側で認証した認証情報(JWT)をサーバー側のJavaScriptで検証する手順を備忘としてまとめます。firebaseのSDKを使えばより簡単に実装可能ですが、今回はそれを使用せず汎用的なJWT検証パッケージを使用して実装しました。
ユースケース
- クライアントからFirebase Authenticationサービスをcallし、認証をリクエスト
- Firebaseがリクエスト情報をチェックし、認証成功した場合に、JWTトークンを含む認証情報をクライアントに返却する
- クライアントはFirebaseから返却された認証用のJWTトークンを添えて、WEB APIをcallする
- サーバー側で、APIリクエスト上のJWTを検証 ← ここ
- 検証成功した場合のみ、後続処理を継続する
JWT検証ロジックの実装
作成物のイメージ
JWTトークンを引数とするJWT検証関数:Auth を作成。
検証に成功した場合はJWTのペイロード(ユーザー認証情報)情報を返却、認証に失敗した場合はエラーメッセージを返却する。
必要なパッケージを読み込み
JWTトークンの検証に使用するjsonwebtoken、WEB上に公開されている公開鍵を取得するために使用するhttp client:axios を最初に読み込む。
const axios = require("axios");
const jwt = require("jsonwebtoken");
公開鍵情報の取得
FirebaseのJWTの署名の検証(JWTの生成元が正しいかどうかの検証)に使用する公開鍵情報を取得します。公開鍵は ここ に公開されているので、axiosでこの情報をGETする。
//---------------------------
// Google公開鍵取得
//---------------------------
async function GetPubKey()
{
const GOOGLE_PUBKEY = "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com";
const axios_res = await axios.get(GOOGLE_PUBKEY);
if(axios_res.status == 200)
{
return axios_res.data;
}
return null;
}
公開鍵の特定
取得した公開鍵情報から、JWTの検証で使用する公開鍵を特定します。どの公開鍵を使用するかはJWTのヘッダー上の kid
で指定されているため、JWTヘッダをデコード・kidを特定します。
// トークンヘッダのデコード
const tokenHeader = Buffer.from(token.split('.')[0], "base64");
// kid取得
const kid = JSON.parse(tokenHeader)["kid"];
公開鍵情報から該当するkidのレコードを取得します。
// 公開鍵情報の取得
const pubKeyObj = await GetPubKey();
// トークンヘッダのkidに対応する公開鍵を取得
const pubKey = pubKeyObj[kid];
JWTの検証
公開鍵を使用しJWTを検証します。検証成功時はデコードしたユーザー認証情報を返却します。
const option = {
algorithms: 'RS256'
};
// トークン検証
return jwt.verify(token, pubKey, option,(error, decoded)=>{
if(error)
{
return {status:"9002", msg:"トークン検証エラー:" + error, body:null};
}
return {status:'0000', msg:"success", body:decoded};
})
完成品
ここまでの流れをまとめ・整理したコード。
//---------------------------
// app.js
//---------------------------
'use strict';
const axios = require("axios");
const jwt = require("jsonwebtoken");
const GOOGLE_PUBKEY = "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com";
main(process.argv[2]);
//---------------------------
// メイン関数
//---------------------------
function main(token)
{
// JWT認証
Auth(token).then((data)=>
{
console.log(JSON.stringify(data));
})
}
//---------------------------
// JWT認証
//---------------------------
async function Auth(token)
{
// 公開鍵オブジェクト取得
const pubKeyObj = await GetPubKey();
if(pubKeyObj == null)
{
return {status:"9000", msg:"公開鍵取得失敗", body:null};
}
// 公開鍵
let pubKey = null;
// 公開鍵の特定
try{
// トークンヘッダのデコード
const tokenHeader = Buffer.from(token.split('.')[0], "base64");
// kid取得
const kid = JSON.parse(tokenHeader)["kid"];
// トークンヘッダのkidに対応する公開鍵を取得
pubKey = pubKeyObj[kid];
}
catch(error)
{
return {status:"9001", msg:"トークンヘッダデコードエラー:" + error, body:null};
}
// トークン検証
const option = {
algorithms: 'RS256'
};
return jwt.verify(token, pubKey, option,(error, decoded)=>{
if(error)
{
return {status:"9002", msg:"トークン検証エラー:" + error, body:null};
}
return {status:'0000', msg:"success", body:decoded};
})
}
//---------------------------
// Google公開鍵取得
//---------------------------
async function GetPubKey()
{
const axios_res = await axios.get(GOOGLE_PUBKEY);
if(axios_res.status == 200)
{
return axios_res.data;
}
return null;
}
動作確認
引数にJWTトークンを渡して実行、結果を確認します。
node app.js <JWTを指定>
JWTは、Firebase SDKを組み込んだフロントエンド、もしくはFirebase Auth REST APIを使用して取得します。検証が成功すれば、ユーザー認証情報が返却・表示される、はず。
実装後の所感
JWT検証ロジックを使用し、少ないコード量で認証情報の検証が可能なことが分かった。ユーザー認証というセキュリティ的なリスクの大きい部分に対し自前実装を減らせることは安心感があります(もちろん使用する各種パッケージのアップデートの取込等、注意を払う必要はありますが)。
今回試したJWT検証機能をWEB APIに取り込んだ内容についても、また気が向いたら追記しようと思います。
参考文献
Comments