Skip to content

Fix Access-Control-Expose-Headers for Flask#2329

Merged
tomkralidis merged 3 commits intogeopython:masterfrom
cgs-earth:fixFlaskExposeHeaders
Apr 28, 2026
Merged

Fix Access-Control-Expose-Headers for Flask#2329
tomkralidis merged 3 commits intogeopython:masterfrom
cgs-earth:fixFlaskExposeHeaders

Conversation

@C-Loftus
Copy link
Copy Markdown
Contributor

Overview

It appears that although it was intended to be set, the header Access-Control-Expose-Headers is not being set for Flask.

If Access-Control-Expose-Headers: * is not set, then browser clients will not be able to read headers in http responses. This prevents JS clients from accessing returned urls in the OGC API Process responses since the url is in the headers.

It seems like this was intended to be set, but flask_cors isn't using the argument that is currently being supplied.

Related Issue / discussion

N/A

Additional information

If you try to fetch the headers from the demo pygeoapi site which I presume is running flask, you can see that Access-Control-Expose-Headers is missing.

curl -i "https://demo.pygeoapi.io/master"

HTTP/2 200 
access-control-allow-origin: *
content-language: en-US
content-type: application/json
date: Mon, 27 Apr 2026 22:18:21 GMT
server: gunicorn
strict-transport-security: max-age=63072000; includeSubDomains; preload
vary: Origin
x-content-type-options: nosniff
x-powered-by: pygeoapi 0.23.4
content-length: 2887

If you try to fetch a pygeoapi server like my own that includes this change, the headers for access-control-expose-headers: * are set as expected

curl -i "https://asu-awo-pygeoapi-864861257574.us-south1.run.app/"
HTTP/2 200 
content-type: application/json
x-powered-by: pygeoapi 0.24.dev0
content-language: en-US
access-control-allow-origin: *
access-control-expose-headers: *
x-cloud-trace-context: 19b0436388bc8d84c0171c7c68335524;o=1
date: Mon, 27 Apr 2026 22:17:41 GMT
server: Google Frontend
content-length: 3381
alt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000

Dependency policy (RFC2)

  • I have ensured that this PR meets RFC2 requirements

Updates to public demo

Contributions and licensing

(as per https://github.com/geopython/pygeoapi/blob/master/CONTRIBUTING.md#contributions-and-licensing)

  • I'd like to contribute [feature X|bugfix Y|docs|something else] to pygeoapi. I confirm that my contributions to pygeoapi will be compatible with the pygeoapi license guidelines at the time of contribution
  • I have already previously agreed to the pygeoapi Contributions and Licensing Guidelines

Comment thread pygeoapi/flask_app.py
try:
from flask_cors import CORS
CORS(APP, CORS_EXPOSE_HEADERS=['*'])
CORS(APP, expose_headers='*')
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I renamed this argument here since expose_headers has explicit documentation in the flask cors docs as

    :param expose_headers:
        The header or list which are safe to expose to the API of a CORS API
        specification.

        Default : None

It seems you are intended to be able to use CORS_EXPOSE_HEADERS in some way but I was not able to get this to work.

It seems like this header is special in some way and the corresponding header only gets set if expose_headers is in the kwargs, not the uppercase CORS_EXPOSE_HEADERS version of it.
https://github.com/corydolphin/flask-cors/blob/main/flask_cors/core.py#L188 I think this might be a bug in the upstream library. Either way, this is highly dynamic it is probably best to use the version of the arg that is specified as an arg and not a dynamic kwarg.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One other note, I also checked all the other defaults on CORS and all the others seem reasonable to me. So I didn't edit anything else.

Comment thread tests/api/test_api.py Outdated
Comment on lines +283 to +285
# Ensure that the expose-headers are set regardless of
# whether apirules are active or not
assert response.headers["Access-Control-Expose-Headers"] == "*"
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to move this elsewhere if preferred. Wanted to avoid spinning up a whole new test just for one header check

@tomkralidis tomkralidis added this to the 0.24.0 milestone Apr 28, 2026
@tomkralidis tomkralidis self-requested a review April 28, 2026 00:34
@tomkralidis tomkralidis merged commit 23e36b3 into geopython:master Apr 28, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants