KongでOAuth2.0認証（Client Credentialsフロー)を試す

# はじめに
Kongの[Get Started with Kong](https://docs.konghq.com/gateway/3.8.x/get-started/key-authentication/)を読むと、Kongには認証方式が色々できると書かれていて、Key認証が紹介されている。
で、OAuth認証のやり方はどうするんだろう、と調べていたら、[KongのBlog](https://konghq.com/blog/engineering/kong-gateway-oauth2#crayon-611d039f7931d479393071)にOAuth認証の手順や、[実際に動かした56さんの記事](https://zenn.dev/kg0r0/articles/18fe4741fd58765a240c)があった。
これらを参考にOAuth認証のやり方を自分なりに整理。なお、KongのインストールやOAuth認証の仕組みの説明は割愛。
基本Blogの設定に従っているが、リソースサーバであるAPIサーバとの接続は[56さんの記事](https://zenn.dev/kg0r0/articles/18fe4741fd58765a240c)の中でGitHubにあげていたサンプルを参考にして、httpsでなくhttpに変更した。

# 前提条件
- Kong Gatewayはdocker-composeでコンテナとして構築
- APIサーバもコンテナで作成したが、KongからAPIサーバを呼び出せなかった。コンテナ同士で通信する場合、仕掛けが必要？よく分からなかったのでAPIサーバはコンテナを諦め、VagrantでVMを作成した
- OAuth認証は４フローあるが、今回は`Client Credentialsフロー`をターゲットにした。Client Credentialフローの詳細は[RFCの和訳](https://tex2e.github.io/rfc-translater/html/rfc6749.html)の「4.4. Client Credentials Grant」を参照。


# APIサーバの設定
### 最初にProxy先となるAPIサーバを作成
基本は[Blogの記事](https://konghq.com/blog/engineering/kong-gateway-oauth2#crayon-611d039f7931d479393071)のとおり。

wb01のホスト名でVM作成後、以下を実施。コマンドのみ記載。

```shell
~$ mkdir project
~$ cd project
~/project$ yarn init
# 質問には全部デフォルト（リターン）で
```
https通信しないので、Blogと違って必要最小限のパッケージだけ

```shell
~/project$ yarn add express
```
Blogのとおり、APIサーバ用のJSONデータ用意。
`GET /stepcounts`で呼び出す。

```project/data.json
{
  "alice": [
    { "date": "2021-01-01", "count": 14500 },
    { "date": "2021-01-02", "count": 6500 },
    { "date": "2021-01-03", "count": 28000 }
  ],
  "bob": [
    { "date": "2021-01-01", "count": 900 },
    { "date": "2021-01-02", "count": 1400 },
    { "date": "2021-01-03", "count": 1100 }
  ],
  "clark": [
    { "date": "2021-01-01", "count": 2500 },
    { "date": "2021-01-02", "count": 12000 },
    { "date": "2021-01-03", "count": 9500 }
  ]
}
```

56さんの記事を参考にhttpで`/stepcounts`のエンドポイント作成

```project/index.js
// PATH: project/index.js

const port = 3000

const express = require('express')
const app = express()
const data = require('./data.json')

const getCurrentUser = ({ headers }) => {
  return headers['mock-logged-in-as'] ||
    headers['x-authenticated-userid']
}

app.get('/stepcounts', (req, res, next) => {
  console.log(req.headers)
  const user = getCurrentUser(req)
  if (!user) {
    res.status(401).send('Not authorized')
    return
  }
  res.send(data[user] || [])
})

app.listen(port, () => {
  console.log(`Server is listening on http://wb01:${port}`
  )
})
```

サーバ起動

```shell
~/project$ node index.js
Server is listening on http://wb01:3000
```

### APIサーバの疎通確認

疎通確認用のクライアントとしてInsomniaでWebアクセスを試す。Postmanでもいいのだが、Header情報の書き換えができなかったりしたため、不便だったので使わず。

index.jsでは`mock-logged-in-as`でユーザチェックをしているので、Header情報が不足しているとエラーになる。

`http://wb01:3000/stepcounts`で`mock-logged-in-as`ない場合
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218194/d5a251a9-f345-197e-ae1d-9a7f00345fc8.png)

`mock-logged-in-as`を付与した場合、clarkの値が返ってくる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218194/0e64cfa8-0e41-8493-d3d7-d5e20bed9642.png)

# Kong GatewayとAPIサーバの配置

### ServiceとRouteを登録

最初にServiceを登録
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218194/2647523d-c961-b2c2-88a0-431f4051ddef.png)

サービス名「step-on-api-service」とURL入れる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218194/2c9b8612-4f8f-4235-2876-870cddd59910.png)

続いてRouteの登録
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218194/86be5657-cc0f-a6f8-0b80-a6a246bd6fc6.png)

特に難しい部分はない。登録済みのServiceがプルダウンから選択可能。
RouteのエンドポイントはBlogだとhttpとhttps両方の設定にしているが、今回はhttpのみで`/stepon`をPathとしている。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218194/e398817e-6f5e-04fa-9f00-b89eb680548f.png)


### Kong GatewayとAPIサーバの通信確認

InsomniaからKongのRouteにアクセス。
アクセス先は`http://localhost:8000/stepon/stepcounts`。Headerに`mock-logged-in-as`も必要。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218194/d1d7191a-9a36-36cd-c1ec-92850e0cc468.png)

# OAuth2プラグインの導入

Service「step-on-api-service」から`OAuth2.0`プラグインを追加。Serviceの中でPluginを追加するとGlobal設定されず、Serviceに閉じた範囲で有効になる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218194/83ea7720-b597-afbc-5c1e-1127ac36e323.png)

oauthで検索し、`OAuth 2.0 Authentification`を選択する。OSS版では使えないプラグインも表示される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218194/4f6ea581-e162-440f-7c69-6dcf56215fb9.png)

今回はOAuth認証フローの`クライアント認証`を試すので、`Enable Client Credentials`にチェックを入れる。その他必要な情報を設定する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218194/a7a4197a-492e-aecc-0fdc-ffd77d71106f.png)

すでにOAuth認証が有効になっているので、Access Tokenなしでアクセスしようとするとエラーになる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218194/868a0472-b815-7f68-a5f0-73844edc4c33.png)

OAuth認証を無効にしたい場合、Enabledのボタン押下で無効化できる。
無効化となると、先ほどAccess Tokenなしでエラーになったリクエストが再び成功する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218194/8f2940a4-0c93-3ac8-e44a-888cc3b82276.png)

### Client Application(API Consumer)を追加
誰がアクセスできるか設定する。
Client Applicationとしてshoeflyshoeの名前で利用者を登録する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218194/088dd3b9-52eb-154a-1b30-fd182b587e6c.png)

最初にshoeflyshoeさんを登録

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218194/03f751c6-6727-add4-cec8-8e456ae64164.png)

次にshowflyshoeさんに認証情報を登録する。クライアントがToken取得する際に必要な情報となる。
Consumersのshoeflyshoeを選択してCredentials情報を登録する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218194/3e31309b-a0ef-d137-ad45-de005e01d8a7.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218194/231d47d8-95e3-e4fb-c415-718355b1f506.png)

client_id, client_secretを入力、`Client Type`は`confidential`が良さそう。
[OAuth2.0のRFC](https://tex2e.github.io/rfc-translater/html/rfc6749.html)の「2.1. Client Types」に記載あり。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218194/b2748795-e52c-15b6-3321-f24cf9ab47af.png)

以上の設定でOAuth2.0認証の設定は完了。
今回は`Client Credentialsフロー`のみを有効にし、クライアント認証

# OAuth2.0認証によるAPIアクセス
`Client Credentialsフロー`をやってみる。

### アクセスToken取得
以下の２通りのやり方がある。詳細は[@TakahikoKawasakiさんのページ](https://qiita.com/TakahikoKawasaki/items/63ed4a9d8d6e5109e401)で説明されている。

- BODY部にclient_id,client_secretを設定
- Head部にBASE64エンコードした情報を設定

Token取得のURLは`/oauth2/token`らしい。
grant_typeはclient_credentialsにする（RFC参照)

#### その１：BODY部にclient_id,client_secretを設定
Body部に値を埋め込む。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218194/facab0b1-b585-578f-d0e5-4b68d4c3cb78.png)

なお、RFCによれば、`Content-Type=application/x-www-form-urlencoded`とあるが、間違って指定してもTokenは取得できた。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218194/d1740f13-b3b8-d467-fb7f-8d69336ce852.png)

#### その２：Head部にBASE64エンコードした情報を設定
client_idとclient_secretの値を":"でつなぎ、BASE64でエンコードしてToken取得リクエスト。
今回の例で言えば`some_client_id:some_client_secret`をエンコードし、生成された`c29tZV9jbGllbnRfaWQ6c29tZV9jbGllbnRfc2VjcmV0`をHeaderに入れる。先頭に`Basic `を入れ忘れないこと。

Body部はgrant_typeのみ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218194/490d5bef-103d-284f-58fe-9856d39b5584.png)

Header部にBASE64エンコード情報を入れる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218194/f35cc7c1-e2a0-6f87-565f-46d2c8b30609.png)

### 取得したAccessTokenを使ってAPIサーバにアクセス

AccessTokenが取得できたら、そのTokenを使ってAPIサーバにアクセス。
Tokenの先頭に`bearer `が必要。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218194/3c5f74a8-b389-7299-250f-59303587266c.png)

もしTokenが誤っていたり、有効時間切れの場合はエラーになる、。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218194/cef25f02-8751-455f-0942-1f9e55fcd26f.png)

# まとめ
Kongの各種設定がGUIから可能になって操作性が向上したので、GUIベースの設定方法の紹介です。
他のフローも同様の手順で試すことができるはずなので、そのうち試してみる。とはいえ、OAuth認証難しい。。。