swagger-api / swagger-api/swagger-ui
OpenID Implicit Flow broken: missing nonce, wrong response_type, issues with window.opener
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.
-
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.
-
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
statein the oauth2-redirect.html) -
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.openerat 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:
- Go to the Swagger UI docs
- Click on "Authorize"
- Choose the implicit flow (fill in the client_id and at least select the "openid" scope to make a proper OpenID request)
- The Authorize URL should contain a
nonce, and theresponse_typeshould 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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