Web APIを扱う場合、認証/認可についてある程度理解していないと正しく扱うことができません。
弊社の非エンジニアから質問されたことをきっかけに、自分自身の整理も兼ねてまとめておきます。
少しだけLINEログインと絡めて説明します。
認証/認可とは
- 認証 Authentication
- それが誰であるかを確認すること。
- 認可 Authorization
- 何らかの情報に対するアクセス権限を確認すること。
例えると、認証は「証明書による本人確認」、認可は「チケット(切符)の発行」です。
2つを分離して考えると、認証は本人確認であってそれによって何が行えるということはなく、認可は権限があるというだけでそれを使うのが誰かには関与しません。
引用:ウェブアプリにLINEログインを組み込む | LINE Developers
LINEログインでいうと
- 認証
- 誰であるかを確認する。
- 認可
- アプリケーションに対するアクセス権限を設定する。
処理の流れとしては、
- LINEログインのためのURLをクリック。
- LINEのログインを実施(認証)。
- ログインしてなければメールパスワード認証やQRコードログインを行わせる。
- ログインしていれば、自動ログインやSSOログインを行う。
- 認証できたら認可の画面で、アプリがLINEの情報を使うことを許可するかどうかをユーザーに確認する。
- 許可されたらその情報を用いてアプリでユーザーの情報を取得できる。
OAuth 2.0とOpen ID Connect
認証と認可を行う標準的な方法としてよく使われているのが、OAuth 2.0やOpenID Connectというプロトコルです。
LINEログインでもこれらに基づいたフローが実装されています。
OAuth 2.0
サードパーティアプリがHTTPサービスよる限定的なアクセスを可能にする認可フレームワーク。
私が以前作ったGoogle Photosに保存されている写真を取得して自動バックアップを行うアプリはOAuth 2.0によりサーバーに権限を付与して自動でバックアップを取得させています。
OpenID Connect
OAuth 2.0は本人であるかどうかを確認する術では本来ないのですが、使われ方として本人かどうかを確認したいというケースが多いです。
そのため、IDに関する情報を付与して、必要最低限のプロフィール情報を取得できるようにしたものがOpenID Connectです。
LINEログインにおけるユーザー情報の取得
上記のOpenID Connectの仕様に基づいて、LINEログインを行った後はid_tokenというものが渡ってきます。
このid_tokenはJWT(JSON Web Token)という仕様で渡ってきます。
{
"access_token": "bNl4YEFPI/hjFWhTqexp4MuEw5YPs...",
"expires_in": 2592000,
"id_token": "eyJhbGciOiJIUzI1NiJ9...",
"refresh_token": "Aa1FdeggRhTnPNNpxr8p",
"scope": "profile",
"token_type": "Bearer"
}
このid_token eyJhbGciOiJIUzI1NiJ9...
の部分を所定の方法でデコードして検証すると以下のような情報に変換できます。
{
"iss": "https://access.line.me",
"sub": "U1234567890abcdef1234567890abcdef",
"aud": "1234567890",
"exp": 1504169092,
"iat": 1504263657,
"nonce": "0987654asdf",
"amr": [
"pwd"
],
"name": "Taro Line",
"picture": "https://sample_line.me/aBcdefg123456",
"email": "taro.line@example.com"
}
引用:IDトークンからプロフィール情報を取得する | LINE Developers
Firebase/php-JWTの仕様
弊社のプロダクトの一部ではJWTの検証にphp-JWTを使用しています。
JWTの検証をWebアプリに組み込む場合は、公開されているライブラリを使うことをおすすめします。
言語ごとに以下のサイトでライブラリを調べる事ができます。
本題のFirebase/php-JWTのリポジトリは以下です。
具体的にはFirebase/JWT/decodeという関数を使用してデコードしています。
この中でやっていることをまとめておきます。
JWTの仕様と前提知識
JWTは.
で3つのセクションに区切られてやってきます。<ヘッダ>.<ペイロード>.<シグネチャー>の形式で渡ってきます。
例)
- base64エンコード
- エンコードとデコードは暗号化と復号に似ていますが、base64だということを知っていれば誰でも変換が可能です。
- データをある規則で文字列に置き換えることをエンコード、それをデータに戻すことをデコードといいます。
- 暗号化と似ていると言いましたが、暗号化のプロセスの中にbase64エンコードは大体入っています。
- ヘッダー
- JWTであるという情報と署名アルゴリズムが書かれています。
- ペイロード
- 実際のデータが入っているところです。
- nbf(Not Before) : JWTが有効になる日時
- iat(Issued At) : JWTの発行時間
- exp(Expiration Time) : JWTの有効期限
- 実際のデータが入っているところです。
- シグネチャー
- 検証用の署名
- HS256の場合、以下のように検証します。
- base64エンコードされたヘッダーとペイロードをピリオドで連結した文字列をチャネルシークレットを鍵としてハッシュ化する。
- これがデコードされたシグネチャーと一致するかを確認して、改ざんされていないかを検証する。
- HS256の場合、以下のように検証します。
- 検証用の署名
Firebase/JWT/decodeの処理
- 現在時刻を取得する。
- 必要な情報が渡ってきているか、形式がおかしくないかなどを確認する。
- ヘッダー、ペイロード、シグネチャに分けて、それぞれbase64デコードする。
- ヘッダーのアルゴリズムを確認して、渡ってきた鍵を使ってシグネチャを検証する
- ペイロードの中にnbtがもしあれば、現在時刻に
leeway
という設定値を足した時間よりあとになっているかを確認する。
→ つまりJWTの有効日時よりあとかどうかを時刻誤差leeway
を許容しつつ確認する。 - ペイロードの中にiatがもしあれば、現在時刻に
leeway
という設定値を足した時間よりあとになっているかを確認する。
→ つまりJWTの発行時間よりあとかどうかを時刻誤差leeway
を許容しつつ確認する。 - ペイロードの中にexpがもしあれば、現在時刻から
leeway
を引いた時間がexpより前であるかを確認する。
→ つまりJWTの有効期限を時刻誤差leeway
を許容しつつ確認する。 - 全部OKだったらペイロード部分を返却する。
おわりに
認証と認可についてまとめてみました。
深く理解するには、ネット上にたくさん文献があるのでそれを読むのが良さそうです。
個人的にJWTの検証部分を修正した際に、nbtやiatの意味がよくわかっておらず苦戦したので同じような方の助けになればと思います。