OpenAPI 3.0でOAuth2/OpenID Connectの認可を表現したい



## はじめに

API仕様書いていますか？

この記事を読まれる人は、何かしらAPI仕様やOpenAPI/Swaggerに関わりがあったり、API仕様の認可の仕組みにOAuth2やOpenID Connect(OIDC)を利用することを検討していたりすると思います。
この記事では、OAuth2やOIDCの認可の仕組みを取り入れて、OpenAPI 3.0で定義されている `securitySchemes` と `security` フィールドをどう使って書いていけばいいか説明します。

OpenAPI/Swaggerや、OAuth2、OpenID Connectの各仕様や解説はそれぞれの大元のサイトやRFC、また先達の素晴らしい解説が多くありますので、そちらを参照して貰うとしては、OpenAPI 3.0のSecurity Scheme Objectのリンクを載せておきます。基本的にリンク先に示した箇所周辺のことを説明しています。
- [OpenAPI 3.0 SecurityScheme Object](https://swagger.io/specification/#security-scheme-object)

なお、本記事はOpenAPI 3.0.3をもとにしています。

## APIリクエストをトークンが必要なことを明示したい

API仕様を設計する上で、リクエストが誰からでも叩けるのはセキュリティ的に好ましくありません。なので、クエリパラメータやヘッダーにアクセストークンを付加してリクエストを送って貰うと考えるとおもいます。

```bash
$ curl --request POST \
  --url https://example.com/users \
  --header 'Authorization: Bearer アクセストークン' --data '{"name": "Bob"}'
```

もしかしたら、Parameter Objectを使って、以下の様に記載するかもしれません。

```yaml
/users:
    post:
      parameters:
        - schema:
            type: string
          in: header
          name: Authorization
          description: Bearer アクセストークンを付加する
```

確かに HTTPヘッダに `Authroization` を付加して、Bearer(署名なし)アクセストークンをここに付加する意図は理解できるかもしれませんが、明確ではありません。

こういう場合は、Security Requirement Object `security` フィールドを利用します。

```yaml
/users:
    post:
      security:
        - AuthBearer: []
```

 `AuthBearer` というのは、Security Scheme Objectで自身で定義するフィールドで、空の配列要素をもっています、空なのは仕様です。この例では、POSTメソッドを表す Path Item Objectの下に Security Requirement Object `security` を配置していますが、一番の上に `security` を配置すると、すべてのリクエストに適用されることを意味まします。

AuthBearerは以下の様に定義します。

```yaml
components:
  securitySchemes:
    AuthBearer:
      type: http
      scheme: bearer
      description: AuthorizationヘッダでBearerアクセストークンを送る場合
```

`type: http` と `scheme: bearer`  で、HTTPヘッダで `Authorization: Bearer` であることを表しています。なお、`type: http` のとき、 `security` フィールド時に配列を空にしないといけません。

`type` は `http` 以外にも、 `apiKey` を指定し、 `in: query` とするとクエリパラメータで送ることになります。ただし、このときはトークンの種類(例えばBearer)を表すことはできません。

```yaml
components:
  securitySchemes:
    QueryKey:
      type: apiKey
      description: クエリパラメータでトークンを送る場合
      in: query
      name: access_token
```

## APIリクエストの認可をアクセストークンのスコープで表現したい

あるAPIリクエストを払い出したアクセストークンに紐付けるのは、OAuth2 [RFC6749 section 3.3](https://tools.ietf.org/html/rfc6749#section-3.3) で定義されている `scope` で表現することが多いでしょう。OpenAPI3も `scope` をAPI毎にスコープを明示することができます。

```yaml
/users:
    post:
      security:
        - OAuth2:
            - 'users:write'
```

`OAuth2` もSecurity Scheme Objectです。配列要素の `'users:write'` は、scopeを表すので、 `/users` というAPIを利用するためには、アクセストークンにこの `scope` が付加していないとアクセスできないこと(そのような実装が必要)を表します。

## OAuth2の認可をAPI仕様に定義したい

Security Scheme Objectを利用します。OAuth2には多くの認可フローがありますが、今回はWebアプリケーション開発する上で一番一般的だと思う **Authorization Code Flow** の定義をしてみます。 

```yaml
components:
  securitySchemes:
    OAuth2:
      type: oauth2
      description: Authorization Code Flowの各エンドポイントを定義します
      flows:
        authorizationCode:
          authorizationUrl: '/oauth2/authorize'
          tokenUrl: '/oauth2/token'
          refreshUrl: '/oauth2/refresh'
          scopes:
            'users:read': ユーザー情報の取得
            'users:write': ユーザー情報の編集
```

`authorizationUrl` などは相対パス(relative path)として記載していますが、ホストが異なる場合は、URLをhttpsからすべて書くとよいでしょう。ここで `OAuth2` を定義することで、先ほど説明した Security Requirement Object `security` に指定できるようになります。さらに、 `scopes` フィールドには、カスタムscopeも可能なので、自身で定義したスコープのうち、アクセストークンが認可する `scope` を列挙します。この `scopes` フィールドで列挙したもののうち、APIに紐付けるスコープを Security Reqirement Objectの `OAuth2` の配列要素として記載します。

```yaml
/users:
    post:
      security:
        - OAuth2:
            - 'users:write'
        - AuthBearer: []
```

### OpenID Connect Discovery 1.0で参照！

認可において、OpenID Connect Discovery 1.0の `.well-know/configuration` から情報を取得して貰うようにする場合は、 `type` に `"openIdConnect"` を利用します。 

```yaml
components:
  securitySchemes:
    OIDC:
      type: openIdConnect
      description: OpenID Providerの設定を取得するためのURI。OpenID Connect Discovery 1.0で定義
      openIdConnectUrl: 'https://example.com/.well-known/openid-configuration'
```

`openIdConnectUrl` から得られるメタデータにも `"scopes_supported"` でscopeの定義がありますので、Security Requirement Objectに `OIDC` を記載して、scopeを列挙できます。

```yaml
/users:
    post:
      security:
        - OIDC:
            - 'users:write'
        - AuthBearer: []
```
## 最後に

記載する量はたいしたことありませんが、これによりAPIリクエストのセキュリティ要件がより明確になります。
API設計者のみなさんのイケてるAPI設計の一助になれば。