【前編】OAuth2.0の勉強でAuthorization Code Grantをクライアント側として体感してみた

## はじめに
認証と認可に関しては、OpenID Connectがデファクトな状況だと思われる。今回はOAuth2.0を拡張した仕様であるOpenID Connectで定義されている、認可のフローであるAuthorization Code Grantについて、認可をもらう側（クライアント側）としてフローを実行する事で、理解を深めてみたいと思う。

流れとしては、

1. 認可エンドポイントへ認可リクエスト
1. 認証（今回はGoogleのOpenID Connectを使うので、Googleの認証）と認可
1. リダイレクトするので、それをserverで受け取りトークンエンドポイントへアクセストークンをリクエスト

の中の、1と3の部分を実際に実装する。
※今回はあえてCSRF対策やPKCE対策に必要になるパラメータを省略している。CSRF対策やPKCE対策については今後記事を執筆予定。

※今回、Google Cloud PlatformでGoogle APIsの設定をしてAuthorization Code Flowを体感する。その際料金はかからない想定で本記事は書いている（無料枠があるので）。詳細は[Google Cloud の無料プログラム](https://cloud.google.com/free/docs/gcp-free-tier?skip_cache=true&hl=ja#free-trial)を参照。ただし、Authorization Code Flowで取得したアクセストークンを使ってAPIを呼び出すが、その際に選んだAPIによっては課金される場合もあるため注意。本記事の内容を実践して万が一課金されても責任は負えません。Google Cloud Platformの利用にかかる課金やその他の事由については自己責任でお願い致します。）

※本記事中で筆者の理解に誤りがあればご指摘頂けると幸いです。

※今回、文章量が多くなってしまったため、記事を前編と後編の2つに分割した。前編では、「OpenID Connectとは？という話からAuthorization Code Flowを体感するための事前準備まで」を書いている。後編では、「実際にクライアント側としてAuthorization Code Flowを実装し、アクセストークンを用いてAPIを実行するまで」を書いている。

ソースコード全体は以下。

https://github.com/yuta-katayama-23/openid-connect-client-demo/tree/3f558f92728898394fac595a8ba20eca8407f6fb

## 今回利用するOpenID Provider
https://developers.google.com/identity/protocols/oauth2/openid-connect

## OAuth2.0？OpenID Connect？
早速だが、記事のタイトルではOAuth2.0の勉強と言いつつ、OpenID Connectという単語が出てきて？？となるかもしれないので、ここでOAuth2.0とOpenID Connectについて私が理解している範囲で整理してみたいと思う。

※ただしOpenID Connectがなぜできたのか？という話については触れない。それについては[OAuth 2.0 + OpenID Connect のフルスクラッチ実装者が知見を語る　認証と認可](https://qiita.com/TakahikoKawasaki/items/f2a0d25a4f05790b3baa#%E8%AA%8D%E8%A8%BC%E3%81%A8%E8%AA%8D%E5%8F%AF)や[単なる OAuth 2.0 を認証に使うと、車が通れるほどのどでかいセキュリティー・ホールができる](https://www.sakimura.org/2012/02/1487/)を参照。

### 前提として…認証・認可とは？
よく混乱しがちな認証と認可の概念ついて、OAuth2.0やOpenID Connectを理解する上では重要になるので、まずは認証と認可の概念について理解する所からスタートしたいと思う。

認証・認可について理解するには、[OAuth 2.0 + OpenID Connect のフルスクラッチ実装者が知見を語る　認証と認可](https://qiita.com/TakahikoKawasaki/items/f2a0d25a4f05790b3baa#%E8%AA%8D%E8%A8%BC%E3%81%A8%E8%AA%8D%E5%8F%AF)を参照頂くと具体的に概念としてどう違うのか？が分かりやすいと思う。端的に言ってしまうと、以下のようになるだろう。

- 認証とは<br>「誰であるか？」を確かめる仕組みで、本人確認をする時に用いられる
- 認可とは<br>「誰が誰に何の権限を与えるか？」に関する仕組みで、権限を委譲する時に用いられる（ここでいう委譲は、その相手が人とは限らずアプリケーションである事もある）

つまり、認証と認可はそもそも別の概念である。

### OAuth2.0とは？
ここからはそれぞれ（OAuth2.0とOpenID Connect）について、何者なのか？を見ていく。

[一番分かりやすい OAuth の説明](https://qiita.com/TakahikoKawasaki/items/e37caf50776e00e733be)の記事を参照するのが分かりやすい。記事を読むと分かるが、OAuth2.0とは、**認可の仕組み**である。具体的には、「誰が誰に何の権限を与えるか？」に関する仕組みで、権限を委譲する時に用いられる。ここでいう委譲は、その相手が人とは限らずアプリケーションである事もある。

実際の仕様としては[RFC6749](https://datatracker.ietf.org/doc/html/rfc6749)にそれが定義されている。

※認可の仕組みであるというのはRFC6749（[3.1.  Authorization Endpoint](https://datatracker.ietf.org/doc/html/rfc6749#section-3.1)）にもちゃんと明記されており、

> The authorization server MUST first verify the identity of the resource owner. The way in which the authorization server authenticates the resource owner (e.g., username and password login, session cookies) is beyond the scope of this specification.（認可サーバーは、最初にリソース所有者の身元を確認しなければならない(MUST)。認可サーバーがリソース所有者を認証する方法(例: ユーザー名とパスワードによるログイン、セッションクッキー)は、この仕様の範囲外である。）

と書かれている。

### OpenID Connectとは？
[一番分かりやすい OpenID Connect の説明](https://qiita.com/TakahikoKawasaki/items/498ca08bbfcc341691fe)の記事を参照するのが分かりやすい。記事を読むと分かるが、OpenID Connectとは、OAuth2.0を拡張したもので、OAuth2.0の認可仕組み（フロー）を流用してIDトークンと呼ばれる認証情報（本人確認されたという事実とそのユーザーの情報）を発行するための仕様を定めたもの。

OpenID ConnectはOAuth2.0を拡張したものなので、

```
OpenID Connect 1.0 is a simple identity layer on top of the OAuth 2.0 protocol.

(Identity, Authentication) + OAuth 2.0 = OpenID Connect
```

というような表現をされ、**認証＋OAuth2.0＝OpenID Connect**である。そのためOpenID Connectには認可の仕組みも含むものになっている（認可の仕組みについては、OAuth2.0のRFC6749そのままの部分ももちろんある。具体的にはOpenID Connectの[`response_type=code かつ scopeにopenidを含まない`のパターン](https://qiita.com/TakahikoKawasaki/items/4ee9b55db9f7ef352b47#1-response_typecode)）。

OpenID Connectの実際の仕様（主要部分）は、[OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html)に定義がされている（その他にも[OpenID Connect Discovery 1.0](https://openid.net/specs/openid-connect-discovery-1_0.html)などあり、全てが`OpenID Connect Core 1.0`に書かれているわけではない）。

- 参考：[一番分かりやすい OAuth の説明](https://qiita.com/TakahikoKawasaki/items/e37caf50776e00e733be)
- 参考：[一番分かりやすい OpenID Connect の説明](https://qiita.com/TakahikoKawasaki/items/498ca08bbfcc341691fe)
- 参考：[OAuth 2.0 + OpenID Connect のフルスクラッチ実装者が知見を語る](https://qiita.com/TakahikoKawasaki/items/f2a0d25a4f05790b3baa)
- 参考：[OAuth 2.0 全フローの図解と動画](https://qiita.com/TakahikoKawasaki/items/200951e5b5929f840a1f)
- 参考：[OpenID Connect 全フロー解説](https://qiita.com/TakahikoKawasaki/items/4ee9b55db9f7ef352b47)

### 今回OAuth2.0の勉強なのにOpenID Connectなの？に対する答え
上記で見てきたように、OpenID Connectの仕様で実装されたサーバ（OpenID Provider）を利用すると、そのサーバには認証・認可の両方の機能が備わっており、認可はOAuth2.0の仕様を満たすものなので、OpenID Connectを利用してOAuth2.0の勉強をしていく。

※OpenID Connectを利用する理由には、今後実際にOpenID ConnectでIDトークンの発行（Implicit Flow）などもやってみて理解を深めていく予定もあるので、単にOAuth2.0の認可サーバではなく、OpenID Connectの認証・認可サーバで勉強するという意図もある。

## 実際に実装するもののフロー
[はじめに](#はじめに)で少し触れたが、ここでもう一度実装しようとしている、Authorization Code Grant(Flow)のクライアント側の動き（
どんなフローなのか？）を図示して整理してみる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/3aa36edd-1375-c422-43cb-1534dffbd73e.png)

今回は上記の図のフローの中で

- ①・②：認可リクエストを送る部分
- ⑦を受け取るserverのエンドポイント：redirect_uriのエンドポイント
- ⑧・⑨：トークンエンドポイントへトークンのリクエストを送る部分

を実装する事になる。

※上記の`Google認証・認可サーバ`の部分で、`認可エンドポイントのサーバ`と`トークンエンドポイントのサーバ`という2つを描いているが、これはOpenID Discoveryのエンドポイント https://accounts.google.com/.well-known/openid-configuration を見ると認可エンドポイントとトークンエンドポイントのサーバのホストが違う事が分かったので（2022/01/27時点）、サーバを2つに分けて描いている。

```
...
"authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth",
...
"token_endpoint": "https://oauth2.googleapis.com/token",
...
```

この辺りは、OpenID Providerによっては同じサーバに認可エンドポイントもトークンエンドポイントもある場合もあり、Providerによって違うものらしい。Yahoo Japan!のOpenID Providerの方（https://auth.login.yahoo.co.jp/yconnect/v2/.well-known/openid-configuration ）はそれぞれ同じサーバである（2022/01/27時点では）。

```
"authorization_endpoint": "https://auth.login.yahoo.co.jp/yconnect/v2/authorization",
"token_endpoint": "https://auth.login.yahoo.co.jp/yconnect/v2/token",
```

- 参考：[OAuth 2.0 全フローの図解と動画　1. 認可コードフロー](https://qiita.com/TakahikoKawasaki/items/200951e5b5929f840a1f#1-%E8%AA%8D%E5%8F%AF%E3%82%B3%E3%83%BC%E3%83%89%E3%83%95%E3%83%AD%E3%83%BC)
- 参考：[OAuth 2.0 の認可レスポンスとリダイレクトに関する説明](https://qiita.com/TakahikoKawasaki/items/8567c80528da43c7e844)
- 参考：[The Discovery document](https://developers.google.com/identity/protocols/oauth2/openid-connect?hl=en#discovery)
- 参考：[Yahoo　OpenID Configurationエンドポイント](https://developer.yahoo.co.jp/yconnect/v2/hybrid/configuration.html)

## 事前準備
Authorization Code Flowについて理解を深めるために、今回はGoogle CalendarのデータをOAuth2.0の仕組みで取得するWebアプリを作成してみる。Google CalendarのAPIを実行できるようにするにはいくつか事前準備が必要なのでその準備を行う。以下で1つずつ見ていく。

※公式で言うと、[Authorizing Requests to the Google Calendar API](https://developers.google.com/calendar/api/guides/auth)に書かれている内容が準備の内容になる。

また、今回はGoogleの認可サーバを利用するが、Googleの認可サーバではクライアントを作成する時に指定する事になるredirect_uriを持つアプリ（今回はWebアプリ）はhttpsである必要がある。さらにIPでの指定はできずパブリックトップレベルドメイン（.com、.org など）にする必要がある。そのための実装・設定も取り上げる。

### APIを有効化する
まずは使うAPI（[CalendarList: list ](https://developers.google.com/calendar/api/v3/reference/calendarList/list)）を有効化する。

Google Cloud Platformにアクセスして`APIとサービス`のページを開き、`APIとサービスの有効化`をクリックする。

| 1 | 2 |
|:-:|:-:|
| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/60ea5d25-7e23-a292-835e-af79df3c025c.png)  | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/28cd7136-65ae-c880-dae3-97c8ac44fdaa.png)  |

検索の部分で`calendar`と入力してEnterを押下すると、以下の図のように`Google Calendar API`というのが表示されるので、これをクリックして`有効にする`から有効にする。

| 3 | 4 |
|:-:|:-:|
| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/31df0dc0-50cf-b908-dea7-77796313b65e.png)  | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/064374d2-e682-7f29-d981-aa8c983ad7ed.png)  |

### OAuth同意画面を設定する
続いて、認可画面に表示される内容とユーザが認可するscopeの種類を登録する。上記の[APIを有効化する](#apiを有効化する)で開いた`APIとサービス`の`OAuth同意画面`のメニューをクリックして、設定を行っていく（ここで設定する内容はユーザが実際に認可を行う際に表示される画面に表示される内容になる）。

外部を選択し、作成を行う。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/19ff220a-f44c-69ec-197a-730062451cef.png)

続けて各Stepで設定を行っていく。

※今回はAPIとしては、CalendarList: listを呼び出すが、このscopeとしては[CalendarList: list　Authorization](https://developers.google.com/calendar/api/v3/reference/calendarList/list#auth)に書かれているscopeが必要なのでそれをStep2の`スコープ`で設定する。
※アプリケーションのホームページ・プライバシーポリシーのリンク・利用規約のリンクなどは設定しなくても問題ない。

| 1 | 2 |
|:-:|:-:|
| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/16613d2d-d371-bfd7-94dd-1d648820c3e1.png)![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/4c24d9f1-63f4-2ba4-33bf-09e8bc57793b.png) | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/c5676ac7-2c72-35bd-9735-d1d57ce9c1ea.png)![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/9437b30e-074a-ce2a-fdab-a9ae2a0af7a4.png)![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/0f0e3cc6-7dd5-9c0e-1da8-2b5f9d7fd55f.png) |

| 3 | 4 |
|:-:|:-:|
| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/fe96bd57-26c7-c7fb-8983-0321114ea1de.png) | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/d2a50e42-0c96-8a6c-400b-5917d0b96985.png)![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/bc409807-330e-4769-4662-39b69a419b78.png) |

設定できたらOK。

### クライアントを作成する
続いてクライアントを登録する。実際にAuthorization Code Flowを実行する時に必要となる情報（client_idやclient_secret）はここで作成される。

上記の[APIを有効化する](#apiを有効化する)で開いた`APIとサービス`の`認証情報`のメニューをクリックして、設定を行っていく。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/74bd7585-8658-a3aa-2a8d-9542278e3e4d.png)

アプリケーションのHomeとなるURIとリダイレクトURIを設定して作成をすると、以下の図のようにclient_id・client_secretが作成されるのでメモしておく。このclient_id・client_secretは認可エンドポイントへ認可リクエストを行う時、トークンエンドポイントへのリクエストを行う時に使う。また、リダイレクトURIとは、[実際に実装するもののフロー](#実際に実装するもののフロー)の章で取り上げた⑥で返ってくる認可レスポンスの302のLocationに設定される。

| 1 | 2 |
|:-:|:-:|
| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/d2a8d777-603c-4dc2-1345-bde3bcb1853b.png) | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/ddbfabaf-4ade-2a44-bb8c-bc980a80eadb.png) |

ここまで設定できれば準備としては完了になる。
次の章からは実際に実装をしていき、アクセストークンを取得後、そのアクセストークンで[CalendarList: list ](https://developers.google.com/calendar/api/v3/reference/calendarList/list)を実行してみる所までみていく。

※ちなみに、[OpenID Connect Dynamic Client Registration 1.0](https://openid.net/specs/openid-connect-registration-1_0.html)という仕様でクライアントを登録する際の仕様が定められている。

### ExpressサーバのHTTPS化
Authorization Code Flowを体感するために、今回はWebアプリをクライアントとして実装するが、Node.jsのExpressを使う。

Googleの認可サーバの仕様に合わせてExpressサーバをhttps化する必要がある（理由は以下の[背景について](#背景について)を参照）のでそれを行う。

#### 背景について
HTTPS化する理由については、以下の2点。

- GoogleAPIの設定でリダイレクトURIには、httpsのみが設定できるようになっているため<br>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/dfcbec78-6d7c-e222-3326-4f5b773e13c3.png)
- （今回は上記で言及したようにOpenID Connectの仕様で実装された認可サーバでAuthorization Code Flowを行うのでOpenID Connectの仕様に従う部分が出てくるが）[OpenID Connect Core 1.0　3.1.2.1.  Authentication Request](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest)の`redirect_uri`には、以下のように書かれており、httpも使えるが基本はhttpsを使うべきと書かれているため

> When using this flow, the Redirection URI SHOULD use the https scheme; however, it MAY use the http scheme, provided that the Client Type is confidential（このフローを使用する場合、Redirection URIはhttpsスキームを使用すべきですが、Client Typeが機密であれば、httpスキームを使用しても構いません(MAY)。）

#### 実際にHTTPS化する
HTTPS化するが、ちゃんと証明書を用意するのは手間なので、オレオレ証明書を使ってHTTPS化する。手順としては、[Enabling HTTPS on express.js](https://stackoverflow.com/questions/11744975/enabling-https-on-express-js)に書かれている内容が参考になる。

まずはOpenSSLを使って、HTTPS化に必要になる`key`と`crt`を生成する。以下のようなopensslコマンドを実行するとCLI上で対話しながらkey・crtを生成できる（質問に対する答えはダミーの内容で実在しないものも含まれている）。

```bash
[root@localhost openid-connect]# sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ./server.key -out server.crt
Generating a 2048 bit RSA private key
..........................................+++
..........+++
writing new private key to './server.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:JP
State or Province Name (full name) []:Tokyo
Locality Name (eg, city) [Default City]:Tokyo City
Organization Name (eg, company) [Default Company Ltd]:Example, Inc. 
Organizational Unit Name (eg, section) []:Example Unit
Common Name (eg, your name or your server's hostname) []:www.example.com
Email Address []:dumy@example.com
```

あとは、ここで生成したkeyとcrtを用いて、以下のようにhttpsモジュールを使ってサーバを作成すればいい（expressの[公式](https://expressjs.com/en/api.html#app.listen)の内容も参照）。

```js
import express from 'express';
import https from 'https';
import fs from 'fs';
...
const app = express();
const server =
	callback.protocol === 'https:'
		? https.createServer(
				{
					key: fs.readFileSync('./ssl/server.key'),
					cert: fs.readFileSync('./ssl/server.crt')
				},
				app
		  )
		: app;
...
server.listen(8080);
```

- 参考：[set-up-SSL-in-nodejs](https://github.com/sagardere/set-up-SSL-in-nodejs)
- 参考：[How To Create a Self-Signed SSL Certificate for Apache in Ubuntu 16.04](https://www.digitalocean.com/community/tutorials/how-to-create-a-self-signed-ssl-certificate-for-apache-in-ubuntu-16-04)
- 参考：[app.listen([port[, host[, backlog]]][, callback])](https://expressjs.com/en/api.html#app.listen)
- 参考：[https.createServer([options][, requestListener])](https://nodejs.org/api/https.html#httpscreateserveroptions-requestlistener)

### windowsのhostsファイルを修正して、リダイレクト時のホストをIPに変換する（ローカルのなんちゃってDNSの設定）
そもそもwindowsのhostsって何？という話は[「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典　hostsファイル](https://wa3.i-3-i.info/word16969.html)が分かりやすいと思う。

本題だが、今回はAuthorization Code Grantを体感するために、OpenID Connectの仕様で実装された[Google's OAuth 2.0](https://developers.google.com/identity/protocols/oauth2/openid-connect)を使っているが、クライアントを登録しているする際に設定するリダイレクトURIにはIPは指定できない仕様になっている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/6b13bbfb-eea0-4f09-ca25-0eddf82fdeb4.png)

そのため何らかののドメインを設定する必要があるが、ちゃんとドメインを取得するのも面倒なのでなんちゃってDNSで対応する。
筆者はWindowsなのでhostsファイルを以下のように設定するだけでドメインをローカルのサーバのIPに解決する事ができるようになる。

```
# Copyright (c) 1993-2009 Microsoft Corp.
#
# This is a sample HOSTS file used by Microsoft TCP/IP for Windows.
#
# This file contains the mappings of IP addresses to host names. Each
# entry should be kept on an individual line. The IP address should
# be placed in the first column followed by the corresponding host name.
# The IP address and the host name should be separated by at least one
# space.
#
# Additionally, comments (such as these) may be inserted on individual
# lines or following the machine name denoted by a '#' symbol.
# 省略

192.168.56.2       example.com
```

※ちなみに、192.168.56.2はWindows上に立てたVirtualBoxの仮想マシンのIP。仮想マシンの構築については[Windows上にLinux(CentOS)のWebアプリ開発環境をvirtualboxで構築する](https://qiita.com/yuta-katayama-23/items/6baf167464eca3376ea0)を参照。

## 続きは「【後編】OAuth2.0の勉強でAuthorization Code Grantをクライアント側として体感してみた」で
https://qiita.com/yuta-katayama-23/items/4214114a21c11ec4f934