【PHP】認証と認可のちがいとJWTの検証について

Web APIを扱う場合、認証/認可についてある程度理解していないと正しく扱うことができません。
[弊社](https://www.milogos.co.jp/)の非エンジニアから質問されたことをきっかけに、自分自身の整理も兼ねてまとめておきます。
少しだけLINEログインと絡めて説明します。

# 認証/認可とは

-  認証 Authentication
    -  それが誰であるかを確認すること。
- 認可 Authorization
    - 何らかの情報に対するアクセス権限を確認すること。

例えると、認証は「証明書による本人確認」、認可は「チケット(切符)の発行」です。
2つを分離して考えると、認証は本人確認であってそれによって何が行えるということはなく、認可は権限があるというだけでそれを使うのが誰かには関与しません。

![0d3470eb-063c-4220-91ac-890d8952b897.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141823/fa8ba5e6-502f-0e1e-1d8b-deb6fed79544.png)
引用:[ウェブアプリにLINEログインを組み込む | LINE Developers](https://developers.line.biz/ja/docs/line-login/integrate-line-login/)

##  LINEログインでいうと

- 認証  
    - 誰であるかを確認する。

![f9303205-a23e-4ec0-a1b0-623730b525e3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141823/b65e0277-926f-3dae-7db2-e91e6832bc91.png)

- 認可  
    - アプリケーションに対するアクセス権限を設定する。

![c8b754f1-f31a-4c43-867d-05821a917aa9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141823/112591cb-fe0d-9256-b71d-b376a1fe9b22.png)

処理の流れとしては、

- 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**によりサーバーに権限を付与して自動でバックアップを取得させています。

- [【Python】GooglePhotosAPIで写真・動画のバックアップを取得する方法 - Qiita](https://qiita.com/tokkun5552/items/38b4d678ccc3e2f796fa)
![69c46ebc-0730-4a0e-839d-d30a507ed1e0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141823/b9153d6a-dcd4-ffff-7742-7ccc56e7d867.png)

## OpenID Connect

OAuth 2.0は本人であるかどうかを確認する術では本来ないのですが、使われ方として本人かどうかを確認したいというケースが多いです。
そのため、IDに関する情報を付与して、必要最低限のプロフィール情報を取得できるようにしたものがOpenID Connectです。

# LINEログインにおけるユーザー情報の取得

上記のOpenID Connectの仕様に基づいて、LINEログインを行った後はid_tokenというものが渡ってきます。
このid_tokenはJWT(JSON Web Token)という仕様で渡ってきます。

```json:response
{
    "access_token": "bNl4YEFPI/hjFWhTqexp4MuEw5YPs...",
    "expires_in": 2592000,
    "id_token": "eyJhbGciOiJIUzI1NiJ9...",
    "refresh_token": "Aa1FdeggRhTnPNNpxr8p",
    "scope": "profile",
    "token_type": "Bearer"
}
```

このid_token `eyJhbGciOiJIUzI1NiJ9...`の部分を所定の方法でデコードして検証すると以下のような情報に変換できます。

```json:JWTの例
{
    "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"
}
```

![061c21a8-2a78-4a29-94b2-a253f4791ae6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141823/98ce2416-59ec-3ee1-5373-b84d8667ee89.png)
引用：[IDトークンからプロフィール情報を取得する | LINE Developers](https://developers.line.biz/ja/docs/line-login/verify-id-token/)

#  Firebase/php-JWTの仕様

弊社のプロダクトの一部ではJWTの検証にphp-JWTを使用しています。
JWTの検証をWebアプリに組み込む場合は、公開されているライブラリを使うことをおすすめします。
言語ごとに以下のサイトでライブラリを調べる事ができます。

- [JSON Web Tokens - jwt.io](https://jwt.io/#libraries-io)

本題のFirebase/php-JWTのリポジトリは以下です。

- [GitHub - firebase/php-jwt: PHP package for JWT](https://github.com/firebase/php-jwt)

具体的には[Firebase/JWT/decodeという関数](https://github.com/firebase/php-jwt/blob/main/src/JWT.php#L84)を使用してデコードしています。
この中でやっていることをまとめておきます。

## JWTの仕様と前提知識

JWTは`.`で3つのセクションに区切られてやってきます。<span style="color: #069417"><ヘッダ></span>.<span style="color: #d61b09"><ペイロード></span>.<span style="color: #9e8c08"><シグネチャー></span>の形式で渡ってきます。
例）
![スクリーンショット 2022-01-27 13.46.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141823/c032bde2-c492-ff5d-18c1-9f7063f3e9bc.png)

- base64エンコード
    - エンコードとデコードは暗号化と復号に似ていますが、base64だということを知っていれば誰でも変換が可能です。
    - データをある規則で文字列に置き換えることをエンコード、それをデータに戻すことをデコードといいます。
    - 暗号化と似ていると言いましたが、暗号化のプロセスの中にbase64エンコードは大体入っています。
 
![40b8e899-63a8-4bd7-b826-70ba03ecae10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141823/068db612-41bd-fee5-b1de-359a45550c80.png)
引用：[JSON Web Tokens - jwt.io](https://jwt.io/#libraries-io)
    
- ヘッダー
    - JWTであるという情報と署名アルゴリズムが書かれています。
- ペイロード
    - 実際のデータが入っているところです。
        - nbf(Not Before) : JWTが有効になる日時
        - iat(Issued At) : JWTの発行時間
        - exp(Expiration Time) : JWTの有効期限
- シグネチャー
    - 検証用の署名
        - HS256の場合、以下のように検証します。
            - base64エンコードされたヘッダーとペイロードをピリオドで連結した文字列をチャネルシークレットを鍵としてハッシュ化する。
            - これがデコードされたシグネチャーと一致するかを確認して、改ざんされていないかを検証する。

## Firebase/JWT/decodeの処理
    
1. 現在時刻を取得する。
- 必要な情報が渡ってきているか、形式がおかしくないかなどを確認する。
- ヘッダー、ペイロード、シグネチャに分けて、それぞれbase64デコードする。
- ヘッダーのアルゴリズムを確認して、渡ってきた鍵を使ってシグネチャを検証する
- ペイロードの中にnbtがもしあれば、現在時刻に`leeway`という設定値を足した時間よりあとになっているかを確認する。  
→ つまりJWTの有効日時よりあとかどうかを時刻誤差`leeway`を許容しつつ確認する。
- ペイロードの中にiatがもしあれば、現在時刻に`leeway`という設定値を足した時間よりあとになっているかを確認する。  
→ つまりJWTの発行時間よりあとかどうかを時刻誤差`leeway`を許容しつつ確認する。
- ペイロードの中にexpがもしあれば、現在時刻から`leeway`を引いた時間がexpより前であるかを確認する。  
→ つまりJWTの有効期限を時刻誤差`leeway`を許容しつつ確認する。
- 全部OKだったらペイロード部分を返却する。

# おわりに

認証と認可についてまとめてみました。
深く理解するには、ネット上にたくさん文献があるのでそれを読むのが良さそうです。
個人的にJWTの検証部分を修正した際に、nbtやiatの意味がよくわかっておらず苦戦したので同じような方の助けになればと思います。
    
## 参考

- [一番分かりやすい OAuth の説明 - Qiita](https://qiita.com/TakahikoKawasaki/items/e37caf50776e00e733be)
- [[レポート] 実装して理解するLINE LoginとOpenID Connect入門 に参加してOAuthとOpenID Connectについても学んできた #linedc | DevelopersIO](https://dev.classmethod.jp/articles/mrmo-linelogin-20190315/)
- [実装して理解するLINE LoginとOpenID Connect入門](https://www.slideshare.net/naohiro.fujie/line-loginopenid-connect)
- [OAuth 認証を真面目に考える | DevelopersIO](https://dev.classmethod.jp/articles/deep-think-about-oauth-authentication/)
- [よくわかる認証と認可 | DevelopersIO](https://dev.classmethod.jp/articles/authentication-and-authorization/)
- [JSON Web Tokens - jwt.io](https://jwt.io/#libraries-io)