Quantcast
Viewing all articles
Browse latest Browse all 8859

【JWT(JSON Web Token)】Node.jsで実際に使ってみた

はじめに

認証方式の1つであるJWTについてのまとめと使用例

JWTとは

JSON Web Tokenの略
認証情報を含むJSONをbase64エンコードしたものに署名を付与したもの

利用例

  1. クライアント側から認証情報(例:ユーザー名、パスワード)をサーバーに送信
  2. サーバー側で認証情報を確認し、認証OKの場合JWTを発行し、クライアント側に返却
  3. クライアントは次回以降、JWTを付与したリクエストを送信
  4. サーバー側はJWTを検証する

なお、JWTの暗号化アルゴリズムは大きく分けて2種類ある。

  • 共通鍵方式
    HS256というアルゴリズムを使用する。
    認証サーバとリソースサーバが同じ場合はこの方式が使われることが多い。

  • 公開鍵/秘密鍵方式
    RS256というアルゴリズムを使用する。
    認証サーバとリソースサーバが別々の場合にこの方式が使われる。
    認証サーバに秘密鍵、リソースサーバに公開鍵が配置される。

JWTの構造

JWTの例は以下
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoidGFyb3UiLCJpYXQiOjE1OTIyNDAxODF9.vgsytL2KiAp-LXFSSmVXObia0bStoZqOCdYoEXdRaz8

JWT公式に貼り付けると内容がわかる

形式は[ヘッダー].[ペイロード].[署名]となる。

ヘッダー

{"alg":"HS256","typ":"JWT"}

署名の暗号化方式とトークンの種類を設定

ペイロード

{"user":"tarou","iat":1592240181}

実際のデータの中身
base64エンコードしているだけなので、パスワードとかの重要情報を含んではいけない。
上記の例以外にもトークンの有効期限や発行者などの情報を設定することもできる。

署名

ヘッダーとペイロードを鍵で暗号化した値
鍵はサーバー側で管理しておく。
署名を検証することによって、データの改ざんが行われていないかチェックすることができる。

実際に使ってみた

Node.js/ExpressでAPIを作ってみる。
作成するAPIは以下2つ

  • JWT発行API
  • 認証必須API

今回は共通鍵方式によるJWTで認証を実現する。

ソースの説明

全体像が分かったほうがいい方のために、ソース全部貼ります。

// ➀おまじないconstexpress=require("express");constjwt=require("jsonwebtoken");constPORT=3000;constapp=express();app.use(express.json())app.use(express.urlencoded({extended:true}));// ➁鍵constSECRET_KEY="abcdefg";// ➂JWT発行APIapp.post('/login',(req,res)=>{// 動作確認用に全ユーザーログインOKconstpayload={user:req.body.user};constoption={expiresIn:'1m'}consttoken=jwt.sign(payload,SECRET_KEY,option);res.json({message:"create token",token:token});});// ➃認証用ミドルウェアconstauth=(req,res,next)=>{// リクエストヘッダーからトークンの取得lettoken='';if(req.headers.authorization&&req.headers.authorization.split('')[0]==='Bearer'){token=req.headers.authorization.split('')[1];}else{returnnext('token none');}// トークンの検証jwt.verify(token,SECRET_KEY,function(err,decoded){if(err){// 認証NGの場合next(err.message);}else{// 認証OKの場合req.decoded=decoded;next();}});}// ➄認証必須APIapp.get('/user',auth,(req,res)=>{res.send(200,`your name is ${req.decoded.user}!`);});// ➅エラーハンドリングapp.use((err,req,res,next)=>{res.send(500,err)})// ➆サーバ起動app.listen(PORT,()=>console.info('listen: ',PORT));

ソース内の項番に沿って、説明します。

  • ➀おまじない
    「おまじない」という表現はあまり好きではないが、とりあえずここはExpressでサーバーを立ち上げるための記述なので、飛ばします。

  • ➁鍵
    暗号化に使用する鍵
    本来であれば、環境変数や別ファイルで管理すべきだが、今回は動作確認が目的なのでべた書き

  • ➂JWT発行API
    クライアント側はこのAPIを呼んで、JWTを発行してもらう。
    ここでは、有効期限が1分のJWTを発行して、レスポンスに含める。

  • ➃認証用ミドルウェア
    次にクライアント側からJWTが送られてきた際に、検証を行うミドルウェアを作成する。
    今回はリクエストヘッダのauthorizationにBearerスキームで送られてくる想定。
    ここでは、以下のケースで場合分けしている。
     「トークンがない場合」:➅エラーハンドリングに飛ぶ
     「トークン認証NGの場合」:➅エラーハンドリングに飛ぶ
     「トークン認証OKの場合」:➄認証必須APIに飛ぶ

  • ➄認証必須API
    クライアント側は➂JWT発行APIで発行されたJWTをリクエストヘッダのauthorizationにBearerスキームで設定してこのAPIを呼ぶ。
    app.get()の第二引数で➃認証用ミドルウェアを指定しているので、まずトークンの検証が行われ、認証OKの場合のみステータス200のレスポンスが返される。

  • ➅エラーハンドリング
    Expressの技術なので、詳しくは説明しないが、next(XXX)された場合、このエラーハンドリングが使われる。
    next()の引数で渡された値がerrに設定され、それをそのままクライアント側に返却している。

  • ➆サーバ起動
    これもExpressの技術なので、ここでは特に説明しません。

動作確認

APIサーバを起動します。

> node .\index.js
listen:  3000

Postmanを使って動作確認。

まずはJWT発行APIです。
Image may be NSFW.
Clik here to view.
image.png

トークンが返ってきました!

次にトークンの有効期限が切れないうちに認証必須APIを呼ぶ。
リクエストヘッダのauthorizationにBearerスキームでトークンを設定するのを忘れずに
Image may be NSFW.
Clik here to view.
image.png

自分の名前が返ってきました!
認証が成功した証。

次にトークンを改ざんして送ってみる。
Image may be NSFW.
Clik here to view.
image.png

ステータス500で無効なトークンとのメッセージが返ってきました!

次に有効期限切れの場合
もう1分経ったので、正しいトークンを送信しても…
Image may be NSFW.
Clik here to view.
image.png

期限切れのメッセージ!

完璧ですね。

ちなみに今回生成されたJWT。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoidGFyb3UiLCJpYXQiOjE1OTIzMjUxMzksImV4cCI6MTU5MjMyNTE5OX0.Uoqk6Yz129DKQCcvpSKhAw3Ncjln6ILucWAz_1ZLFhg

これをJWT公式に貼り付けると以下のようになる。
Image may be NSFW.
Clik here to view.
image.png

トークンの保存場所とサーバへの送信方法

いろんな方法があるよう
また、後述する脆弱性との兼ね合いもあり、何がベストプラクティスなのかは正直全く分かっていない。

  • サーバー側でcookieに保存して、そのままやりとりする
  • クライアント側はcookieもしくはweb strageに保存して、必要な場合のみリクエストヘッダに付与する
  • Authorizationに設定する場合は、Bearerスキームが一般的?
  • リクエストボディに入れてもいい

脆弱性

JWTについて調べていると、脆弱性の指摘について、いろいろな記事を見かけた。
ただ、自分の知識が足らず全てを理解することはできなかったので、以下にメモ程度として残しておく。

  • cookieに保存するとCSRFの恐れがある
  • web strageに保存するとXSSの恐れがある
  • cookie or web strageに保存して、使う場合だけリクエストヘッダに含める
  • 有効期限を過ぎるまで無効化する方法がないため、有効期限は極力短くすること
  • ということは、セッション管理などでは使えなさそう
  • 上記の脆弱性も様々な手法で回避できる?

思ったこと

JWTを理解することはそこまで難しくないし、実際に試すことも簡単だったが、
JWTを使いこなすには、OAuthなどの認証方式や、XSS・CSRFなどの攻撃手法などを理解する必要があり、結構ハードルが高そう。

参考


Viewing all articles
Browse latest Browse all 8859

Trending Articles