Skip to content

Make JWTRefreshMiddleware extensible via BaseAuthManager.get_jwt_refresh_middleware() - #70783

Merged
pierrejeambrun merged 1 commit into
apache:mainfrom
stephen-bracken:make-jwt-extendible
Oct 22, 2026
Merged

pierrejeambrun merged 1 commit into
apache:mainfrom
stephen-bracken:make-jwt-extendible

Conversation

@stephen-bracken

@stephen-bracken stephen-bracken commented Jul 30, 2026 •

Copy link
Copy Markdown
Contributor

closes: #70720

Make JWTRefreshMiddleware extensible via BaseAuthManager.get_jwt_refresh_middleware()

Refactor the initialisation flow for the JWTRefreshMiddleware to give greater control to auth managers over how this middleware gets initialised.

By moving the JWTRefreshMiddleware initialisation to BaseAuthManager.get_jwt_refresh_middleware(), this allows auth managers to override the JWTRefreshMiddleware behaviour by inheriting from it and using the inherited class in get_fastapi_middlewares instead.

  • Changed the _refresh_user() and _build_cookie_headers() methods to public refresh_user() and build_cookie_headers() methods to allow accessing any cookies or state information from the request and setting cookies in the response.
  • get_extra_cookies() allows subclasses to add / clear additional cookies in the response when a new user token is issued.
  • generate_new_user_token() allows subclasses to control how JWTs are issued for new users.

Also adds the airflow.api_fastapi.app.request_cookie_is_secure() helper to standardise setting HTTP secure cookies

Was generative AI tooling used to co-author this PR?
  • Yes (please specify the tool below)
  • No

@stephen-bracken stephen-bracken changed the title Make JWTRefreshMiddleware into a standard fastapi middleware Make JWTRefreshMiddleware extensible via BaseAuthManager.get_jwt_refresh_middleware()` Jul 30, 2026
@stephen-bracken stephen-bracken changed the title Make JWTRefreshMiddleware extensible via BaseAuthManager.get_jwt_refresh_middleware()` Make JWTRefreshMiddleware extensible via BaseAuthManager.get_jwt_refresh_middleware() Jul 30, 2026
@stephen-bracken
stephen-bracken force-pushed the make-jwt-extendible branch 3 times, most recently from 474ce7b to 7655032 Compare July 30, 2026 22:56
@stephen-bracken
stephen-bracken marked this pull request as ready for review July 30, 2026 23:38
Comment thread airflow-core/src/airflow/api_fastapi/auth/middlewares/refresh_token.py Outdated
Comment thread airflow-core/src/airflow/api_fastapi/core_api/app.py Outdated
@stephen-bracken
stephen-bracken force-pushed the make-jwt-extendible branch 3 times, most recently from 16198fd to 3a89840 Compare July 31, 2026 19:11
@vincbeck

Copy link
Copy Markdown
Contributor

CI is failing

@stephen-bracken
stephen-bracken force-pushed the make-jwt-extendible branch 6 times, most recently from a8c2ae1 to 3dc4d12 Compare August 1, 2026 16:29
@stephen-bracken
stephen-bracken force-pushed the make-jwt-extendible branch 4 times, most recently from d0d9932 to 3702205 Compare September 15, 2026 13:47
@stephen-bracken

stephen-bracken commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor Author

@pierrejeambrun

I believe this is breaking the because core no longer hardcode JWTRefreshMiddleware, but old provider doesn't call super().get_fastapi_middlewares().

I have added BaseAuthManager.use_jwt_middleware (default True) that will trigger the old refresh behaviour where necessary. This allows auth managers to control the order in which the middleware from get_jwt_refresh_middleware() gets initialised without breaking old auth flows.

@stephen-bracken
stephen-bracken force-pushed the make-jwt-extendible branch 4 times, most recently from 25b289d to 8dbd6d9 Compare September 15, 2026 16:20
@stephen-bracken stephen-bracken changed the title Make JWTRefreshMiddleware extensible via BaseAuthManager._get_jwt_refresh_middleware() Make JWTRefreshMiddleware extensible via BaseAuthManager.get_jwt_refresh_middleware() Sep 16, 2026
@vatsrahul1001 vatsrahul1001 removed this from the Airflow 3.4.0 milestone Oct 18, 2026
@vincbeck

Copy link
Copy Markdown
Contributor

I am trying to understand, why it is breaking? Do we really need BaseAuthManager.use_jwt_middleware?

@stephen-bracken

Copy link
Copy Markdown
Contributor Author

@vincbeck The original implementation of this middleware made an implicit assumption that the Airflow JWT can fully reconstruct the user model for the request. The JWTRefreshMiddleware gets initialised as the first middleware in the FastAPI app and this behaviour is not modifiable by the auth manager. See initialisation here:

app.add_middleware(JWTRefreshMiddleware, skip_path_prefixes=(f"{_STATIC_URL_PATH}/",))

That assumption is broken for auth managers that use additional cookies, e.g. in #70800 I added KeycloakJWTMiddleware to the KeycloakAuthManager that manages a separate JWT for Keycloak in a separate cookie. There are good reasons to do this like reducing the size of the Airflow JWT claims, but the repercussion is that the Airflow JWT no longer contains a full user model, as the Keycloak JWTs have to be injected from separate cookies.
There may be other scenarios where auth managers want to control how and where the JWT gets refreshed, so adding a use_jwt_middleware property to the BaseAuthManager allows other auth managers to disable the default behaviour which adds the JWTRefreshMiddleware to the app as the first middleware, and take full control of the user request validation process. The intent of this PR is to allow the KeycloakAuthManager to override the base JWTRefreshMiddleware with the KeycloakJWTMiddleware or similar so that we don't have to break the auth manager interface as badly in KeycloakAuthManager.

Adding the use_jwt_middleware property to the BaseAuthManager with default value True preserves the existing JWT behaviour for any auth managers that do not need this override.

@stephen-bracken

Copy link
Copy Markdown
Contributor Author

The getattr guard is what preserves the behaviour for backwards compatibility:

https://github.com/apache/airflow/pull/70783/changes#diff-da26ba2c49d5bc5f117895c11ad7fea6e24615eccd3d27c3300a5026195b5cd8R310

@vincbeck

Copy link
Copy Markdown
Contributor

But do we really need this use_jwt_middleware? If an auth manager do not want to use JWTRefreshMiddleware, it can just override get_jwt_refresh_middleware and return an empty list?

@stephen-bracken

Copy link
Copy Markdown
Contributor Author

I see your point - I can modify the init procedure to prepend the jwt middleware if present.

@vincbeck vincbeck left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM but I would like to have @pierrejeambrun's review

@pierrejeambrun pierrejeambrun left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for iterating on this, I think the current shape is much better: keeping the JWT middleware in its own overridable method keeps old providers working on the new core, and preserves the middleware order. A few things to fix before it's ready, the first one is breaking the API server (and the standalone CI job). Could you also update the PR description? It still mentions use_jwt_middleware and _set_new_token.

Comment thread airflow-core/src/airflow/api_fastapi/auth/managers/base_auth_manager.py Outdated
Comment thread airflow-core/tests/unit/api_fastapi/core_api/test_app.py Outdated
Comment thread airflow-core/src/airflow/api_fastapi/core_api/app.py Outdated
Comment thread airflow-core/src/airflow/api_fastapi/auth/middlewares/refresh_token.py Outdated
@stephen-bracken

stephen-bracken commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor Author

I've fixed the CI issues and added a get_extra_cookies() method that auth manager middlewares can subclass to set / clear extra cookies in new token responses (e.g. Keycloak tokens)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make request cookies available to AuthManager methods

5 participants