swagger-api / swagger-api/swagger-ui

OpenID Implicit Flow broken: missing nonce, wrong response_type, issues with window.opener

Open
#8,315 3 comments 6 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
JavaScript
Stars
29k
Forks
9.3k
Avg merge
2d 23h
Merged PRs (30d)
25

Description

Q&A (please complete the following information)
  • OS: Ubuntu 22.04
  • Browser: Chrome (google-chrome-stable)
  • Version: Version 107.0.5304.121 (Official Build) (64-bit)
  • Method of installation: deb package from official website
  • Swagger-UI version: 4.15.5
  • Swagger/OpenAPI version: OpenAPI 3.0.2
Content & configuration

Both Swagger Docs and the OpenID Authorization Server are served by the same domain: https://develhosthere.com

The OpenID Authorization Server has configured a Client with ID "project-api-docs" allowed to use the OpenID Implicit flow.

Swagger/OpenAPI definition:

openapi: 3.0.2
info:
  title: SuiteIAM API
  version: v1
paths:
  /core/api/v1/health:
    get:
      operationId: _core_api_v1_health__get
      description: ''
      parameters: []
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthRead'
          description: ''
      tags:
      - health
      security:
      - OpenID: []
components:
  schemas:
    HealthRead:
      type: object
      properties:
        status:
          type: string
          default: ok
  securitySchemes:
    OpenID:
      type: openIdConnect
      openIdConnectUrl: https://develhosthere.com/core/identity/provider/openid/.well-known/openid-configuration

Swagger-UI configuration options:

SwaggerUI({
        url: "https://develhosthere.com/core/api/v1/openapi.yml",
        dom_id: "#swagger-ui",
        presets: [
          SwaggerUIBundle.presets.apis,
          SwaggerUIBundle.SwaggerUIStandalonePreset,
        ],
        layout: "BaseLayout",
        deepLinking: true,
        tagsSorter: "alpha",
        operationsSorter: "alpha",
        // Authorization config
        persistAuthorization: true,
        clientId: "project-api-docs",
})
Describe the bug you're encountering

As far as I know, the Swagger UI is designed to work as a browser application that does not depend on a backend server. Such applications (e.g. a SPA) MUST use the OpenID/OAuth2 Implicit flow since they CANNOT safely store a client_secret (as we'd do in an authorization_code flow).

When using the security scheme openIdConnect , we are telling the Swagger UI to use the OpenID protocol, which is a layer on top of the OAuth2. This enables three new authorization methods at the "Authorize" button. Everything's okay so far.

Three problems arise when working with the OpenID Implicit flow.

  1. The response_type should be "id_token" or "id_token token" (see RFC Section 3) but Swagger UI is using "token". This would be valid for an OAuth2 flow, but the "id_token" is essential to make a proper OpenID request.

  2. The nonce parameter is missing but required by the standard as stated in RFC Section 3.2.2.1. A standard-compliant OpenID Authorization Server rejects the request if this parameter is not present. The client (Swagger UI) should validate this parameter was the one sent in the initial request (as it's done with the state in the oauth2-redirect.html)

  3. The current implementation would work after fixing these issues if both the Swagger Docs and the Authorization Server are served by the same domain. Otherwise the window.opener at the oauth2-redirect.html receives a null. At least, this happens to me with my chrome version.

To reproduce...

To fully reproduce the issue, we should use a real OpenID Authorization Server with a Client configured to use the Implicit flow. In a real scenario, the Authorization Server MUST complain about the missing nonce and wrong response_type.

Nevertheless, what should happen could be tested without having a real Auth Server:

  1. Go to the Swagger UI docs
  2. Click on "Authorize"
  3. Choose the implicit flow (fill in the client_id and at least select the "openid" scope to make a proper OpenID request)
  4. The Authorize URL should contain a nonce, and the response_type should be "id_token token" (the "id_token" option would make a proper OpenID request, but we won't get the access_token in response).

In the redirect page, we should check that the nonce is equal to the sent one (as it's done with the state). The problem is Swagger UI should not rely on the window.opener to support the case when the Swagger UI and Authorization Server are in different domains. This case could often happen when using a 3rd party solution for the OpenID part.

Expected behavior

Described in the previous section

Screenshots

Does not apply

Additional context or thoughts

Regarding the window.opener issue, could the localStorage be a solution? The browser offers an storage event to listen when other context (tab or window) changes a value.

Regarding the parameters, this plugin partially fixes the issue. I think this behaviour should be provided by the Swagger UI itself without relying on additional plugins. Moreover, this code does not check the nonce on the redirect page and won't work well when multiple securitySchemes are provided (e.g. when mixing OpenID with other OAuth2)

Workaround using a plugin
      /**
       * Issue a random nonce value.
       *
       * https://stackoverflow.com/a/1349426
       */
      function randomNonce() {
        var result = "";
        var characters =
          "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        var charactersLength = characters.length;
        for (var i = 0; i < 10; i++) {
          result += characters.charAt(
            Math.floor(Math.random() * charactersLength)
          );
        }
        return result;
      }

      /**
       * Fix OpenID request parameters.
       *
       * According to the specification [1], the authorize request for an OpenID Implicit flow MUST contain
       * either "id_token" or "id_token token", but SwaggerUI only uses "token" which actually matches an
       * OAuth2 (not OpenID) Implicit flow.
       *
       * The second patch is for the nonce parameter that MUST be present in a Implicit flow request [2]. This
       * plugin does NOT validate that nonce value at redirect matches the initially issued. To do so, we'd need
       * to store it at the localStorage to later check at the redirect URL.
       *
       * This code is inspired by [3], mentioned at [4]
       *
       * [1] https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.3
       * [2] https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.3.2.2.1
       * [3] https://github.com/inouiw/SwaggerUIJsonWebToken/blob/master/wwwroot/swagger-extensions/my-swagger-ui-plugins.js
       * [4] https://github.com/swagger-api/swagger-ui/issues/7698
       */
      const OpenIdImplicitFlowFix = function (system) {
        return {
          statePlugins: {
            auth: {
              wrapActions: {
                // Called when you click in the 'Authorize' in the Authorize pop-up.
                authPopup:
                  (oriAction, system) => (url, swaggerUIRedirectOauth2) => {
                    const nonce = randomNonce();
                    const newUrl = url.replace(
                      "response_type=token",
                      `response_type=token+id_token&nonce=${nonce}`
                    );
                    console.log(`authPopup wrapAction. new url: ${newUrl}`);
                    return oriAction(newUrl, swaggerUIRedirectOauth2);
                  },
              },
            },
          },
        };
      };

      const ui = SwaggerUIBundle({
        url: "{% url schema_url %}",
        dom_id: "#swagger-ui",
        presets: [
          SwaggerUIBundle.presets.apis,
          SwaggerUIBundle.SwaggerUIStandalonePreset,
        ],
        layout: "BaseLayout",
        deepLinking: true,
        tagsSorter: "alpha",
        operationsSorter: "alpha",
        // Authorization config
        persistAuthorization: true,
        clientId: "suiteiam-api-docs",
        // Workaround for the missing params
        plugins: [OpenIdImplicitFlowFix],
      });

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by tracing the authPopup action and oauth2-redirect.html, then reproduce the implicit-flow authorization URL described in the issue. Compare the generated response_type and nonce with the OpenID requirements, and inspect how the redirect page communicates with the opener. Done means valid OpenID parameters, nonce validation, and redirect handling that does not depend on a same-domain window.opener.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript
Domain
authentication, frontend, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.