OpenID Connectを超ざっくりな感じでまとめてみる

[Applibot Advent Calendar 2024](https://qiita.com/advent-calendar/2024/applibot) 16日目の記事です

# はじめに
みなさん普段からサードパーティ認証してますか？私はしてます。
最近は色んなサービスのログインにGoogleアカウント使ったり、Twitterアカウント使ったり出来るのでわざわざサービス毎にアカウントを作ってパスワードを覚えてなどしなくてよくて便利な時代になりました。
今回は初めて手元で認証周りを触る機会があったので、自分なりにざっくりとまとめてみました。

# この記事のゴール
OpenID Connectを通してサードパーティからID Tokenを受け取って検証するところまで

# OpenID Connectってなんぞや
OpenID ConnectとはOAuth2.0で定義されている認可フローに則りつつ、認証まで出来るようにした規格のことです。
詳しくは[こちら](https://openid-foundation-japan.github.io/openid-connect-basic-1_0.ja.html)を読んでください。
～ _Fin._ ～

……で終わるのは流石に雑すぎるのでもう少し解説します。

まずOAuth2.0というのは外部アプリケーションがあるサービス(GoogleやApple、Facebookなど)にアクセスするための認可のフローを規格化したフレームワークのことです。

通常、サービスに対してアクセスするためにはアクセストークンという文字列が必要になります。
このアクセストークンを鍵としてサービス側に渡すことで、ユーザーデータの取得を行ったり、SNS系のサービスであればソーシャルグラフの取得を行ったりなどをすることが出来るのですが、あらゆるアプリケーションがそれを無制限にできてしまうと困ったことになるので、サービスが認めた限定された外部アプリケーションのみに渡す必要があります。
このアクセストークンを渡す仕組みを規格化したものが先ほどのOAuth2.0となります。

ではその上でOpenID Connectとは何かというと、OAuth2.0に則ったフローを保ちながら認証も出来るようにした拡張規格となります。

OAuth2.0では認可の結果としてアクセストークンを取得しました。
これはこれでいいのですが、このアクセストークンには誰が認証したのかなどの情報は含まれていないため、これを振りかざして身分証明書のように使うことはできません。
そこで誰が認証したのかや署名による改ざん防止などをID Tokenというトークンに含めて、身分証明書のように使えるようにし、それをアクセストークンと一緒に発行しようと定めたのがOpenID Connectになります。

# 手順
[こちらのフロー](https://openid-foundation-japan.github.io/openid-connect-basic-1_0.ja.html#CodeFlow)で進めていきます。

###  認証リクエストを作って認可エンドポイントに投げる
まず認可エンドポイントってどこ？という話ですが、これは各サービスのOpenID Configurationに全て記載されています。(OpenID Configurationに関しては[こちら](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig)を参照してください。)
例えば、Googleであれば以下のURLから確認できます。
```
https://accounts.google.com/.well-known/openid-configuration
```
ここにアクセスするとJSON形式でいくつかパラメータがありますが、その中でも`authorization_endpoint`にあたる箇所が認可エンドポイントになるため、URLは以下の通りです。
```
https://accounts.google.com/o/oauth2/v2/auth
```
さて、その上でリクエストをするためにいくつかパラメータを指定する必要があり、必須のものだけを集めると以下になります。
| 名前 | 説明 |
|:-:|:-:|
| response_type | `code`と指定してください。認可の方法を表していて、OAuth2.0的には全部で4つのやり方があるのですが、OpenID Connectではそのうちの一つに乗っかっています |
| client_id | クライアントIDです。各サービスの管理画面から取得してください。 |
| scope | 要求するアクセス権限となります。スペースもしくはカンマ区切りで要求する権限を記していきますが、必ず`openid`を含めるようにしてください。また、サービス毎に固有のscope値が存在する場合があります。 |
| redirect_uri | レスポンスが返されるURIです。ここで指定するURIは全く同じものをサービス側にも登録する必要があります。 |
| state | リクエスト時とレスポンスを受け取った際に共通で把握できるランダム値です。CSRF対策に使用されます。 |

そしてリクエストの例が以下となります。
```
https://server.example.com/authorize?
    response_type=code
    &client_id=s6BhdRkqt3
    &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb
    &scope=openid%20profile
    &state=af0ifjsldkj
```

### 認可コードを貰う
無事認証が終わるとリダイレクトされてレスポンスが帰って来ます。
成功時は以下のパラメータが上で指定した`redirect_uri`の後ろにくっついてきます
| 名前 | 説明 |
|:-:|:-:|
| code | 認可コードです。次のセクションで使います。 |
| state | 上で送ったstateがそのまま送られてくるはずです。<br>全く同じであることを検証してください。 |

### 認可コードをトークンエンドポイントに投げる
認可コードだけ貰ってもしょうがないので、これをID TokenとAccess Tokenに交換してもらう必要があります。

まずトークンエンドポイントに関してですが、これも認可エンドポイントと同じようにOpenID Configurationに記載されています。
同じようにGoogleを例にとると`token_endpoint`の値がそれにあたるため、URLは以下になります。
```
https://oauth2.googleapis.com/token
```

次にリクエストのパラメータですが、以下を指定する必要があります。
| 名前 | 説明 |
|:-:|:-:|
| grant_type | 権限付与の種別です。ここでは`authorization_code`を指定してください。 |
| code | 上で受け取った認可コードを指定してください。 |
| redirect_uri | 認可時で指定したものと同じものを指定してください。 |

リクエストの例は以下となります
```http
POST /token HTTP/1.1
  Host: server.example.com
  Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
  Content-Type: application/x-www-form-urlencoded

  grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
  &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
```

### ID TokenとAccess Tokenを貰う
レスポンスとして下記のパラメータが返ってきます
| 名前 | 説明 |
|:-:|:-:|
| access_token | アクセストークン |
| token_type | トークンの種別です。ここでは必ず`Bearer`が返ってきます。 |
| id_token | ID トークン |

### ID Tokenを検証する
最後は返ってきたID Tokenが本物なのかを検証する必要があります。
検証するにあたってID Tokenの構成について知っておく必要があります。

ID TokenはJWT(JSON Web Token)形式の文字列で、ドットで区切られたヘッダ、ペイロード、署名の3つの部分から構成されています。

特にペイロードに関しては以下のパラメータを含んでおり、これらを元に検証を行います。
| 名前 | 説明 |
|:-:|:-:|
| iss | Issuerの識別子 |
| sub | ユーザーの識別子 |
| aud | トークンの発行対象の識別子 |
| exp | トークンの有効期限 |
| iat | 発行日時 |

具体的な手順に関してはそのまま引用する形になってしまうので[こちら](https://openid-foundation-japan.github.io/openid-connect-basic-1_0.ja.html#IDTokenValidation)を参照してください。

# まとめ
かなり駆け足のまとめになってしまい恐縮ですが、以上が一連の流れになります。
本当は軽く実装まで書けるとよかったのですが、この記事を書き終わったのがかなりギリギリで体力の限界でした。
もっと早めから記事を書き始めるべきだったかもしれない……

明日は[@yuucu](https://qiita.com/yuucu)さんになります！