Skip to content

Instantly share code, notes, and snippets.

@wallyhall
Last active October 28, 2025 23:32
Show Gist options
  • Save wallyhall/915fedb4dfc766b61f442a32c95e1c29 to your computer and use it in GitHub Desktop.
Save wallyhall/915fedb4dfc766b61f442a32c95e1c29 to your computer and use it in GitHub Desktop.
Apache Airflow Azure AAD SSO howto

The following instructions for enabling Azure SSO for Apache Airflow nearly take you all the way - but fall short a couple of details around the configuration of airflow itself:

https://objectpartners.com/2021/12/24/enterprise-auth-for-airflow-azure-ad

All the "Azure" instructions there can be safely followed - the resulting webserver_config.py (which can be injected into a dockerised Airflow in /opt/airflow/webserver_config.py) can be built from the following:

from __future__ import annotations

import os

from airflow.www.fab_security.manager import AUTH_OAUTH
from airflow.www.security import AirflowSecurityManager
from airflow.utils.log.logging_mixin import LoggingMixin

basedir = os.path.abspath(os.path.dirname(__file__))

# Flask-WTF flag for CSRF
WTF_CSRF_ENABLED = True
WTF_CSRF_TIME_LIMIT = None

AUTH_TYPE = AUTH_OAUTH

OAUTH_PROVIDERS = [{
    'name':'Microsoft Azure AD',
    'token_key':'access_token',
    'icon':'fa-windows',
    'remote_app': {
        'api_base_url': "https://login.microsoftonline.com/{}".format(os.getenv("AAD_TENANT_ID")),
        'request_token_url': None,
        'request_token_params': {
            'scope': 'openid email profile'
        },
        'access_token_url': "https://login.microsoftonline.com/{}/oauth2/v2.0/token".format(os.getenv("AAD_TENANT_ID")),
        "access_token_params": {
            'scope': 'openid email profile'
        },
        'authorize_url': "https://login.microsoftonline.com/{}/oauth2/v2.0/authorize".format(os.getenv("AAD_TENANT_ID")),
        "authorize_params": {
            'scope': 'openid email profile'
        },
        'client_id': os.getenv("AAD_CLIENT_ID"),
        'client_secret': os.getenv("AAD_CLIENT_SECRET"),
        'jwks_uri': 'https://login.microsoftonline.com/common/discovery/v2.0/keys'
    }
}]

AUTH_USER_REGISTRATION_ROLE = "Public"
AUTH_USER_REGISTRATION = True
AUTH_ROLES_SYNC_AT_LOGIN = True
AUTH_ROLES_MAPPING = {
    "airflow_prod_admin": ["Admin"],
    "airflow_prod_user": ["Op"],
    "airflow_prod_viewer": ["Viewer"]
}

class AzureCustomSecurity(AirflowSecurityManager, LoggingMixin):
    def get_oauth_user_info(self, provider, response=None):
        me = self._azure_jwt_token_parse(response["id_token"])
        return {
            "name": me["name"],
            "email": me["email"],
            "first_name": me["given_name"],
            "last_name": me["family_name"],
            "id": me["oid"],
            "username": me["preferred_username"],
            "role_keys": me["roles"]
        }

# the first of these two appears to work with older Airflow versions, the latter newer.
FAB_SECURITY_MANAGER_CLASS = 'webserver_config.AzureCustomSecurity'
SECURITY_MANAGER_CLASS = AzureCustomSecurity

The above assumes environment variables are configured for the OAuth client secret, etc - and has been tested thoroughly and confirmed working.

Note the roles need to match what you configured in Azure (the example above is using airflow_prod_user etc, in deviation to the linked article above).

@EbrahimKaram
Copy link

EbrahimKaram commented Oct 6, 2025

Make sure you have
apache-airflow-providers-fab==2.4.4 installed

This worked with Apache airflow 3.1.0
This is what I got to work on my end: this needs to be on webserver_config.py (that would be at the root location of apache home)


from airflow.providers.fab.auth_manager.security_manager.override import FabAirflowSecurityManagerOverride
from flask_appbuilder.security.manager import AUTH_OAUTH

# Use OAuth
AUTH_TYPE = AUTH_OAUTH

# OAuth provider settings
CLIENT_ID = 
TENANT_ID = 

## References
# https://airflow.apache.org/docs/apache-airflow/stable/core-concepts/auth-manager/index.html?utm_source=chatgpt.com
# https://www.nextlytics.com/blog/implementing-single-sign-on-sso-authentication-in-apache-airflow

# Resources: https://github.com/Azure-Samples/ms-identity-python-webapp/tree/main
# https://airflow.apache.org/docs/apache-airflow-providers-fab/stable/auth-manager/webserver-authentication.html (the most useful and direct)

# Gist Github
# https://gist.github.com/wallyhall/915fedb4dfc766b61f442a32c95e1c29


# what you Replace on the config for this work
# Auth manager
# auth_manager = airflow.api_fastapi.auth.managers.simple.simple_auth_manager.SimpleAuthManager

# airflow.providers.fab.auth_manager.fab_auth_manager.FabAuthManager


APP_CLIENT_SECRET=CLIENT_SECRET_VALUE = 

CLIENT_SECRET_ID = 

# Redirect URI: https:/AIRFLOWDOMAIN/auth/oauth-authorized/azure
OAUTH_PROVIDERS = [
    {
        'name': 'azure',
        'icon': 'fa-windows',
        'token_key': 'access_token',
        'remote_app': {
            'client_id': CLIENT_ID,
            'client_secret': CLIENT_SECRET_VALUE,
            'api_base_url': 'https://graph.microsoft.com/v1.0/',
            'client_kwargs': {
                'scope': 'User.Read openid email profile',
            },
            "request_token_url": None,
            'authorize_url': f'https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/authorize',
            'access_token_url': f'https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/token',
            'jwks_uri': 'https://login.microsoftonline.com/common/discovery/v2.0/keys'
        },
    }
]


AUTH_USER_REGISTRATION_ROLE = "Public"
AUTH_USER_REGISTRATION = True
AUTH_ROLES_SYNC_AT_LOGIN = True

# Optional: map Microsoft users to FAB roles
# These are the Roles you can have
# https://airflow.apache.org/docs/apache-airflow/3.1.0/security/security_model.html#regular-users
# https://airflow.apache.org/docs/apache-airflow-providers-fab/stable/auth-manager/access-control.html
# Permisions:  Admin, Op, User, Viewer,  Public  (most permissive to least permissive)

AUTH_ROLES_MAPPING = {

    "airflow_prod_admin": ["Admin"],
    "airflow_prod_user": ["Op"],
    "airflow_prod_viewer": ["Viewer"],
    "Admin": ["Admin"],
    "Op": ["Op"],
    "User": ["User"],
    "Viewer": ["Viewer"],
    "Public": ["Public"]

}


class AzureCustomSecurity(FabAirflowSecurityManagerOverride):
    def get_oauth_user_info(self, provider, response=None):
        try:
            me = super().get_oauth_user_info(provider, response)
            self.log.debug("User info from Azure: %s", me)
            print("User info from Azure: %s", me)
        except Exception as e:
            import traceback
            traceback.print_exc()
            self.log.debug(e)
            self.log.debug("The super call failed, trying to continue...")

        return {
            "name": me["first_name"] + " " + me["last_name"],
            "email": me["email"],
            "first_name": me["first_name"],
            "Last_name": me["last_name"],
            "id": me["username"],
            "username": me["email"],
            # "role_keys": me.get("email", ["Viewer"]).split("@")[0].split(".")  # Example: derive roles from email prefix

            "role_keys":["Public"]  
        }


# the first of these two appears to work with older Airflow versions, the latter newer.
FAB_SECURITY_MANAGER_CLASS = 'webserver_config.AzureCustomSecurity'
SECURITY_MANAGER_CLASS = AzureCustomSecurity


@Yatharth0045
Copy link

Yatharth0045 commented Oct 9, 2025

I've used the above configuration, and literally every version shown in this gist but none of them worked. Everytime, it stuck on http error which says this

The redirect URI 'http://<airflow-domain>/auth/oauth-authorized/azure' specified in the request does not match the redirect URIs configured for the application 'XXXXXXXXX'. Make sure the redirect URI sent in the request matches one added to your application in the Azure portal. Navigate to https://aka.ms/redirectUriMismatchError to learn more about how to fix this.

Here, it says that the redirect URI is http but I don't know why because I've configured airflow with https and I'm even accessing the UI with https://<airflow-domain> and its working.

Sharing some of the relevant configuration I already have in my values.yaml for reference

images:
  airflow:
    repository: apache/airflow
    tag: "3.1.0"

## FYI, we are using traefik v2.6 as ingress controller

ingress:
  apiServer:
    enabled: true
    annotations: {}
    path: "/"
    pathType: "ImplementationSpecific"
    hosts: 
    - name: "<airflow-domain>"
      tls:
        enabled: true
        secretName: ""
    ingressClassName: "traefik"

executor: "KubernetesExecutor"

extraEnv: |
  - name: AIRFLOW__WEBSERVER__ENABLE_PROXY_FIX
    value: 'True'

config:
  core:
    auth_manager: "airflow.providers.fab.auth_manager.fab_auth_manager.FabAuthManager"
  logging:
    fab_logging_level: 'DEBUG'
  fab:
    enable_proxy_fix: 'True'

Also, adding my traefik ingress yaml for reference

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    meta.helm.sh/release-name: airflow-v3-poc
    meta.helm.sh/release-namespace: airflow-v3-poc
  creationTimestamp: "2025-10-09T02:43:40Z"
  generation: 2
  labels:
    app.kubernetes.io/managed-by: Helm
    chart: airflow-1.18.0
    component: airflow-ingress
    heritage: Helm
    release: airflow-v3-poc
    tier: airflow
  name: airflow-v3-poc-ingress
  namespace: airflow-v3-poc
  resourceVersion: "337356144"
  uid: 627dd685-1ae6-4a84-89db-f1633005bada
spec:
  ingressClassName: traefik
  rules:
  - host: <airflow-domain>
    http:
      paths:
      - backend:
          service:
            name: airflow-v3-poc-api-server
            port:
              name: api-server
        path: /
        pathType: ImplementationSpecific
  tls:
  - hosts:
    - <airflow-domain>

Not sure If I'm missing something here.

@pawgajda-drs
Copy link

pawgajda-drs commented Oct 9, 2025

I've used the above configuration, and literally every version shown in this gist but none of them worked. Everytime, it stuck on http error which says this

The redirect URI 'http://<airflow-domain>/auth/oauth-authorized/azure' specified in the request does not match the redirect URIs configured for the application 'XXXXXXXXX'. Make sure the redirect URI sent in the request matches one added to your application in the Azure portal. Navigate to https://aka.ms/redirectUriMismatchError to learn more about how to fix this.

Here, it says that the redirect URI is http but I don't know why because I've configured airflow with https and I'm even accessing the UI with https://<airflow-domain> and its working.

Sharing some of the relevant configuration I already have in my values.yaml for reference

images:
  airflow:
    repository: apache/airflow
    tag: "3.1.0"

## FYI, we are using traefik v2.6 as ingress controller

ingress:
  apiServer:
    enabled: true
    annotations: {}
    path: "/"
    pathType: "ImplementationSpecific"
    hosts: 
    - name: "<airflow-domain>"
      tls:
        enabled: true
        secretName: ""
    ingressClassName: "traefik"

executor: "KubernetesExecutor"

extraEnv: |
  - name: AIRFLOW__WEBSERVER__ENABLE_PROXY_FIX
    value: 'True'

config:
  core:
    auth_manager: "airflow.providers.fab.auth_manager.fab_auth_manager.FabAuthManager"
  logging:
    fab_logging_level: 'DEBUG'
  fab:
    enable_proxy_fix: 'True'

Also, adding my traefik ingress yaml for reference

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    meta.helm.sh/release-name: airflow-v3-poc
    meta.helm.sh/release-namespace: airflow-v3-poc
  creationTimestamp: "2025-10-09T02:43:40Z"
  generation: 2
  labels:
    app.kubernetes.io/managed-by: Helm
    chart: airflow-1.18.0
    component: airflow-ingress
    heritage: Helm
    release: airflow-v3-poc
    tier: airflow
  name: airflow-v3-poc-ingress
  namespace: airflow-v3-poc
  resourceVersion: "337356144"
  uid: 627dd685-1ae6-4a84-89db-f1633005bada
spec:
  ingressClassName: traefik
  rules:
  - host: <airflow-domain>
    http:
      paths:
      - backend:
          service:
            name: airflow-v3-poc-api-server
            port:
              name: api-server
        path: /
        pathType: ImplementationSpecific
  tls:
  - hosts:
    - <airflow-domain>

Not sure If I'm missing something here.

Wrong environment variable, try:

env:
  # Fixes Oauth Issues
  - name: "AIRFLOW__FAB__ENABLE_PROXY_FIX"
    value: "True"

Worked for me with airflow/airflow versions: 3.0.2 and 3.0.6.

EDIT:

Yes, I see you have it set in config but the question is if it works as intended. I personally haven't used config keyword in values.yaml files at all. Everything is in webserver-configmap and then set as Environment Variables.

@Yatharth0045
Copy link

Yatharth0045 commented Oct 9, 2025

Wrong environment variable, try:

env:
  # Fixes Oauth Issues
  - name: "AIRFLOW__FAB__ENABLE_PROXY_FIX"
    value: "True"

Worked for me with airflow/airflow versions: 3.0.2 and 3.0.6.

EDIT:

Yes, I see you have it set in config but the question is if it works as intended. I personally haven't used config keyword in values.yaml files at all. Everything is in webserver-configmap and then set as Environment Variables.

Tried but didn't worked.
Updated env to AIRFLOW__FAB__ENABLE_PROXY_FIX=True
@pawgajda-drs

@pawgajda-drs
Copy link

pawgajda-drs commented Oct 9, 2025

Wrong environment variable, try:

env:
  # Fixes Oauth Issues
  - name: "AIRFLOW__FAB__ENABLE_PROXY_FIX"
    value: "True"

Worked for me with airflow/airflow versions: 3.0.2 and 3.0.6.
EDIT:
Yes, I see you have it set in config but the question is if it works as intended. I personally haven't used config keyword in values.yaml files at all. Everything is in webserver-configmap and then set as Environment Variables.

Tried but didn't worked. Updated env to AIRFLOW__FAB__ENABLE_PROXY_FIX=True @pawgajda-drs

Does your ingress redirect to HTTPS automatically?
Your config on Airflow side seems correct then, given that your Configmap does not have any errors in it.

You can also try this to make sure your Configmap is loaded correctly:

# apiServer
apiServer:
  apiServerConfigConfigMapName: airflow-webserver-config

# Webserver
webserver:
  # this is also needed for apiServer (that replaces Webserver in Airflow 3.x)
  # airflow_api_server_config_configmap_name refers to webserverConfigConfigMapName
  # https://github.com/apache/airflow/blob/main/chart/templates/_helpers.yaml#L607
  webserverConfigConfigMapName: airflow-webserver-config

on the newest stable version of Helm Chart I had issues with loading my config file so after reading the source code I came up with that workaround.

@Yatharth0045
Copy link

Yatharth0045 commented Oct 9, 2025

Does your ingress redirect to HTTPS automatically? Your config on Airflow side seems correct then, given that your Configmap does not have any errors in it.

You can also try this to make sure your Configmap is loaded correctly:

# apiServer
apiServer:
  apiServerConfigConfigMapName: airflow-webserver-config

# Webserver
webserver:
  # this is also needed for apiServer (that replaces Webserver in Airflow 3.x)
  # airflow_api_server_config_configmap_name refers to webserverConfigConfigMapName
  # https://github.com/apache/airflow/blob/main/chart/templates/_helpers.yaml#L607
  webserverConfigConfigMapName: airflow-webserver-config

on the newest stable version of Helm Chart I had issues with loading my config file so after reading the source code I came up with that workaround.

Configmap would be loading as I was able to atleast redirect to azure-sso, and my changes were taking effect, I can say that because I use to change the SSO icon, and that use to work. Also, I'm passing inline config for it.

apiServerConfig: |
    ...
    AUTH_TYPE = AUTH_OAUTH
    ...

I believe it might be an issue with my traefik ingress controller.

It would be great if someone can help me with ingress part.

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