Skip to content

Latest commit

 

History

History
529 lines (444 loc) · 19 KB

File metadata and controls

529 lines (444 loc) · 19 KB

New Security Definitions

Metadata

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

Change Log

| Date | Responsible Party | Description | | 2021/05/13 | Jeremy Whitlock | Initial proposal |

Introduction

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.

Motivation

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 overload type: apiKey since you are describing a Security Scheme using a JWT and not an API Key
  • type: 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.

Proposed solution

This proposal suggests the following changes to the Security Scheme object:

  • Add a new config property that is type specific to contain the scheme-specific configuration not related to credential configuration
  • Add a new credentials property 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, openIdConnectUrl and scheme) and move them into a type-specific config property
  • Update type to allow for any value instead of the hard-coded list of supported types

Detailed design

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.

Value Getter Object

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.

Example

Bearer Token (Header)
in: header
name: authorization
format: ^[B|b][E|e][A|a][R|r][E|e][R|r] (.*)$
Query Parameter (Simple)
in: query
name: accessToken

Schema

# /types/ValueGetter
type: object
properties:
  in:
    type: string
    enum: ['body', 'cookie', 'header', 'path', 'query']
  format:
    type: string
  name:
    type: string
required: ['in', 'name']

Credential Object

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.

Schema

# /types/Credential
$ref: #/types/ValueGetter

New Security Scheme Object

The 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

Schema

# /types/NewSecurityScheme
type: object
oneOf:
- $ref: '#/types/GenericSecurityScheme'
- $ref: '#/types/JWTSecurityScheme'
- $ref: '#/types/OAuthSecurityScheme'
- $ref: '#/types/OIDCSecurityScheme'

API Key or Generic

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
Example
# ...
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:
        # ...
# ...
Schema
# /types/GenericSecurityScheme
type: object
properties:
  type:
    type: string
    enum: ['apiKey', 'generic']
  config:
    type: object
  credentials:
    type: array
    items:
      $ref: '#/types/CredentialObject'
required: ['credentials', 'type']

JSON Web Keys (JWT)

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.

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
    # 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
# ...
Schema
# /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']

OAuth

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.

Example
# ...
components:
  securitySchemes:

# ...
Schema
# /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']

OpenID Connect (OIDC)

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.

Example
# ...
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
# ...
Schema
# /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']

Backwards compatibility

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.

Alternatives considered

Allowing Credential Location(s) for all Security Schemes

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.

Allowing type to be specified by the top-level configuration key

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:

As Proposed

# ...
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
# ...

Considered

# ...
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
# ...