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&timestamp_gte=***&timestamp_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]

Reply via email to