openapi, springdoc with Swagger Annotations splitting free form application/x-www-form-urlencoded request body into individual characters

Viewed 362

I'm trying to migrate from Springfox to springdoc for our Swagger page, and there is one endpoint that I am having a hard time getting working with springdoc. It is mimicking a OAuth2 token endpoint, taking in an application/x-www-form-urlencoded request body with different grant types allowed. I have the generated openapi documentation below. According to the Swagger page, the free form data should be allowed when using schema of type object. However, when I pass in the example values that worked on Springfox and swagger 2 (grant_type=authorization_code&code=xxxxxxxxxx&client_id=xxxxxxxxxx), the request is built out like this (note the body):

curl -X 'POST' \
  'http://localhost:8080/v1/token' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d '0=g&1=r&2=a&3=n&4=t&5=_&6=t&7=y&8=p&9=e&10=%3D&11=a&12=u&13=t&14=h&15=o&16=r&17=i&18=z&19=a&20=t&21=i&22=o&23=n&24=_&25=c&26=o&27=d&28=e&29=%26&30=c&31=o&32=d&33=e&34=%3D&35=x&36=x&37=x&38=x&39=x&40=x&41=x&42=x&43=x&44=x&45=%26&46=c&47=l&48=i&49=e&50=n&51=t&52=_&53=i&54=d&55=%3D&56=x&57=x&58=x&59=x&60=x&61=x&62=x&63=x&64=x&65=x'

Is there something that I am doing wrong in the openapi yaml, or am I putting the request body in incorrectly on the swagger page?

Swagger documentation: Swagger requestBody documentation

OpenAPI YAML to be used at https://editor.swagger.io/

openapi: 3.0.1
info:
  title: Test API
  description: Testing
  version: "1.0"
servers:
  - url: http://localhost:8080
    description: Generated server url
security:
  - api: []
paths:
  /v1/token:
    post:
      tags:
        - token-controller
      description: Oauth 2 Access Token.
      operationId: getOauthAccessToken
      requestBody:
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
            examples:
              authorization_code grant type:
                description: authorization_code grant type
                value: grant_type=authorization_code&code=xxxxxxxxxx&client_id=xxxxxxxxxx
              client_credentials grant type:
                description: client_credentials grant type
                value: grant_type=client_credentials&client_id=xxxxxxxxxx&client_secret=xxxxxxxxxx
              refresh_token grant type:
                description: refresh_token grant type
                value: grant_type=refresh_token&refresh_token=xxxxxxxxxx
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OauthAccessTokenResponseView_V1'
components:
  schemas:
    OauthAccessTokenResponseView_V1:
      type: object
      properties:
        scope:
          type: string
        access_token:
          type: string
        refresh_token:
          type: string
        token_type:
          type: string
        expires_in:
          type: integer
          format: int64
      description: 'The Oauth 2 Access Token response: https://www.oauth.com/oauth2-servers/access-tokens/access-token-response/'
  securitySchemes:
    api:
      type: http
      scheme: bearer
1 Answers

Your definition looks fine. This is a bug (or limitation?) in Swagger UI related to OpenAPI 3 examples keyword and form data. Please report it in the issue tracker: https://github.com/swagger-api/swagger-ui/issues

I could not find a working way to provide multiple examples for form data.


From the documentation point of view, I would recommend updating the request body schema to define the exact expected properties. Below is one possible variant that uses oneOf and property-level example values. But unfortunately there are other known issues with the rendering and "try it out" for form data with oneOf.

      requestBody:
        content:
          application/x-www-form-urlencoded:
            schema:
              oneOf:
                - $ref: '#/components/schemas/AuthorizationCodeTokenRequest'
                - $ref: '#/components/schemas/ClientCredentialsTokenRequest'
                - $ref: '#/components/schemas/RefreshTokenRequest'
      ...

components:
  schemas:
    AuthorizationCodeTokenRequest:
      type: object
      required:
        - grant_type
        - code
        - client_id
      properties:
        grant_type:
          type: string
          enum: [authorization_code]
        code:
          type: string
          example: xxxxxxxxxx
        client_id:
          type: string
          example: xxxxxxxxxx
    ClientCredentialsTokenRequest:
      type: object
      required:
        - grant_type
        - client_id
        - client_secret
      properties:
        grant_type:
          type: string
          enum: [client_credentials]
        client_id:
          type: string
          example: xxxxxxxxxx
        client_secret:
          type: string
          format: password
          example: xxxxxxxxxx
    RefreshTokenRequest:
      type: object
      required:
        - grant_type
        - refresh_token
      properties:
        grant_type:
          type: string
          enum: [refresh_token]
        refresh_token:
          type: string
          format: password
          example: xxxxxxxxxx

Do you actually need to document the payload for the token endpoint? If it's a standard OAuth 2.0 token endpoint as per RFC 6749, you could define it in securitySchemes instead:

paths:
  /something:
    get:
      description: One of the endpoints that require OAuth Bearer token.
      security:
        - oauth2: []  # <-----
      responses:
        ...

components:
  securitySchemes:
    oauth2:           # <-----
      type: oauth2
      flows:
        authorizationCode:
          authorizationUrl: '???' # TODO
          tokenUrl: /v1/token
          refreshUrl: '???' # TODO
          scopes: {}
        clientCredentials:
          tokenUrl: /v1/token
          refreshUrl: '???' # TODO
          scopes: {}
Related