はじめに
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.3をもとにしています。
APIリクエストをトークンが必要なことを明示したい
API仕様を設計する上で、リクエストが誰からでも叩けるのはセキュリティ的に好ましくありません。なので、クエリパラメータやヘッダーにアクセストークンを付加してリクエストを送って貰うと考えるとおもいます。
$ curl --request POST \
--url https://example.com/users \
--header 'Authorization: Bearer アクセストークン' --data '{"name": "Bob"}'
もしかしたら、Parameter Objectを使って、以下の様に記載するかもしれません。
/users:
post:
parameters:
- schema:
type: string
in: header
name: Authorization
description: Bearer アクセストークンを付加する
確かに HTTPヘッダに Authroization
を付加して、Bearer(署名なし)アクセストークンをここに付加する意図は理解できるかもしれませんが、明確ではありません。
こういう場合は、Security Requirement Object security
フィールドを利用します。
/users:
post:
security:
- AuthBearer: []
AuthBearer
というのは、Security Scheme Objectで自身で定義するフィールドで、空の配列要素をもっています、空なのは仕様です。この例では、POSTメソッドを表す Path Item Objectの下に Security Requirement Object security
を配置していますが、一番の上に security
を配置すると、すべてのリクエストに適用されることを意味まします。
AuthBearerは以下の様に定義します。
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)を表すことはできません。
components:
securitySchemes:
QueryKey:
type: apiKey
description: クエリパラメータでトークンを送る場合
in: query
name: access_token
APIリクエストの認可をアクセストークンのスコープで表現したい
あるAPIリクエストを払い出したアクセストークンに紐付けるのは、OAuth2 RFC6749 section 3.3 で定義されている scope
で表現することが多いでしょう。OpenAPI3も scope
をAPI毎にスコープを明示することができます。
/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 の定義をしてみます。
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
の配列要素として記載します。
/users:
post:
security:
- OAuth2:
- 'users:write'
- AuthBearer: []
OpenID Connect Discovery 1.0で参照!
認可において、OpenID Connect Discovery 1.0の .well-know/configuration
から情報を取得して貰うようにする場合は、 type
に "openIdConnect"
を利用します。
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を列挙できます。
/users:
post:
security:
- OIDC:
- 'users:write'
- AuthBearer: []
最後に
記載する量はたいしたことありませんが、これによりAPIリクエストのセキュリティ要件がより明確になります。
API設計者のみなさんのイケてるAPI設計の一助になれば。