| Tag | Value |
|---|---|
| Proposal | 2021-05-19-New-Security-Definitions |
| Authors | Jeremy Whitlock |
| Review Manager | TBD |
| Status | Proposal |
| Implementations | N/A |
| Issues | {issueid} |
| Previous Revisions | N/A |
| Date | Responsible Party | Description | | 2021/05/13 | Jeremy Whitlock | Initial proposal |
This purpose of this proposal is to rethink how Security Schemes are defined in OpenAPI. This proposal would separate the credential location(s) from the Security Scheme type and would allow for complete control over dictating where the credential(s) are provided for a Security Scheme. This proposal will add a generic way to retrieve a credential from anywhere in a request, and allow any Security Scheme to dictate where its supported credential(s) are instead of relying on OpenAPI-supported conventions/opinions. This proposal also separates the credential location(s) details from the other configurations associated with the Security Scheme (like JWKS details for JWT).
Note: The proposal isn't to remove type from the Security Scheme object but to allow for more control over where
the credential(s) are provided for a Security Scheme. This proposal would still have reasonable defaults for the
already-supported type values and will be documented as such.
Security Schemes (formerly known as "Security Definitions") provide a way to describe the security scheme(s) used by your API. Unfortunately, the types supported by OpenAPI are not only limited but they often tie the credential location to the Security Scheme type. This proposal will add a generic way to define where a credential is and its format so that the consumer can have complete control over where credentials are used in their API. Not only would this allow consumers to specify their own Security Schemes without OpenAPI having to be the gatekeeper, but it also allows a level of flexibility not available now. There have also been extra Security Scheme properties that have been tacked on to support the growing needs of consumers, which would be alleviated (or moved in this case).
For example, if a consumer wants to use a JSON Web Key (JWT) in their API, OpenAPI will allow this but only under certain circumstances:
type: apiKey: This will allow you to provide your JWT anywhere you would like, but it could be confusing to overloadtype: apiKeysince you are describing a Security Scheme using a JWT and not an API Keytype: http: This will only work if your JWT is in a header and in a supported HTTP Auth format
OpenAPI being so opinionated about these kinds things does not serve any real benefit to anyone involved, and this proposal would make things much more flexible.
This proposal suggests the following changes to the Security Scheme object:
- Add a new
configproperty that is type specific to contain the scheme-specific configuration not related to credential configuration - Add a new
credentialsproperty that is an array of Credential Objects that describe a credential (where it is located, what its format is, etc.) - Remove the credential-specific properties (
bearerFormat,flows,in,openIdConnectUrlandscheme) and move them into a type-specificconfigproperty - Update
typeto allow for any value instead of the hard-coded list of supported types
The bulk of this proposal is defining a way to separate the properties used to describe the credential(s) for a Security Scheme from the root properties of the Security Scheme. This proposal will also allow you to define multiple credentials (multiple locations, formats, ...) for the same Security Scheme for cases where you might support multiple formats/locations for effectively the same credential. The new objects being suggested as part of this proposal are defined below.
Note: While not specifically mentioned below for brevity, JSON References and Extensions should be allowed within every complex type documented below.
The Value Getter Object describes a place in the request where something exists that you want to retrieve. The name and purpose of this object is generic, as in I could see other places in OpenAPI where you could point to a value in the request. The Value Getter Object is the base object to be extended by all Credential Objects. Below is its schema:
| Field Name | Type | Applies To | Description |
|---|---|---|---|
| in | string |
Any | REQUIRED. Specifies where in the request the credential is provided. Valid values are: body, cookie, header, path and query |
| format | regex |
Any | Specifies the format of the credential. Since in many cases, the credential is a subset of the raw value, format must be a regular expression that matches the complete value, and contains a single capturing group to match the actual value within the raw value. |
| name | string |
Any | REQUIRED. Specifies the name used to resolve the credential in the location provided by in. |
Note: For body, name should be a JSON Pointer (or something similar) that lets you define what part of the
body you are interested in. For example, you could use # to dictate that body in its entirety is the credential or
you could use something like #/some/path to dictate that a portion of the body is the credential. I understand that
JSON Pointer usage implies that the body is JSON but that doesn't have to be the case and could be used to address any
structured data.
Note: For format, I realize that it being a regular expression might seem a bit off-putting but there is no way to
reasonably support raw values (as-is) and taking a raw value and only grabbing the important part
(like when using HTTP Auth where you don't want the scheme details). We could also have named formats that would
alleviate the need for consumers to author regular expressions themselves for things like HTTP Auth, Bearer Tokens, etc.
Note: I could see a case for a description field.
in: header
name: authorization
format: ^[B|b][E|e][A|a][R|r][E|e][R|r] (.*)$in: query
name: accessToken# /types/ValueGetter
type: object
properties:
in:
type: string
enum: ['body', 'cookie', 'header', 'path', 'query']
format:
type: string
name:
type: string
required: ['in', 'name']The Credential Object describes a credential, as in where it is in the request and the details that define the credential. As mentioned above, the Credential Object extends the Value Getter Object described above.
Note: As of right now, the Credential Object is the only object that extends the Value Getter Object and it could be simpler to just make the Value Getter Object the Credential Object in the short term.
# /types/Credential
$ref: #/types/ValueGetterThe New Security Scheme Object describes the proposed approach to Security Schemes. Below is the schema for the New Security Scheme Object:
| Field Name | Type | Description |
|---|---|---|
| config | object | An object representing one of the supported New Security Scheme Objects |
| credentials | [Credential Object] | An array of credential locations |
# /types/NewSecurityScheme
type: object
oneOf:
- $ref: '#/types/GenericSecurityScheme'
- $ref: '#/types/JWTSecurityScheme'
- $ref: '#/types/OAuthSecurityScheme'
- $ref: '#/types/OIDCSecurityScheme'The purpose of the Generic Security Scheme is that it supports an opaque credential or credentials in the request, as in there are no scheme-specific configuration beyond where the credential(s) are located. Below is its schema:
| Field Name | Type | Description |
|---|---|---|
| type | string | REQUIRED. Either apiKey (for backward compatibility) or generic |
| config | object | This security scheme does not explicitly have any configuration properties but the consumer could potentially throw anything in here that they wanted |
| credentials | REQUIRED. [Credential Object] | An array of credential locations |
# ...
components:
securitySchemes:
apiKey:
type: apiKey
credentials:
- in: header
name: authorization
format: ^[B|b][E|e][A|a][R|r][E|e][R|r] (.*)$
generic:
type: generic
credentials:
- in: query
name: apiKey
genericWithConfig:
type: generic
credentials:
- in: query
name: apiKey
config:
# ...
# ...# /types/GenericSecurityScheme
type: object
properties:
type:
type: string
enum: ['apiKey', 'generic']
config:
type: object
credentials:
type: array
items:
$ref: '#/types/CredentialObject'
required: ['credentials', 'type']The JWT Security Scheme is to support JWT credentials and the configuration required by clients to sign JWTs for use by the API described in the OpenAPI Document. Below is its schema:
| Field Name | Type | Description |
|---|---|---|
| type | string | REQUIRED. Must be jwt |
| config | object | REQUIRED. The JWT configuration details |
| config.audiences | [string] | When set, specifies the allowed audiences (used to verify the aud claim) |
| config.issuer | string | REQUIRED. Specifies the issuer of the JWT (used to verify the issuer claim) |
| config.jwks | object | REQUIRED. Specifies the JWKS details |
| config.jwks.uri | string | The URI to the JWKS |
| config.jwks.keyIds | [string] | The JWKS key ids that this Security Scheme uses |
| credentials | REQUIRED. [Credential Object] | An array of credential locations |
Note: In theory, one could use a JSON Reference for config.jwks to point to an inline JWKS.
# ...
components:
securitySchemes:
# External JWKS
googleJWKS:
type: jwt
credentials:
- in: header
name: authorization
format: ^[B|b][E|e][A|a][R|r][E|e][R|r] (.*)$
config:
audiences:
- 32555940559.apps.googleusercontent.com
issuer: https://accounts.google.com
jwks:
uri: https://www.googleapis.com/oauth2/v3/certs
# External JWKS (with specific keys)
googleJWKSWithKeys:
type: jwt
credentials:
- in: header
name: authorization
format: ^[B|b][E|e][A|a][R|r][E|e][R|r] (.*)$
config:
audiences:
- 32555940559.apps.googleusercontent.com
issuer: https://accounts.google.com
jwks:
uri: https://www.googleapis.com/oauth2/v3/certs
keyIds:
- afde80eb1edf9f3bf4486dd877c34ba46afbba1f
# ...# /types/JWTSecurityScheme
type: object
properties:
type:
type: string
enum: ['jwt']
config:
type: object
properties:
audiences:
type: array
items:
type: string
issuer:
type: string
jwks:
type: object
properties:
keyIds:
type: array
items:
type: string
uri:
type: string
required: ['uri']
required: ['issuer', 'jwks']
credentials:
type: array
items:
$ref: '#/types/CredentialObject'
required: ['config', 'credentials', 'type']The OAuth Security Scheme is used to support OAUth. This Security Scheme is almost identical to the pre-proposal
OAuth Security Scheme but with support for credentials. Below is its schema:
| Field Name | Type | Description |
|---|---|---|
| type | string | REQUIRED. Must be oauth |
| config | object | REQUIRED. The JWT configuration details |
| config.flows | Flows Object | REQUIRED. An object containing configuration information for the flow types supported. |
| credentials | REQUIRED. [Credential Object] | An array of credential locations |
Note: OAuth's access tokens are basically defined as being a
Bearer Token. And since a Bearer Token can be specified in a few
different places, pre-defined by the specification mind you, one could potentially expect OpenAPI to have a Bearer
Token credential type and it would pre-populate credentials with all of the locations allowed in RFC 6570. But since
the API producer likely will dictate where its Bearer Token is expected to be, for consistency it makes sense to treat
OAuth credentials no differently than other credentials.
# ...
components:
securitySchemes:
# ...# /types/JWTSecurityScheme
type: object
properties:
type:
type: string
enum: ['oauth']
config:
type: object
properties:
flows:
$ref: 'https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.1/schema.json#/$defs/oauth-flows'
required: ['flows']
credentials:
type: array
items:
$ref: '#/types/CredentialObject'
required: ['config', 'credentials', 'type']The OIDC Security Scheme is to support JWT credentials provided by an OpenID Connect identity provider, and the configuration required by clients to sign JWTs for use by the API described in the OpenAPI Document. Below is its schema:
| Field Name | Type | Description |
|---|---|---|
| type | string | REQUIRED. Must be oidc |
| config | object | REQUIRED. The JWT configuration details |
| config.audiences | [string] | When set, specifies the allowed audiences (used to verify the aud claim) |
| config.discoveryDocument | object | REQUIRED. Specifies the Discovery Document details |
| config.discoveryDocument.uri | string | The URI to the Discovery Document |
| config.discoveryDocument.keyIds | [string] | The JWKS key ids that this Security Scheme uses |
| credentials | REQUIRED. [Credential Object] | An array of credential locations |
Note: In theory, one could use a JSON Reference for config.discoveryDocument to point to an inline Discovery
Document.
# ...
components:
securitySchemes:
# External JWKS
googleOIDC:
type: oidc
credentials:
- in: header
name: authorization
format: ^[B|b][E|e][A|a][R|r][E|e][R|r] (.*)$
config:
audiences:
- 32555940559.apps.googleusercontent.com
discoveryDocument:
uri: https://accounts.google.com/.well-known/openid-configuration
# External JWKS (with specific keys)
googleOIDCWithKeys:
type: oidc
credentials:
- in: header
name: authorization
format: ^[B|b][E|e][A|a][R|r][E|e][R|r] (.*)$
config:
audiences:
- 32555940559.apps.googleusercontent.com
discoveryDocument:
uri: https://accounts.google.com/.well-known/openid-configuration
keyIds:
- afde80eb1edf9f3bf4486dd877c34ba46afbba1f
# ...# /types/OIDCSecurityScheme
type: object
properties:
type:
type: string
enum: ['oidc']
config:
type: object
properties:
audiences:
type: array
items:
type: string
issuer:
type: string
discoveryDocument:
type: object
properties:
keyIds:
type: array
items:
type: string
uri:
type: string
required: ['uri']
required: ['issuer', 'discoveryDocument']
credentials:
type: array
items:
$ref: '#/types/CredentialObject'
required: ['credentials', 'type']To support backward compatibility, the existing #/components/securitySchemes object should use oneOf to match the
existing Security Scheme Objects or the New Security Scheme Objects. The old-style approach could/should be treated as
deprecated and tooling could/should warn of such things.
While the main point of this proposal was to give complete control over where the credential(s) are for a Security
Scheme, another minor goal was to provide consistency and separation of control. One alternative considered was to
instead of adding credentials and config, just making in/name/scheme/bearerFormat available for all Security
Schemes. I'm not a fan of that as it quickly becomes confusing and I'd much rather see a separation of configuring the
credential location(s) from the other configurations related to the Security Scheme.
Note: As I was writing this, the more I realize I like this approach.
Another option that was considered was to instead of using type in each of the New Security Scheme Objects, we would
use a named property that itself indicates the New Security Scheme type. Here is an example:
# ...
components:
securitySchemes:
# External JWKS
googleJWKS:
type: jwt
credentials:
- in: header
name: authorization
format: ^[B|b][E|e][A|a][R|r][E|e][R|r] (.*)$
config:
audiences:
- 32555940559.apps.googleusercontent.com
issuer: https://accounts.google.com
jwks:
uri: https://www.googleapis.com/oauth2/v3/certs
# ...# ...
components:
securitySchemes:
# External JWKS
googleJWKS:
credentials:
- in: header
name: authorization
format: ^[B|b][E|e][A|a][R|r][E|e][R|r] (.*)$
jwt:
audiences:
- 32555940559.apps.googleusercontent.com
issuer: https://accounts.google.com
jwks:
uri: https://www.googleapis.com/oauth2/v3/certs
# ...