Mixed Content errors in Swagger UI served by APIFlask

108 Views Asked by At

New to APIFlask, but I have a small app that works fine locally. I can go to /docs to load Swagger UI then use that to successfully test my endpoints.

However, when I deploy it to my staging environment and use Swagger UI to hit an endpoint, I get a NetworkError - and if I open the browser console, I see the following error:

Blocked loading mixed active content “http://MYHOST.com/foo”

(I replaced the real domain name with MYHOST for privacy, and foo is an endpoint I use for testing basic health.)

The error message also includes a link to this reference: https://developer.mozilla.org/en-US/docs/Web/Security/Mixed_content

I'm not an expert on this particular area, but I'm guessing it's because I'm using http locally, but using https in the staging environment, and when the Swagger UI is loaded via https and then tries to hit /foo using http, it triggers the Mixed Content error.

What's the fix for this?

1

There are 1 best solutions below

0
odigity On

tl;dr - I added --forwarded-allow-ips="*" to my gunicorn command line.


The relevant behavior is described in the Gunicorn source:

"If the source IP is permitted by forwarded-allow-ips (below), and at least one request header matches a key-value pair listed in this dictionary, then Gunicorn will set wsgi.url_scheme to https, so your application can tell that the request is secure."

This explains why I saw different behavior locally vs deployed with a real IP address, but it's still worth explaining how it specifically determines the server URL that ends up in the Swagger UI dropdown box.


The apiflask.APIFlask class determines what URL(s) to put in the dropdown box based on the SERVERS config setting (which is inflexible) or based on the value of request.url_root (src):

kwargs['servers'] = [{'url': request.url_root}]

The APIFlask class inherits from flask.Flask, which uses a Request class that inherits from werkzeug.wrappers.request.Request, which inherits from werkzeug.sansio.request.Request.

You can see in wrappers.request.Request that:

  1. The url_root property simply returns self.root_url. (src)
  2. The __init__ method passes the value of environ["wsgi.url_scheme"] as the scheme arg to the parent class. (src)

You can see that in sansio.request.Request, the root_url property returns the value of self.scheme, which is set in the __init__ method. (src)


That's the full chain of causality, more or less.