Authentication¶
Airflow 3 uses a pluggable authentication manager. The examples in this section use the
Flask App Builder (FAB) auth manager,
which is provided by the apache-airflow-providers-fab package.
To use FAB authentication in Airflow 3, install apache-airflow-providers-fab in the Airflow environment and set
AIRFLOW__CORE__AUTH_MANAGER to
airflow.providers.fab.auth_manager.fab_auth_manager.FabAuthManager.
The FAB auth manager supports the following authentication methods (however only one method at any time can be used):
Database (default)
The default FAB authentication method is database authentication, which simply keeps user credentials in your database in hashed form. Users can use the Airflow UI to change their passwords without going through an external service.
We have provided sample configuration for each external authentication method in this section. When using the FAB auth
manager, external authentication configuration (like the examples given in below sections) goes into a file called
webserver_config.py stored in your $AIRFLOW_HOME directory. More details on each authentication method can be
found in
FAB’s own documentation on security
For each external authentication method, make sure to disable or update any deployment step that creates a default
database-authenticated admin user, such as a Helm createUserJob or Docker Compose bootstrap command.
Database¶
This is the default authentication method. If you do not require some fine tuning or non-default features, it works out of the box and no extra configuration is necessary.
LDAP¶
from flask_appbuilder.security.manager import AUTH_LDAP
AUTH_TYPE = AUTH_LDAP
AUTH_LDAP_SERVER = "ldaps://ldap.example.com"
# For STARTTLS, use an ldap:// URL and set AUTH_LDAP_USE_TLS = True instead.
AUTH_LDAP_USE_TLS = False
AUTH_LDAP_SEARCH = "ou=users,dc=example,dc=com"
AUTH_LDAP_UID_FIELD = "uid"
AUTH_LDAP_BIND_USER = "cn=admin,dc=example,dc=com"
AUTH_LDAP_BIND_PASSWORD = "admin-password"
AUTH_USER_REGISTRATION = True
AUTH_USER_REGISTRATION_ROLE = "Public"
AUTH_LDAP_FIRSTNAME_FIELD = "givenName"
AUTH_LDAP_LASTNAME_FIELD = "sn"
AUTH_LDAP_EMAIL_FIELD = "mail"
AUTH_ROLES_MAPPING = {
"cn=airflow-admins,ou=Groups,dc=example,dc=com": ["Admin"],
"cn=airflow-users,ou=Groups,dc=example,dc=com": ["User"],
}
AUTH_LDAP_GROUP_FIELD = "memberOf"
AUTH_ROLES_SYNC_AT_LOGIN = True
LDAP service should separately be configured with the memberOf overlay if role mapping depends on the
memberOf attribute (see “Reverse Group Membership Maintenance” section in
overlays documentation).
OAuth and OpenID Connect¶
OAuth can support multiple providers at a time. Modern OpenID Connect identity providers, such as Keycloak, Okta, Auth0, and Azure AD, should also be configured as OAuth providers. A full list of example configs for OAuth can be found in the FAB documentation and the Airflow FAB auth manager documentation.
For OpenID Connect providers, make sure Airflow and users’ browsers use the same issuer URL for the identity provider. For example, do not configure Airflow to retrieve metadata from an internal container hostname while browser redirects use a public hostname. If the issuer in the returned tokens does not match the issuer from the provider metadata, the login callback will be rejected.
The OAuth client in the identity provider must also allow the Airflow callback URL as a redirect URI. This is normally
https://<airflow-host>/auth/oauth-authorized/<provider-name>, where <provider-name> matches the name field
in OAUTH_PROVIDERS. In Airflow deployments that serve the UI through a proxy or load balancer, set
AIRFLOW__API__BASE_URL to the browser-facing Airflow URL so redirects are generated with the externally reachable
hostname.
from flask_appbuilder.security.manager import AUTH_OAUTH
AUTH_TYPE = AUTH_OAUTH
AUTH_USER_REGISTRATION = True
AUTH_USER_REGISTRATION_ROLE = "User"
AUTH_ROLES_SYNC_AT_LOGIN = True
OAUTH_PROVIDERS = [
{
"name": "keycloak",
"icon": "fa-key",
"token_key": "access_token",
"remote_app": {
"client_id": "airflow",
"client_secret": "client-secret",
"server_metadata_url": (
"https://keycloak.example.com/realms/airflow/.well-known/openid-configuration"
),
"api_base_url": "https://keycloak.example.com/realms/airflow/protocol/openid-connect",
"client_kwargs": {"scope": "openid email profile"},
"access_token_url": "https://keycloak.example.com/realms/airflow/protocol/openid-connect/token",
"authorize_url": "https://keycloak.example.com/realms/airflow/protocol/openid-connect/auth",
"request_token_url": None,
},
},
]
OpenID 2.0 authentication with AUTH_OID is not supported by current Flask App Builder versions. If your identity
provider supports OpenID Connect, configure it through AUTH_OAUTH as shown above.
Remote User Authentication¶
Airflow’s remote user authentication takes the username of the user and creates a local FAB account
with the role that you specify in the AUTH_USER_REGISTRATION_ROLE setting. Additionally, for remote user
authentication to work you need to use a custom middleware. This is because the AUTH_REMOTE_USER setting
looks for the username value in the header REMOTE_USER, however if the user makes a request
to the Airflow API server with the header REMOTE_USER, this will be converted to HTTP_REMOTE_USER in the
request environ dictionary. Thus, you need a CustomMiddleware like in the below example, which will
copy the value of HTTP_REMOTE_USER (or any other header you want) to REMOTE_USER to make remote user
authentication work.
Only use remote user authentication behind a trusted reverse proxy or SSO gateway that authenticates users, sets the
trusted user header, and strips any incoming REMOTE_USER or HTTP_REMOTE_USER headers from client requests.
Without that boundary, a client could spoof the header and impersonate another user.
from typing import Any, Callable
from flask import current_app
from flask_appbuilder.const import AUTH_REMOTE_USER
class CustomMiddleware:
def __init__(self, wsgi_app: Callable) -> None:
self.wsgi_app = wsgi_app
def __call__(self, environ: dict, start_response: Callable) -> Any:
remote_user = environ.get("HTTP_REMOTE_USER")
if remote_user:
environ["REMOTE_USER"] = remote_user
return self.wsgi_app(environ, start_response)
current_app.wsgi_app = CustomMiddleware(current_app.wsgi_app)
AUTH_TYPE = AUTH_REMOTE_USER
AUTH_USER_REGISTRATION = True
AUTH_USER_REGISTRATION_ROLE = "User"