{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-learn/arazzo/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":["code-walkthrough","step"]},"redocly_category":"Learn","type":"markdown"},"seo":{"title":"OAuth2 Authorization with Arazzo with Redocly Respect","description":"OpenAPI-generated documentation tool with 24k+ stars on Github - make APIs your company's superpower.","siteUrl":"https://redocly.com","image":"/assets/redocly-card.f670aae34a39545a5ea633a540cb3a4a333a1f23bb2ed3c4a1b17a5fbcf0ac85.db81178d.png","lang":"en-US","llmstxt":{"hide":false,"sections":[{"title":"Table of contents","includeFiles":["**/*"],"excludeFiles":[]}],"excludeFiles":[]}},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"CodeWalkthrough","attributes":{"__idx":1,"filters":{},"filesets":[{"files":[{"path":"learn/arazzo/_filesets/practical-example-series/oauth2/redocly-cafe-api.arazzo.yaml","content":["arazzo: 1.0.1","info:","  title: Redocly Cafe API - Products","  version: 1.0.0","  description: This is the API workflow for the Redocly Cafe Products.",{"start":5,"condition":{"steps":["final-sources"]},"children":["sourceDescriptions:","  - name: redocly-cafe-api","    type: openapi","    url: redocly-cafe-api.yaml","  - name: authorization","    type: arazzo","    url: authorization.arazzo.yaml"]},"","workflows:","  - workflowId: menu-items-workflow","    summary: Menu Items Workflow","    steps:",{"start":19,"condition":{"steps":["final-step-authorize"]},"children":["      - stepId: authorize","        workflowId: $sourceDescriptions.authorization.workflows.authorize-with-client_credentials","        outputs:","          access_token: $outputs.access_token_with_client_credentials","          client_id: $outputs.client_id"]},{"start":26,"condition":{"steps":["final-step-create"]},"children":["      - stepId: create-menu-item","        operationId: $sourceDescriptions.redocly-cafe-api.createMenuItem","        description: This step creates a new menu item.",{"start":30,"condition":{"steps":["final-x-security"]},"children":["        x-security:","          - schemeName: OAuth2","            values:","              accessToken: $steps.authorize.outputs.access_token","              clientId: $steps.authorize.outputs.client_id"]},{"start":37,"condition":{"steps":["final-create-body"]},"children":["        requestBody:","          contentType: multipart/form-data","          payload:","            name: $faker.string.uuid()","            price: 4000","            calories: 8000","            category: 'dessert'","            photoTextDescription: 'this is a tiramisu'"]},"        successCriteria:","          - condition: $statusCode == 201","          - condition: $response.body#/id != null","        outputs:","          menuItemId: $response.body#/id","          name: $response.body#/name"]},{"start":54,"condition":{"steps":["final-step-verify"]},"children":["      - stepId: verify-menu-item","        operationId: $sourceDescriptions.redocly-cafe-api.listMenuItems","        description: Verifies the created menu item","        parameters:","          - in: query","            name: filter","            value: 'name:{$steps.create-menu-item.outputs.name}'","        successCriteria:","          - condition: $statusCode == 200","          - condition: $response.body#/items/0/name == $steps.create-menu-item.outputs.name"]},""],"metadata":{"steps":["final-sources","final-step-authorize","final-step-create","final-x-security","final-create-body","final-step-verify"]},"basename":"redocly-cafe-api.arazzo.yaml","language":"yaml"},{"path":"learn/arazzo/_filesets/practical-example-series/oauth2/authorization.arazzo.yaml","content":["arazzo: 1.0.1","info:","  title: Redocly Cafe API - Authorization","  version: 1.0.0","  description: This is the API workflow for the Redocly Cafe OAuth2 client registration.",{"start":5,"condition":{"steps":["auth-sources"]},"children":["sourceDescriptions:","  - name: redocly-cafe-api","    type: openapi","    url: redocly-cafe-api.yaml",{"start":10,"condition":{"steps":["auth-source-arazzo"]},"children":["  - name: register-oauth2","    type: arazzo","    url: register-oauth2-client.arazzo.yaml"]}]},"","workflows:","  - workflowId: authorize-with-client_credentials","    summary: Authorize a new OAuth2 client","    steps:",{"start":21,"condition":{"steps":["auth-step-reuse"]},"children":["      - stepId: register-oauth2-client","        description: This step registers a new OAuth2 client.","        workflowId: $sourceDescriptions.register-oauth2.workflows.register-oauth2-client-workflow","        outputs:","          clientId: $outputs.clientId","          clientSecret: $outputs.clientSecret"]},{"start":29,"condition":{"steps":["auth-step-token"]},"children":["      - stepId: authorize-with-client_credentials",{"start":31,"condition":{"steps":["auth-x-operation"]},"children":["        x-operation:","          method: POST","          url: https://cafe.cloud.redocly.com/oauth2/token"]},"        parameters:","          - in: header","            name: content-type","            value: application/x-www-form-urlencoded",{"start":40,"condition":{"steps":["auth-token-request-body"]},"children":["        requestBody:","          payload:","            grant_type: client_credentials","            client_id: $steps.register-oauth2-client.outputs.clientId","            client_secret: $steps.register-oauth2-client.outputs.clientSecret"]},"        successCriteria:","          - condition: $statusCode == 200","          - condition: $response.body#/access_token != null",{"start":50,"condition":{"steps":["auth-token-output"]},"children":["        outputs:","          access_token: $response.body#/access_token"]}]},{"start":55,"condition":{"steps":["auth-workflow-outputs"]},"children":["    outputs:","      access_token_with_client_credentials: $steps.authorize-with-client_credentials.outputs.access_token","      client_id: $steps.register-oauth2-client.outputs.clientId"]},""],"metadata":{"steps":["auth-sources","auth-source-arazzo","auth-step-reuse","auth-step-token","auth-x-operation","auth-token-request-body","auth-token-output","auth-workflow-outputs"]},"basename":"authorization.arazzo.yaml","language":"yaml"},{"path":"learn/arazzo/_filesets/practical-example-series/oauth2/register-oauth2-client.arazzo.yaml","content":["arazzo: 1.0.1","info:","  title: Redocly Cafe API - Register OAuth2 Client","  version: 1.0.0","  description: This is the API workflow for the Redocly Cafe OAuth2 client registration.",{"start":5,"condition":{"steps":["register-source"]},"children":["sourceDescriptions:","  - name: redocly-cafe-api","    type: openapi","    url: redocly-cafe-api.yaml"]},"",{"start":12,"condition":{"steps":["register-workflow"]},"children":["workflows:","  - workflowId: register-oauth2-client-workflow","    summary: Register a new OAuth2 client","    description: This workflow registers a new OAuth2 client.","    steps:",{"start":18,"condition":{"steps":["register-step"]},"children":["      - stepId: register-oauth2-client",{"start":20,"condition":{"steps":["register-step-operation"]},"children":["        operationId: $sourceDescriptions.redocly-cafe-api.registerOAuth2Client"]},"        description: This step registers a new OAuth2 client.",{"start":24,"condition":{"steps":["register-request-body"]},"children":["        requestBody:","          payload:","            name: code","            redirectUris:","              - https://cafe.cloud.redocly.com/callback","            scopes:","              - 'menu:read'","              - 'menu:write'","            grantTypes:","              - 'client_credentials'","              - 'authorization_code'"]},{"start":37,"condition":{"steps":["register-success-criteria"]},"children":["        successCriteria:","          - condition: $statusCode == 201","          - condition: $response.body#/clientId != null","          - condition: $response.body#/clientSecret != null"]},{"start":43,"condition":{"steps":["register-step-outputs"]},"children":["        outputs:","          clientId: $response.body#/clientId","          clientSecret: $response.body#/clientSecret"]}]},{"start":49,"condition":{"steps":["register-workflow-outputs"]},"children":["    outputs:","      clientId: $steps.register-oauth2-client.outputs.clientId","      clientSecret: $steps.register-oauth2-client.outputs.clientSecret"]}]},""],"metadata":{"steps":["register-source","register-workflow","register-step","register-step-operation","register-request-body","register-success-criteria","register-step-outputs","register-workflow-outputs"]},"basename":"register-oauth2-client.arazzo.yaml","language":"yaml"},{"path":"learn/arazzo/_filesets/practical-example-series/oauth2/redocly-cafe-api.yaml","content":["openapi: 3.1.0","info:","  title: Redocly Cafe","  version: 1.0.0","  contact:","    email: team@redocly.com","    url: https://redocly.com/contact-us/","  license:","    name: MIT","    url: https://opensource.org/licenses/MIT","  termsOfService: https://redocly.com/subscription-agreement","servers:","  - url: https://cafe.cloud.redocly.com","tags:","  - name: Authorization","    description: Create a client to demo the API.","  - name: Products","    description: Operations related to products.","paths:","  /menu:","    get:","      tags:","        - Products","      summary: List all menu items","      operationId: listMenuItems","      parameters:","        - $ref: '#/components/parameters/Filter'","      security: []","      responses:","        '200':","          description: Successful operation.","          content:","            application/json:","              schema:","                $ref: '#/components/schemas/MenuItemList'","        '400':","          description: Bad request - invalid input parameters.","        '500':","          description: Internal server error.",{"start":39,"condition":{"steps":["openapi-create-menu","final-create-operation"]},"children":["    post:","      tags:","        - Products","      summary: Create menu item","      operationId: createMenuItem","      security:","        - OAuth2:","            - menu:write","      requestBody:","        required: true","        content:","          multipart/form-data:","            schema:","              $ref: '#/components/schemas/MenuItem'","      responses:","        '201':","          description: Menu item created successfully.","          content:","            application/json:","              schema:","                $ref: '#/components/schemas/MenuItem'","        '400':","          description: Bad request - invalid input parameters.","        '401':","          description: Unauthorized - authorization required.","        '403':","          description: Forbidden - insufficient permissions.","        '500':","          description: Internal server error."]},"  /menu/{menuItemId}:","    parameters:","      - $ref: '#/components/parameters/MenuItemId'","    delete:","      tags:","        - Products","      summary: Delete a menu item","      operationId: deleteMenuItem","      security:","        - OAuth2:","            - menu:write","      responses:","        '204':","          description: Menu item deleted successfully.","        '400':","          description: Bad request - invalid input parameters.","        '401':","          description: Unauthorized - authorization required.","        '403':","          description: Forbidden - insufficient permissions.","        '404':","          description: Resource not found.","        '500':","          description: Internal server error.",{"start":94,"condition":{"steps":["openapi-oauth-register","register-step-operation"]},"children":["  /oauth2/register:","    post:","      tags:","        - Authorization","      summary: Create OAuth2 client","      operationId: registerOAuth2Client","      security: []","      requestBody:","        required: true","        content:","          application/json:","            schema:","              $ref: '#/components/schemas/RegisterClientObject'","      responses:","        '201':","          description: OAuth2 client registered successfully.","          content:","            application/json:","              schema:","                $ref: '#/components/schemas/OAuth2Client'","        '400':","          description: Bad request - invalid input parameters.","        '401':","          description: Unauthorized - authorization required.","        '500':","          description: Internal server error."]},"components:",{"start":123,"condition":{"steps":["openapi-oauth2-scheme"]},"children":["  securitySchemes:","    OAuth2:","      type: oauth2","      flows:","        authorizationCode:","          authorizationUrl: https://cafe.cloud.redocly.com/oauth2/authorize","          tokenUrl: https://cafe.cloud.redocly.com/oauth2/token","          scopes:","            menu:read: Read access to menu items and images","            menu:write: Write access to menu items (create, delete)","        clientCredentials:","          tokenUrl: https://cafe.cloud.redocly.com/oauth2/token","          scopes:","            menu:read: Read access to menu items and images","            menu:write: Write access to menu items (create, delete)","    ApiKey:","      type: apiKey","      name: X-API-Key","      in: header"]},"  parameters:","    MenuItemId:","      name: menuItemId","      in: path","      required: true","      schema:","        type: string","        pattern: ^prd_[0-9abcdefghjkmnpqrstvwxyz]{26}$","    Filter:","      name: filter","      description: Filters the collection items using space-separated `name:value` pairs.","      in: query","      required: false","      schema:","        type: string","      example: orderId:ord_01h1s5z6vf2mm1mz3hevnn9va7","  schemas:","    Page:","      type: object","      properties:","        endCursor:","          type:","            - string","            - 'null'","        startCursor:","          type:","            - string","            - 'null'","        hasNextPage:","          type: boolean","        hasPrevPage:","          type: boolean","        limit:","          type: integer","          minimum: 1","          maximum: 100","          default: 10","        total:","          type: integer","          minimum: 0","      required:","        - endCursor","        - startCursor","        - hasNextPage","        - hasPrevPage","        - limit","        - total","    MenuBaseItem:","      type: object","      properties:","        createdAt:","          type: string","          format: date-time","          readOnly: true","        updatedAt:","          type: string","          format: date-time","          readOnly: true","        id:","          type: string","          readOnly: true","          pattern: ^prd_[0-9abcdefghjkmnpqrstvwxyz]{26}$","        object:","          type: string","          const: menuItem","          readOnly: true","        name:","          type: string","          minLength: 1","          maxLength: 50","        price:","          type: integer","          minimum: 0","        photo:","          writeOnly: true","          type:","            - string","            - 'null'","          format: binary","        photoUrl:","          readOnly: true","          type: string","          format: uri","        photoTextDescription:","          type:","            - string","            - 'null'","      required:","        - id","        - name","        - price","        - createdAt","        - updatedAt","        - object","    Beverage:","      allOf:","        - type: object","          properties:","            category:","              type: string","              const: beverage","            volume:","              type: number","              exclusiveMinimum: 0","            containsCaffeine:","              type: boolean","          required:","            - category","            - volume","            - containsCaffeine","        - $ref: '#/components/schemas/MenuBaseItem'","    Dessert:","      allOf:","        - type: object","          properties:","            category:","              type: string","              const: dessert","            calories:","              type: number","              exclusiveMinimum: 0","          required:","            - category","            - calories","        - $ref: '#/components/schemas/MenuBaseItem'","    MenuItem:","      discriminator:","        propertyName: category","        mapping:","          beverage: '#/components/schemas/Beverage'","          dessert: '#/components/schemas/Dessert'","      oneOf:","        - $ref: '#/components/schemas/Beverage'","        - $ref: '#/components/schemas/Dessert'","      required:","        - category","    MenuItemList:","      type: object","      properties:","        object:","          type: string","          const: list","        page:","          $ref: '#/components/schemas/Page'","        items:","          type: array","          items:","            $ref: '#/components/schemas/MenuItem'","      required:","        - object","        - page","        - items","    Error:","      type: object","      properties:","        type:","          type: string","          format: uri-reference","          default: about:blank","        title:","          type: string","        status:","          type: integer","          format: int32","          minimum: 100","          exclusiveMaximum: 600","        instance:","          type: string","          format: uri-reference","        details:","          type: object","          additionalProperties: true","      required:","        - type","        - title","        - status","    RegisterClientObject:","      type: object","      properties:","        name:","          type: string","        redirectUris:","          type: array","          items:","            type: string","            format: uri","        scopes:","          type: array","          items:","            type: string","            enum:","              - menu:read","              - menu:write","        grantTypes:","          type: array","          items:","            type: string","            enum:","              - authorization_code","              - client_credentials","      required:","        - name","    OAuth2Client:","      type: object","      properties:","        clientId:","          type: string","        clientSecret:","          type: string","        clientIdIssuedAt:","          type: integer","          format: int64","        clientSecretExpiresAt:","          type: integer","          format: int64","        name:","          type: string","        redirectUris:","          type: array","          items:","            type: string","            format: uri","        registrationClientUri:","          type: string","          format: uri","        registrationAccessToken:","          type: string","        scopes:","          type: array","          items:","            type: string","            enum:","              - menu:read","              - menu:write","        grantTypes:","          type: array","          items:","            type: string","            enum:","              - authorization_code","              - client_credentials","      required:","        - clientId","        - clientSecret","        - clientIdIssuedAt","        - clientSecretExpiresAt","        - registrationClientUri","        - registrationAccessToken",""],"metadata":{"steps":["openapi-create-menu","final-create-operation","openapi-oauth-register","register-step-operation","openapi-oauth2-scheme"]},"basename":"redocly-cafe-api.yaml","language":"yaml"}],"downloadAssociatedFiles":[]}],"steps":[{"id":"openapi-create-menu","heading":"Protected POST /menu operation"},{"id":"openapi-oauth-register","heading":"POST /oauth2/register"},{"id":"openapi-oauth2-scheme","heading":"OAuth2 security scheme"},{"id":"register-source","heading":"Reference the OpenAPI description"},{"id":"register-workflow","heading":"Define the registration workflow"},{"id":"register-step","heading":"A single registration step"},{"id":"register-step-operation","heading":"Connect the step to the operation"},{"id":"register-request-body","heading":"Supply the request body"},{"id":"register-success-criteria","heading":"Assert the response shape"},{"id":"register-step-outputs","heading":"Capture step outputs"},{"id":"register-workflow-outputs","heading":"Expose workflow outputs"},{"id":"auth-sources","heading":"Multiple source descriptions"},{"id":"auth-source-arazzo","heading":"Reusable Arazzo source"},{"id":"auth-step-reuse","heading":"Call the reusable workflow"},{"id":"auth-step-token","heading":"Call the token endpoint"},{"id":"auth-x-operation","heading":"x-operation extension"},{"id":"auth-token-request-body","heading":"Token request body"},{"id":"auth-token-output","heading":"Capture the access token"},{"id":"auth-workflow-outputs","heading":"Expose the authorization outputs"},{"id":"final-sources","heading":"Combine the OpenAPI and authorization sources"},{"id":"final-step-authorize","heading":"Reuse the authorization workflow"},{"id":"final-step-create","heading":"Create a menu item"},{"id":"final-x-security","heading":"Provide credentials with x-security"},{"id":"final-create-operation","heading":"The protected operation"},{"id":"final-create-body","heading":"Send a multipart/form-data body"},{"id":"final-step-verify","heading":"Verify the menu item"}],"inputs":{},"toggles":{}},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"oauth2-authorization-with-arazzo-with-redocly-respect","__idx":0},"children":["OAuth2 Authorization with Arazzo with Redocly Respect"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Use Respect powered by Arazzo workflows to test API endpoints that require OAuth2 authorization."," ","Learn how to define reusable workflows in separate files, pass values between workflows, and use those values to authorize API requests."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["You are going to:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Create reusable workflows with exposed outputs."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Authorize API requests with OAuth2."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Use the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["x-security"]}," Respect extension with a protected API operation."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"the-problem","__idx":1},"children":["The problem"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["In the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/learn/arazzo/practical-example-series/api-contract-testing-01"},"children":["Test API contracts using Respect"]}," tutorial, you learned how to execute an API contract test with ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://redocly.com/respect-cli"},"children":["Redocly Respect"]},"."," ","Let's create a more realistic workflow: calling protected API endpoints."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Real APIs are rarely open to public access."," ","They usually require an authentication or authorization flow before clients can read or change protected resources."," ","To test those endpoints with Respect, the workflow must first obtain credentials or an access token, then use that authorization data in later steps."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Respect supports this use case with the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://redocly.com/docs/respect/extensions/x-security#x-security-extension"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["x-security"]}," extension"]},", which lets a workflow provide security values for protected operations."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"prerequisites","__idx":2},"children":["Prerequisites"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Familiarity with the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://redocly.com/docs/respect/extensions/x-security#x-security-extension"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["x-security"]}," extension"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["An API described with OpenAPI."," ","The examples use a modified version of the Redocly Cafe API description."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"explore-the-openapi-description","__idx":3},"children":["Explore the OpenAPI description"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Open ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["redocly-cafe-api.yaml"]}," in the right panel."," ","The walkthrough focuses on three pieces: the protected ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST /menu"]}," operation, the OAuth2 security scheme, and the dynamic client registration endpoint."]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"openapi-create-menu","heading":"Protected POST /menu operation"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST /menu"]}," creates a menu item."," ","The operation declares ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["OAuth2"]}," with the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["menu:write"]}," scope under ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["security"]},", which means callers must present a valid OAuth2 access token with that scope."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The plain ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GET /menu"]}," operation, by contrast, leaves ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["security"]}," empty and is publicly accessible."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"openapi-oauth-register","heading":"POST /oauth2/register"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The API supports dynamic client registration through ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST /oauth2/register"]},"."," ","This endpoint is itself unauthenticated (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["security: []"]},") and returns a new ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["clientId"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["clientSecret"]}," that we can use to obtain an access token."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This flow follows the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://datatracker.ietf.org/doc/html/rfc7591"},"children":["Dynamic Client Registration Protocol (RFC 7591)"]},"."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"openapi-oauth2-scheme","heading":"OAuth2 security scheme"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["OAuth2"]}," security scheme defines two flows: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["authorizationCode"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["clientCredentials"]},"."," ","Both publish a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["tokenUrl"]}," and the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["menu:read"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["menu:write"]}," scopes."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This walkthrough uses these endpoints (and scopes) to obtain an access token and call the protected ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST /menu"]}," operation."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"step-1-register-an-oauth2-client","__idx":4},"children":["Step 1. Register an OAuth2 client"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Because client registration is useful in more than one workflow, describe it in its own Arazzo file and reuse it later."," ","Keeping the authorization details in a dedicated workflow keeps the protected-operation tests focused on the operation under test."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Switch to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["register-oauth2-client.arazzo.yaml"]}," in the right panel."]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"register-source","heading":"Reference the OpenAPI description"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The workflow declares a single OpenAPI source."," ","Operations in that source are accessible through ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["$sourceDescriptions.redocly-cafe-api.<operationId>"]},"."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"register-workflow","heading":"Define the registration workflow"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The file declares one workflow, ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["register-oauth2-client-workflow"]}," that registers a new client and exposes its credentials as workflow-level outputs."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"register-step","heading":"A single registration step"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The workflow has just one step, ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["register-oauth2-client"]},", which calls the registration operation."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"register-step-operation","heading":"Connect the step to the operation"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The step targets the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["registerOAuth2Client"]}," operation from the connected OpenAPI source."," ","Because the step is connected to a documented operation, Respect can automatically validate the response status, content type, and body schema."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"register-request-body","heading":"Supply the request body"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Because this is a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST"]}," request, the step provides a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["requestBody.payload"]}," with the client metadata."," ","Here we request only the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["client_credentials"]}," grant type and ask for both menu scopes."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"register-success-criteria","heading":"Assert the response shape"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The step asserts a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["201"]}," status code and the presence of ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["clientId"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["clientSecret"]}," in the response body."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"register-step-outputs","heading":"Capture step outputs"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The step extracts ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["clientId"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["clientSecret"]}," from the response body and stores them as step outputs."," ","Other steps in the same workflow can reference them through ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["$steps.register-oauth2-client.outputs.<name>"]},"."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"register-workflow-outputs","heading":"Expose workflow outputs"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["To make these values available to ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["other"]}," Arazzo files that reuse this workflow, the same values are also exposed as workflow-level ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["outputs"]},"."," ","This promotion is the bridge between a reusable workflow and the workflows that consume it."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Execute the file with Redocly CLI to inspect the API response and confirm which values are mapped to workflow outputs:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"npx @redocly/cli respect register-oauth2-client.arazzo.yaml --verbose\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"step-2-get-an-access-token","__idx":5},"children":["Step 2. Get an access token"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Switch to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["authorization.arazzo.yaml"]}," in the right panel."," ","This workflow exchanges the registered client for an access token in two steps:"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Reuse ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["register-oauth2-client-workflow"]}," to get a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["clientId"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["clientSecret"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Call the token endpoint and expose the returned access token as a workflow output."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"auth-sources","heading":"Multiple source descriptions"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This file declares two sources: the OpenAPI description (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["redocly-cafe-api"]},") and the previous Arazzo file (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["register-oauth2"]},")."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Arazzo source descriptions are not limited to OpenAPI - another Arazzo document is a valid source too."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"auth-source-arazzo","heading":"Reusable Arazzo source"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Adding the registration file as a source with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["type: arazzo"]}," is what enables reuse."," ","A step can now reference the registration workflow by id, just like operations reference OpenAPI ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["operationId"]},"s."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"auth-step-reuse","heading":"Call the reusable workflow"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The first step uses ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["workflowId"]}," (not ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["operationId"]},") to call the registration workflow from the other Arazzo file."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Its ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["outputs"]}," mirror the workflow-level outputs we exposed earlier (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["clientId"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["clientSecret"]},"), which makes them available locally as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["$steps.register-oauth2-client.outputs.*"]},"."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"auth-step-token","heading":"Call the token endpoint"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The second step, ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["authorize-with-client_credentials"]},", calls ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["/oauth2/token"]}," using the credentials returned by the previous step."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This walkthrough uses the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["client credentials"]}," grant, a machine-to-machine flow where the client authenticates itself directly with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["client_id"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["client_secret"]},"."," ","There is no user-agent or browser involved, so no ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["redirect_uri"]}," or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["/callback"]}," is needed - the token is returned directly in the response to this single request."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"auth-x-operation","heading":"x-operation extension"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["/oauth2/token"]}," is not described as an ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["operationId"]}," in the OpenAPI file, so the step uses the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://redocly.com/docs/respect/extensions/x-operation"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["x-operation"]}," Respect extension"]}," to declare the request URL and method inline."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Use ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["x-operation"]}," whenever you need to call a URL that is not part of the OpenAPI description."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"auth-token-request-body","heading":"Token request body"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The token request body follows the OAuth2 spec: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["grant_type"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["client_id"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["client_secret"]},"."," ","The credentials are read from the previous step's outputs using ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["$steps.register-oauth2-client.outputs.*"]},"."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"auth-token-output","heading":"Capture the access token"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The step extracts ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["access_token"]}," from the response body so the next workflow can use it to authorize protected requests."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"auth-workflow-outputs","heading":"Expose the authorization outputs"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Just like the registration workflow, the authorization workflow exposes its outputs at the workflow level so any consumer Arazzo file can read ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["access_token_with_client_credentials"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["client_id"]},"."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Execute this file:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"npx @redocly/cli respect authorization.arazzo.yaml --verbose\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Inspect the response body of the last step."," ","It contains the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["access_token"]}," used by the final workflow:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"    Response Body:\n      {\n        \"access_token\": \"********\",\n        \"token_type\": \"Bearer\",\n        \"expires_in\": 3600,\n        \"scope\": \"menu:read menu:write\"\n      } \n \n    ✓ success criteria check - $statusCode == 200\n    ✓ success criteria check - $response.body#/access_token != null\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"step-3-call-the-protected-endpoint","__idx":6},"children":["Step 3. Call the protected endpoint"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Switch to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["redocly-cafe-api.arazzo.yaml"]}," in the right panel."," ","This is the workflow you run in CI:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["authorize"]}," step calls the reusable authorization workflow."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["create-menu-item"]}," step reads ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["access_token"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["client_id"]}," from the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["authorize"]}," step outputs."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["x-security"]}," extension automatically constructs the appropriate authorization headers, query parameters, or cookies for the protected ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["createMenuItem"]}," operation."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"final-sources","heading":"Combine the OpenAPI and authorization sources"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The final file references two sources: the OpenAPI description for the operations under test, and the authorization Arazzo file for the access token."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"final-step-authorize","heading":"Reuse the authorization workflow"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The first step calls ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["authorize-with-client_credentials"]}," from the authorization file and stores the access token and client id locally."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This reuse pattern is exactly the same as the one inside ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["authorization.arazzo.yaml"]},", but now applied one level higher."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"final-step-create","heading":"Create a menu item"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["create-menu-item"]}," step calls the protected ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["createMenuItem"]}," operation."," ","It uses ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["$faker.string.uuid()"]}," to generate a unique ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["name"]}," so the test is repeatable."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"final-x-security","heading":"Provide credentials with x-security"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://redocly.com/docs/respect/extensions/x-security#x-security-extension"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["x-security"]}," extension"]}," tells Respect which security scheme to satisfy and how."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Here the workflow passes the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["accessToken"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["clientId"]}," it received from the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["authorize"]}," step."," ","Respect reads the security scheme from the OpenAPI description and adds the right headers automatically - you do not need to construct ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Authorization: Bearer ..."]}," yourself."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"final-create-operation","heading":"The protected operation"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This is the operation the step targets back in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["redocly-cafe-api.yaml"]},"."," ","Note the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["security"]}," block listing ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["OAuth2"]}," with the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["menu:write"]}," scope - that is exactly what the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["x-security"]}," block satisfies."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"final-create-body","heading":"Send a multipart/form-data body"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["createMenuItem"]}," accepts ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["multipart/form-data"]},", so the step declares ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["contentType"]}," explicitly and supplies the form fields as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["payload"]},"."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"final-step-verify","heading":"Verify the menu item"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The final step lists menu items and asserts that the item created in the previous step exists."," ","This step uses outputs from ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["create-menu-item"]}," directly in a request parameter and in a success criterion."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Run the workflow to confirm that the authorization step, the protected POST request, and the verification step all pass:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"npx @redocly/cli respect redocly-cafe-api.arazzo.yaml --verbose\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"summary","__idx":7},"children":["Summary"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Respect extensions make it possible to describe authorized API requests as part of an Arazzo workflow."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["By splitting the process into smaller workflows with outputs, you can reuse authentication and authorization steps across multiple API contract tests."," ","Reuse keeps complex workflows easier to maintain while still testing realistic protected API behavior."]}]}]},"headings":[{"value":"OAuth2 Authorization with Arazzo with Redocly Respect","id":"oauth2-authorization-with-arazzo-with-redocly-respect","depth":1},{"value":"The problem","id":"the-problem","depth":2},{"value":"Prerequisites","id":"prerequisites","depth":2},{"value":"Explore the OpenAPI description","id":"explore-the-openapi-description","depth":2},{"value":"Step 1. Register an OAuth2 client","id":"step-1-register-an-oauth2-client","depth":2},{"value":"Step 2. Get an access token","id":"step-2-get-an-access-token","depth":2},{"value":"Step 3. Call the protected endpoint","id":"step-3-call-the-protected-endpoint","depth":2},{"value":"Summary","id":"summary","depth":2}],"frontmatter":{"markdown":{"toc":{"hide":true}},"footer":{"hide":true},"seo":{"title":"OAuth2 Authorization with Arazzo with Redocly Respect"}},"lastModified":"2026-05-22T11:37:22.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/learn/arazzo/practical-example-series/oauth2-authorization-02","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}