curlとKeycloakではじめるOpenID Connect

この記事は[ラクスアドベントカレンダー](https://qiita.com/advent-calendar/2022/rakus)の15日目の記事です。昨年から1年ぶりの投稿です。

今年は業務で、OpenID ConnectやOAuthやSAMLなど認証・認可プロトコルに触れることが多かったです。
OpenID Connectの勉強をする際に、Relaying Partyを用意するのが面倒なのと、実際のフローを確認したかったので、curlとKeycloak利用してOpenID Connectで認証してみました。

OpenID Connectの仕様書は理解したけど、実際の認証フローの想像がつかない方はぜひ参考にしてみてください。

## 環境

Keycloakは`19.0.2`を利用し、ローカルのDockerで`localhost:8080`で動作しています。

## curlとKeycloakでAuthorization Code Flow

### Keycloakの準備

Keycloakには、`oidc-sample`というRealmに、`rp1`というOpenID ConnectのClientと、`admin`というUserを作成しています。
Clientの主に変更・取得した設定は以下になります。

| 設定 | 値 |
| --- | --- |
| Client ID | rp1 |
| Valid redirect URIs | http://localhost:3000/oidc |
| Client authentication | On |
| Client secret | `Credentials`タブから取得 |


KeycloakのOpenID Connectプロバイダー情報は[http://localhost:8080/realms/oidc-sample/.well-known/openid-configuration](http://localhost:8080/realms/oidc-sample/.well-known/openid-configuration)から取得できます。
今回は以下のエンドポイントを利用します。

| エンドポイント名 | エンドポイント | 
| --- | --- |
| Authorization Endpoint | http://localhost:8080/realms/oidc-sample/protocol/openid-connect/auth |
| Token Endpoint | http://localhost:8080/realms/oidc-sample/protocol/openid-connect/token |
| UserInfo Endpoint | http://localhost:8080/realms/oidc-sample/protocol/openid-connect/userinfo |

### Authorization Code Flow

詳しくは説明しませんが、以下のようなフローになります。

<figure><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239179/808f76af-23ff-5da6-b71c-23df463558a7.png" /><figcaption><a href="https://openid.net/specs/openid-connect-core-1_0.html#Overview">OpenID Connect Core 1.0 incorporating errata set 1</a>から引用</figcaption></figure>


1. Authentication Request
RP（今回はcurlを実行するPC）がOP（Keycloak）に認証リクエストを送る
1. Authentication & Authorization
OPはEnd-Userを認証、認可する
1. Token Request
OPはID TokenとAccess TokenをRPに発行する
1. UserInfo Request $ Response
RPはAccess Tokenを添えてUserInfo Endpointにリクエストを送り、OPはEnd-UserのClaimを返す


では早速Authentication Requestから実施します。

#### Authentication Request

IDトークンとアクセストークンを発行するために必要な認証コードを取得するために、Authentication Requestを発行し、Keycloakにログインします。
ブラウザでログインが必要なため、以下のURLにブラウザでアクセスします。

```
http://localhost:8080/realms/oidc-sample/protocol/openid-connect/auth?response_type=code&client_id=rp1&redirect_uri=http://localhost:3000/oidc&scope=openid
```

Keycloakのログイン画面が表示されます。

![keycloak login](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239179/6f1ac4a5-8396-fadb-52a5-f3beb63bd983.png)


#### Authentication & Authorization

Keycloakのログイン画面が表示されるので、Userでログインします。ログインに成功すると、`redirect_uri`に設定したURLにリダイレクトされますが、`localhost:3000`でWebアプリを動かしていないのでエラー画面が出ます。

![error](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239179/90644b83-65ed-905f-a162-12141a1bcef3.png)


が、気にせずURLを取得してください。以下のようなURLを取得できると思います。

```
http://localhost:3000/oidc?session_state=・・・・&code=<認証コード>
```
`code`パラメータが認証コードになります。ではこの認証コードを利用してIDトークンとアクセストークンを発行します。

#### Token Request

取得した認証コードを利用してToken Endpointで、IDトークンとアクセストークンを発行します。

```
curl -d "grant_type=authorization_code&code=<認証コード>&redirect_uri=http://localhost:3000/oidc&client_id=rp1&client_secret=<Client secret>" http://localhost:8080/realms/oidc-sample/protocol/openid-connect/token
```

Token Requestが成功すれば以下のようなToken Responseが返り、IDトークンとアクセストークンを取得できます。

```
{
  "access_token": <アクセストークン>,
  "expires_in": 300,
  "refresh_expires_in": 1800,
  "refresh_token": <リフレッシュトークン>,
  "token_type": "Bearer",
  "id_token": <IDトークン>,
  "not-before-policy": 0,
  "session_state": "8468db1c-feb4-4803-bfeb-213149b560cf",
  "scope": "openid email profile"
}
```

[jwt.io](https://jwt.io/)などで各種トークンを確認してみてください。

#### UserInfo Request $ Response

取得したアクセストークンを利用して、UserInfo EndpointからKeycloakのUser情報を取得します。

```
curl -H "Authorization: Bearer <アクセストークン>" http://localhost:8080/realms/oidc-sample/protocol/openid-connect/userinfo
```

以下のようなUser情報を取得できると思います。

```
{
  "sub": <User ID>,
  "email_verified": false,
  "preferred_username": "admin",
  "given_name": "",
  "family_name": ""
}
```

もしアクセストークンの有効期限が切れている場合は、以下のようにリフレッシュトークンを利用して、アクセストークンを再発行してください。

```
curl -X POST -d "client_id=rp1&client_secret=<Client Secret>&grant_type=refresh_token&refresh_token=<リフレッシュトークン>&scope=openid profile" http://localhost:8080/realms/oidc-sample/protocol/openid-connect/token
```

以上、curlとKeycloakでAuthorization Code Flowを実践してみました。

## 終わりに

実際に手で動かすと、より理解が深まると思います。
OpenID Connectの仕様を理解するための手段の1つとして、ぜひ参考にしてみてください。