swagger-ui
swagger-ui copied to clipboard
redirect endpoint returning a 302 redirect to AWS S3 - generating an auth problem
I have a download endpoint in my API which is redirecting the user to a AWS S3 presigned URL
Here is the swagger file describing my endpoint:
openapi: 3.0.0
info:
title: My API
description: API
version: 2.0
servers:
- url: myapi.com
description: API v2.0.
components:
securitySchemes:
Auth:
type: apiKey
in: header
name: Authorization
security:
- Auth: []
paths:
/download/:
get:
summary: Download
description: Download
responses:
'302':
description: Redirects to a location for downloading
content:
application/gzip:
schema:
type: string
format: binary
My problem is when the SwaggerUI (version 3.14.2) it trying the endpoint it does get the redirect order, but when it tries to go the redirect location it for some reason sends the "Authorization" header to that URL although it's not on the same domain.
This problem is causing AWS S3 to omit the following error because it's receiving both "Authorization" header and the "AWSAccessKeyId" get parameters.
<?xml version="1.0"?>
<Error>
<Code>InvalidArgument</Code>
<Message>Only one auth mechanism allowed; only the X-Amz-Algorithm query parameter, Signature query string parameter or the Authorization header should be specified</Message>
<ArgumentName>Authorization</ArgumentName>
<ArgumentValue>Token TTTTTTTTTTTTT</ArgumentValue>
<RequestId>RRRRRRRRRRRRRR</RequestId>
<HostId>HHHHHHHHHHHHHHHH</HostId>
</Error>
Any idea how to solve this issue?
Hey @kobymeir!
Transparently following the redirect your server provides is intended behavior. I've mentioned this before over in Swagger Client - see https://github.com/swagger-api/swagger-js/issues/1030#issuecomment-312728833.
I'm curious what led you to the expectation that the Authorization header wouldn't be forwarded to the redirect location - if you can shed some light on that, it'd be helpful in considering whether this is something we should explore changing 😄
Hey shockey, we have the same problem here. Currently you cannot use Swagger-Ui with AWS, if you have such an authorization step.
Swagger-Ui sends the original authorization token together with the redirect authorization token to AWS. AWS complains with a 400 bad request. Swagger-Ui should send only the redirect authorization token in the final request, then it would work.
Currently, we are forced to use Postman instead of Swagger-Ui, because of this problem. It would be nice, if this gets fixed soon.
Hi @shockey
There is no reason to pass on the authorization headers as you have no idea where the redirect it going, thus potentially exposing the authorization token to a 3rd party that we might not want them to receive the token.
Hope that my answered helped :)
Postman had the problem in the past: https://github.com/postmanlabs/postman-app-support/issues/1160
@kobymeir @MaxHo1234, upon further inspection, this is a limitation within the browser. The request is being transparently followed, with no way to control the behavior from within a web application like Swagger UI. Postman gets around this with their Interceptor extension, which is not subject to the same constraints.
I'm going to keep this open: if there's sufficient interest, we could consider building a similar extension that allows users to circumvent browser limitations.
This is a few months late, but I had this same problem and found a fix - thought I'd update anyone who comes across this page.
The problem seems to be when using an 'Authorization' header to access your api then getting redirected to a signed url. There is a conflict because your browser (Chrome in my case) doesn't like that there are two separate authorizations: the signed url authorizations and the header you passed called 'Authorization' - as @shockey mentioned.
The easy fix for me was to change my 'Authorization' header to something different...'usertoken' in my case. Then update your API to authorize based on your new header and boom! It works like a charm.
I've hit this problem today, and I feel like @dalearbo 's solution would be inappropriate: I certainly won't change my server and API code to accommodate the lack of forwarding of the Authorization header.
However, while digging further I realized that the problem was my fault: my server was redirecting to the wrong address (https://example.com/api/v1/blah) whereas it should have been (http://... ). In order words, I was mixing my https and http server. After fixing this small problem, the redirect works as expected, keeping the Authorization header.
Just a heads up for people in the future how stumble into this issue.
@dojeta: Thanks for your comment. Just to be clear, my solution does not drop the authorization header. What you’re doing is just calling your authorization header something else because “Authorization” is a reserved keyword. You can literally call it anything else and you’re still sending it - your server is still requiring the header that you choose so there is no security issue created. I think that we are basically troubleshooting separate issues but it should be known that sending an “Authorization” header in addition to a signed URL is not possible in Chrome.
Side note: application/gzip
on 302 doesn't make any sense.
We've just run into this problem. In response to this:
I'm curious what led you to the expectation that the Authorization header wouldn't be forwarded to the redirect location - if you can shed some light on that, it'd be helpful in considering whether this is something we should explore changing
Funny that this discussion has happened in GitHub issues and StackOverflow threads for all kinds of HTTP clients, but usually not forwarding the header is considered the default, and the discussion proceeds from there ... :-)
I wish the HTTP spec was more specific about it.
But in any case, Swagger is making an unusual choice by forwarding our Authorization header to S3. Chrome, Safari and Internet Explorer don't work that way. Python Requests and Postman also fixed this problem -- Postman has made it an opt-in behaviour. Curl actually treated it as a security issue and removed the behaviour a couple of years ago.
As I committer of Apache HttpComponents: HttpClient will never do this, unless you wide-scope your credentials. Similar issue: https://issues.apache.org/jira/browse/WAGON-590
I too came across this problem and realized that when fetch
redirects to the presigned S3 URL you can't prevent it from sending the Authorization headers from your API.
Eventually I have able to get this working by using the responseInterceptor
configuration argument for Swagger with a custom function that detects the bad request (400) response from S3 and then re-issues the fetch
request with credentials: 'omit'
.
Here is my custom response interceptor for Swagger:
// swagger-ui-extensions.js
function serializeHeaderValue(value) {
const isMulti = value.includes(', ');
return isMulti ? value.split(', ') : value;
}
function serializeHeaders(headers = {}) {
return Array.from(headers.entries()).reduce((acc, [header, value]) => {
acc[header] = serializeHeaderValue(value);
return acc;
}, {});
}
function myResponseInterceptor(response) {
// NOTE: Additional checks should probably be added whether to re-issue the fetch. This was just an initial starting point.
if (response.ok === false && response.status === 400 && response.headers['server'] === 'AmazonS3') {
// Here is the important part, re-issue fetch but don't allow our Authentication header to flow
response = fetch(response.url, { credentials: 'omit' })
.then(nativeResponse => {
// We can't return the native response because Swagger UI attempts to assign the header property (and potentially other properties
// too) on the response. So return a serialized clone of the native response. FYI, this is the same exact logic from Swagger's fake
// implementation of fetch.
const getBody = nativeResponse.blob || nativeResponse.buffer;
return getBody.call(nativeResponse).then(body => {
return {
ok: nativeResponse.ok,
url: nativeResponse.url,
status: nativeResponse.status,
statusText: nativeResponse.statusText,
headers: serializeHeaders(nativeResponse.headers),
data: body
};
});
});
}
return response;
}
Then I had to specify my custom myResponseInterceptor
when initializing the Swagger UI in index.html
// (other code omitted for brevity...)
// Make sure to include your custom JS in the page
// <script src="./swagger-ui-extensions.js"></script>
// Specifying the custom responseInterceptor here...
configObject.responseInterceptor = myResponseInterceptor;
// Begin Swagger UI call region
const ui = SwaggerUIBundle(configObject);
ui.initOAuth(oauthConfigObject);
// End Swagger UI call region
window.ui = ui;
I was using ASP.NET Core and used these instructions to provide my own index.html
for Swagger UI:
https://github.com/domaindrivendev/Swashbuckle.AspNetCore#customize-indexhtml
After all that, this surprisingly worked and I was able to see the redirected response from S3 in Swagger.
I have the same problem with Azure Blob Storage Shared Access Signatures.
Same problem with MinIO. It is necessary to somehow remove Authorization from header in redirected request.
I was able to also solve the issue with MinIO by using the responseInterceptor solution mentioned up above.
Have you needed to update the code you posted above since you posted it, @polewskm ? I ask as I'm trying to use your fix, and, while the re-fetch appears to work, Swagger cannot deal with the response and shows the message "Could not render n, see the console" rather than initiate the S3 file download.