This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
get_swagger_ui_html does not pass X-API-Key to openapi_url for authorized access #2678
Comments
Relevant SO item at
If I've read those correctly, the proposed solution is a // The spec URL
const url = "https://www.example.com/authorised-users-only/spec.json";
SwaggerUI({
url, // spec url
requestInterceptor: (req) => {
// Only set Authorization header if the request matches the spec URL
if (req.url === url) {
req.headers.Authorization = "Basic " + btoa("myUser" + ":" + "myPassword");
}
return req;
}
}) For my question, the header details would be an For comparison, the <body>
<div id="swagger-ui">
</div>
<script src="https://cdn.jsdelivr.net/npm/swagger-ui-dist@3/swagger-ui-bundle.js"></script>
<!-- `SwaggerUIBundle` is now available on the page -->
<script>
const ui = SwaggerUIBundle({
url: '/openapi.json',
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIBundle.SwaggerUIStandalonePreset
],
layout: "BaseLayout",
deepLinking: true,
showExtensions: true,
showCommonExtensions: true
})
</script>
</body> |
Using a copy-paste and hack-it, the following custom function adds support for a request interceptor that adds an X-API-Key header in order to get the The key addition is the if api_key:
req = ' requestInterceptor: (req) => { if (req.url.endsWith("openapi.json")) { req.headers["X-API-Key"] = "%s"; }; return req; },' % api_key
html += req The full function: import json
from typing import Any
from typing import Dict
from typing import Optional
from fastapi.encoders import jsonable_encoder
from starlette.responses import HTMLResponse
def custom_swagger_ui_html(
*,
openapi_url: str,
title: str,
swagger_js_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@3/swagger-ui-bundle.js",
swagger_css_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@3/swagger-ui.css",
swagger_favicon_url: str = "https://fastapi.tiangolo.com/img/favicon.png",
oauth2_redirect_url: Optional[str] = None,
init_oauth: Optional[Dict[str, Any]] = None,
api_key: Optional[str] = None,
) -> HTMLResponse:
# Adapted from fastapi.openapi.docs.get_swagger_ui_html
# Related to https://github.com/tiangolo/fastapi/issues/2678
# Related to https://github.com/swagger-api/swagger-ui/issues/2793
html = f"""
<!DOCTYPE html>
<html>
<head>
<link type="text/css" rel="stylesheet" href="{swagger_css_url}">
<link rel="shortcut icon" href="{swagger_favicon_url}">
<title>{title}</title>
</head>
<body>
<div id="swagger-ui">
</div>
<script src="{swagger_js_url}"></script>
<!-- `SwaggerUIBundle` is now available on the page -->
<script>
const ui = SwaggerUIBundle({{
url: '{openapi_url}',
"""
if api_key:
req = ' requestInterceptor: (req) => { if (req.url.endsWith("openapi.json")) { req.headers["X-API-Key"] = "%s"; }; return req; },' % api_key
html += req
if oauth2_redirect_url:
html += f"oauth2RedirectUrl: window.location.origin + '{oauth2_redirect_url}',"
html += """
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIBundle.SwaggerUIStandalonePreset
],
layout: "BaseLayout",
deepLinking: true,
showExtensions: true,
showCommonExtensions: true
})"""
if init_oauth:
html += f"""
ui.initOAuth({json.dumps(jsonable_encoder(init_oauth))})
"""
html += """
</script>
</body>
</html>
"""
return HTMLResponse(html) The resulting HTML contains const ui = SwaggerUIBundle({
url: '/openapi.json',
requestInterceptor: (req) => { if (req.url.endsWith("openapi.json")) { req.headers["X-API-Key"] = "xxx-api_key-xxx"; }; return req; },
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIBundle.SwaggerUIStandalonePreset
],
layout: "BaseLayout",
deepLinking: true,
showExtensions: true,
showCommonExtensions: true
}) It seems to work, so that the openapi.json doc is protected, e.g. @app.get("/openapi.json", include_in_schema=False, tags=["documentation"])
async def get_open_api_endpoint(api_key: str = Depends(get_api_key)):
response = JSONResponse(app.openapi())
return response The swagger-ui exposes any API Keys in the browser in the curl command after using the swagger authorize UI, so any concerns about exposing the API key may just belong with the end user. Perhaps the requestInterceptor could also check that the endpoint is an HTTPS connection before adding the API key (maybe it makes localhost dev/test cycles more tedious or difficult while better protecting the API key in the wild). |
The additional tweaks for this to work with AWS API-Gateway require adding an API-Gateway server to the openapi.json doc, e.g. assume that def get_aws_uri(request: Request) -> Optional[str]:
# https://mangum.io/adapter/#retrieving-the-aws-event-and-context
# https://github.com/awsdocs/aws-lambda-developer-guide/blob/master/sample-apps/nodejs-apig/event.json
aws_event = request.scope.get("aws.event", {})
context = aws_event.get("requestContext", {})
domain_name = context.get("domainName")
domain_stage = context.get("stage")
if domain_name and domain_stage:
url = f"https://{domain_name}/{domain_stage}"
return urlparse(url).geturl()
# now within factory method that creates FastAPI app:
def custom_openapi(request: Request):
if app.openapi_schema:
return app.openapi_schema
openapi_schema = app.openapi()
aws_uri = get_aws_uri(request)
if aws_uri:
# AWS API-Gateway is deployed to a baseURL path for a stage
openapi_schema["servers"] = [{"url": aws_uri}]
app.openapi_schema = openapi_schema
return app.openapi_schema
@app.get("/openapi.json", include_in_schema=False, tags=["documentation"])
async def get_open_api_endpoint(
request: Request, api_key: str = Depends(get_api_key)
):
response = JSONResponse(custom_openapi(request))
return response
@app.get("/docs", include_in_schema=False, tags=["documentation"])
async def get_documentation(request: Request, api_key: str = Depends(get_api_key)):
aws_uri = get_aws_uri(request)
if aws_uri:
openapi_uri = f"{aws_uri}/openapi.json"
else:
openapi_uri = "/openapi.json"
response = custom_swagger_ui_html(
openapi_url=openapi_uri, title="docs", api_key=api_key
)
return response Maybe all that could be avoided if the |
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
First check
get_swagger_ui_html
Example
See https://medium.com/data-rebels/fastapi-authentication-revisited-enabling-api-key-authentication-122dc5975680 but the use of cookies to hold an API Key circumvents the behavior of the swagger-UI so that all the authorized endpoints are working without using the swagger authenticate entry. If the cookie is not set (as below) and the api-key auth only allows an X-API-Key header, then the swagger-UI fails to get the openapi.json document; an error appears like "Failed to load API definition. Fetch Error. Unauthorized /openapi.json".
It seems like there should be a way to call
get_swagger_ui_html
so that any swagger JS can use an X-API-Key to authenticate when it tries to fetch the /openapi.json document. Or, is there a way to define/docs
so that the swagger-ui is provided theapp.openapi()
content without needing to call a/openapi.json
endpoint that is protected by an X-API-Key? (Additional context - a deployment uses an AWS API-Gateway with X-API-Key auth enabled on the API-Gateway with a use policy attached to the API keys - the API-Gateway seems to be tricky to configure to allow unrestricted access to/openapi.json
when everything else requires an API key or some other auth, even if the fastapi itself might allow unrestricted access to/openapi.json
.). Is it considered safe/secure to allow unrestricted access to/openapi.json
?See the medium post link above but, below, the cookie for an api key is disabled.
Description
/docs
.Request
Would it be possible to use
The function signature does not yet include such an option, i.e.
The X-API-Key auth option is not covered by any
init_oauth
options, is it? It should not require any OAuth flows.I'm not so familiar with swagger JS, is it possible to use it with an X-API-Key?
Are there fatal security flaws in attempting to use swagger JS in this way? e.g. it could embed the API key in the JS payload somewhere, basically exposing it in the HTML/JS response payload.
When
/docs
returns, it is possible to force it to open a modal dialog that would present the swagger-ui auth dialog that requires an API-key auth? (Assuming that the swagger JS can even get the/openapi.json
doc)Environment
The text was updated successfully, but these errors were encountered: