anjo0511 opened a new issue, #57981: URL: https://github.com/apache/airflow/issues/57981
### Apache Airflow version 3.1.2 ### If "Other Airflow 2/3 version" selected, which one? 3.1.2, 3.1.1 ### What happened? When login in with Azure Oauth SSO the initial authentication delay causes an error message to pop up on screen, when the authentication is successful 1 second after the error message dissapears. <img width="578" height="543" alt="Image" src="https://github.com/user-attachments/assets/6b9baf96-0ee6-4e64-8b1b-0e2ecff7e383" /> <img width="689" height="570" alt="Image" src="https://github.com/user-attachments/assets/46a77b0b-36a3-427d-ae1f-219d2a3f3da0" /> Logs: `api-server INFO: 10.9.155.17:60082 - "GET /api/v2/version HTTP/1.1" 200 OK api-server INFO: 10.9.155.17:34212 - "GET /api/v2/version HTTP/1.1" 200 OK api-server INFO: 10.9.155.17:34218 - "GET /api/v2/version HTTP/1.1" 200 OK api-server INFO: 10.128.1.206:43444 - "GET /api/v2/auth/login?next=***" 307 Temporary Redirect api-server Provider: None api-server DEBUG: Provider: None [views.py:633] api-server INFO: 10.128.1.206:43444 - "GET /auth/login/?next=***" 200 OK api-server INFO: 10.128.1.206:43444 - "GET /auth/static/appbuilder/css/fontawesome/fontawesome.min.css HTTP/1.1" 304 Not Modified api-server INFO: 10.128.1.206:43444 - "GET /auth/static/appbuilder/css/fontawesome/solid.min.css HTTP/1.1" 304 Not Modified ... api-server INFO: 10.128.1.206:43548 - "GET /auth/static/dist/flash.5583a9e0cf11f2be93da.css HTTP/1.1" 304 Not Modified api-server INFO: 10.128.1.206:43492 - "GET /auth/static/dist/main.3cf3be1a0c5439bb640d.js HTTP/1.1" 304 Not Modified ... api-server Provider: azure api-server DEBUG: Provider: azure [views.py:633] api-server DEBUG: Going to call authorize for: azure [views.py:646] api-server INFO: 10.128.1.206:43578 - "GET /auth/login/azure?next=***" 302 Found api-server INFO: 10.9.155.17:44558 - "GET /api/v2/version HTTP/1.1" 200 OK ... api-server DEBUG: Authorized init [views.py:676] api-server DEBUG: OAUTH Authorized resp: { 'token_type': 'Bearer', 'scope': 'email openid profile User.Read', 'access_token': '***', 'id_token': '***', 'userinfo': { 'email': '[REDACTED]', 'name': '[REDACTED]', ... } } [views.py:690] api-server DEBUG: Parsing JWT token for provider: azure [webserver_config.py:105] api-server DEBUG: User info from Azure: { 'email': '[REDACTED]', 'name': '[REDACTED]', 'roles': ['role1', 'role2', ...] } [override.py:2112] api-server DEBUG: _____________________role_keys start__________________________________ [webserver_config.py:115] api-server DEBUG: Parsed JWT token: { 'email': '[REDACTED]', 'username': '[REDACTED]', 'role_keys': [...] } [webserver_config.py:116] api-server DEBUG: Role Keys: [...] [webserver_config.py:117] api-server DEBUG: _____________________role_keys end____________________________________ [webserver_config.py:118] api-server DEBUG: User info retrieved from azure: { 'name': '[REDACTED]', 'email': '[REDACTED]', 'role_keys': [...] } [views.py:699] api-server DEBUG: No whitelist for OAuth provider [views.py:712] api-server DEBUG: Calculated new roles for user='[REDACTED]' as: [Public, Admin] [override.py:2043] api-server INFO: Updated user [REDACTED] [override.py:1449] api-server INFO: 10.128.1.1:38716 - "GET /auth/oauth-authorized/azure?code=***&state=***&session_state=*** HTTP/1.1" 302 Found api-server INFO: 10.128.1.1:38716 - "GET /dags/test_remote_logging/runs/manual__*** HTTP/1.1" 200 OK api-server INFO: 10.9.155.17:59236 - "GET /api/v2/version HTTP/1.1" 200 OK ... api-server INFO: 10.128.1.1:38716 - "GET /ui/config HTTP/1.1" 401 Unauthorized api-server INFO: 10.128.1.1:38716 - "GET /api/v2/auth/login?next=***" 307 Temporary Redirect api-server DEBUG: Provider: None [views.py:633] api-server DEBUG: Already authenticated [REDACTED] [views.py:635] api-server INFO: 10.128.1.1:38716 - "GET /auth/login/?next=*** HTTP/1.1" 302 Found api-server INFO: 10.128.1.1:38716 - "GET /auth/ HTTP/1.1" 302 Found api-server INFO: 10.128.1.1:38716 - "GET / HTTP/1.1" 200 OK api-server INFO: 10.128.1.1:38716 - "GET /ui/config HTTP/1.1" 200 OK api-server INFO: 10.128.1.1:38716 - "GET /static/i18n/locales/en/dag.json HTTP/1.1" 304 Not Modified api-server INFO: 10.128.1.1:38716 - "GET /ui/auth/menus HTTP/1.1" 200 OK ... api-server INFO: 10.128.1.1:50736 - "GET /ui/dashboard/historical_metrics_data?start_date=*** HTTP/1.1" 200 OK api-server INFO: 10.128.1.1:50740 - "GET /api/v2/assets/events?limit=6&order_by=-timestamp×tamp_gte=***×tamp_lte=*** HTTP/1.1" 200 OK api-server INFO: 10.128.1.1:50668 - "GET /api/v2/dags/~/dagRuns/~/hitlDetails?state=deferred&response_received=false HTTP/1.1" 200 OK api-server INFO: 10.128.1.1:50666 - "GET /api/v2/dags/~/dagRuns?state=running&state=queued HTTP/1.1" 200 OK api-server INFO: 10.128.1.1:50700 - "GET /ui/dags?limit=10&is_favorite=true HTTP/1.1" 200 OK ... ` ### What you think should happen instead? The ui should not render the error message and should wait for the login outcome. ### How to reproduce Setup Airflow 3.1.2 on python3.12 ### Operating System Debian GNU/Linux 12 (bookworm) ### Versions of Apache Airflow Providers _No response_ ### Deployment Official Apache Airflow Helm Chart ### Deployment details Deploy using official helm chart to k8s on azure (aks) ### Anything else? This occurs each time a login happens, This is the apiserver_config.py used for the oauth towards azure, worked on airlfow 3.0.6 without issues. `# ## https://gist.github.com/wallyhall/915fedb4dfc766b61f442a32c95e1c29#file-apache_airflow_sso_howto-md ## https://gist.github.com/wallyhall/915fedb4dfc766b61f442a32c95e1c29?permalink_comment_id=5253211#gistcomment-5253211 # from __future__ import annotations import os # Airflow 3.1.x and FAB provider >=2.6.0 #! https://gist.github.com/wallyhall/915fedb4dfc766b61f442a32c95e1c29?permalink_comment_id=5623661#gistcomment-5623661 from airflow.providers.fab.auth_manager.security_manager.override import FabAirflowSecurityManagerOverride from airflow.utils.log.logging_mixin import LoggingMixin from flask_appbuilder.security.manager import AUTH_OAUTH basedir = os.path.abspath(os.path.dirname(__file__)) print("------------------------------------STARTING AUTH------------------------------------") # ------------------------------------------------------------------------------ # General Flask AppBuilder / Airflow Auth Config # ------------------------------------------------------------------------------ WTF_CSRF_ENABLED = True WTF_CSRF_TIME_LIMIT = None # ------------------------------------------------------------------------------ # Azure AD OAuth Settings # ------------------------------------------------------------------------------ AAD_TENANT_ID = os.getenv("AAD_TENANT_ID") AAD_CLIENT_ID = os.getenv("AAD_CLIENT_ID") AAD_CLIENT_SECRET = os.getenv("AAD_CLIENT_SECRET") AUTH_TYPE = AUTH_OAUTH OAUTH_PROVIDERS = [ { "name": "azure", "token_key": "access_token", "icon": "fa-windows", "remote_app": { "api_base_url": f"https://login.microsoftonline.com/{AAD_TENANT_ID}", "request_token_url": None, "request_token_params": {"scope": "openid profile email"}, "access_token_url": f"https://login.microsoftonline.com/{AAD_TENANT_ID}/oauth2/v2.0/token", "authorize_url": f"https://login.microsoftonline.com/{AAD_TENANT_ID}/oauth2/v2.0/authorize", "client_id": f"{AAD_CLIENT_ID}", "client_secret": f"{AAD_CLIENT_SECRET}", "client_kwargs": {"scope": "openid profile email"}, "jwks_uri": "https://login.microsoftonline.com/common/discovery/v2.0/keys", }, } ] # ------------------------------------------------------------------------------ # User registration and role sync # ------------------------------------------------------------------------------ AUTH_USER_REGISTRATION = True AUTH_USER_REGISTRATION_ROLE = "Public" AUTH_ROLES_SYNC_AT_LOGIN = True # This gets injected via Helm values usually, First you MUST create a role like"Admin with value Admin" in the AppRegistration "App Roles" section in the Azure Portal under Microsoft EntraID. # Then groups MUST be linked from the Microsoft Entra ID "EnterpriseApplication" section in the Azure Portal under the "Users and Groups" section. # Each groups or users MUST be assigned a role e.g.: Admin, Op, Viewer in the "Users and Groups" # https://airflow.apache.org/docs/apache-airflow/2.4.3/security/access-control.html # AUTH_ROLES_MAPPING = { # "airflow-admin": ["Admin"], # "airflow-user": ["User"], # } PLACEHOLDER_FOR_AUTH_ROLES_MAPPING # ------------------------------------------------------------------------------ # Custom Security Manager # ------------------------------------------------------------------------------ class AzureCustomSecurity(FabAirflowSecurityManagerOverride, LoggingMixin): """ Custom FAB security manager for Azure AD OAuth. Compatible with Airflow 3.1.x (API server). """ def oauth_user_info_save(self, userinfo): """ Override Airflow 3.1+ user creation to reuse existing users by email. """ email = userinfo.get("email") if not email: self.log.warning("OAuth userinfo missing email — cannot authenticate user.") return None existing_user = self.find_user(email=email) if existing_user: self.log.debug(f"User {email} already exists, reusing record instead of creating new one.") return existing_user self.log.debug(f"No existing user found for {email}, creating new user.") return super().oauth_user_info_save(userinfo) def get_oauth_user_info(self, provider, response=None): self.log.debug(f"Parsing JWT token for provider: {provider}") try: me = super().get_oauth_user_info(provider, response) except Exception as e: import traceback traceback.print_exc() self.log.debug(e) return None self.log.debug("_____________________role_keys start__________________________________") self.log.debug(f"Parsed JWT token: {me}") self.log.debug(f"Role Keys: {me.get('role_keys')}") self.log.debug("_____________________role_keys end____________________________________") #! Example output from: self.log.debug(f"Parse JWT token : {me}") # DEBUG - Parse JWT token : {'email': '[email protected]', # 'first_name': 'A', # 'last_name': 'J', # 'username': 'ebxxxxxxxxxxxxxxxxxxxxxxxxxxae7', # 'role_keys': ['airflow-user','airflow-admin']} return { "name": f"{me.get('first_name', '')} {me.get('last_name', '')}", "email": me.get("email"), "first_name": me.get("first_name"), "last_name": me.get("last_name"), "id": me.get("username"), "username": me.get("email"), # email as username avoids duplicates "role_keys": me.get("role_keys"), } # ------------------------------------------------------------------------------ # Register the Auth Manager for Airflow 3.1.x API Server # ------------------------------------------------------------------------------ FAB_SECURITY_MANAGER_CLASS = "webserver_config.AzureCustomSecurity" SECURITY_MANAGER_CLASS = AzureCustomSecurity ` ### Are you willing to submit PR? - [ ] Yes I am willing to submit a PR! ### Code of Conduct - [x] I agree to follow this project's [Code of Conduct](https://github.com/apache/airflow/blob/main/CODE_OF_CONDUCT.md) -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
