Authentication Differences Between Airflow 2.x and 3.x in MCP Airflow API

The mcp-airflow-api package automatically switches between HTTP Basic Auth for Airflow 2.x (API v1) and JWT Bearer tokens for Airflow 3.x (API v2), with a fallback to Basic Auth when token acquisition fails.

The mcp-airflow-api repository provides a unified Model Context Protocol (MCP) interface for interacting with Apache Airflow's REST API across major versions. Understanding the authentication differences between Airflow 2.x and 3.x is critical for configuring secure connections, as the underlying mechanisms differ significantly in credential handling and session management.

How Authentication Works in Airflow 2.x (API v1)

Airflow 2.x relies exclusively on HTTP Basic Authentication for every API request. The MCP package handles this transparently by reading environment variables and attaching credentials to each call.

Basic Auth Implementation

In src/mcp_airflow_api/functions.py, the airflow_request() function detects API version v1 and creates a BasicAuth object using the username and password from environment variables:


# Lines 64-66 in functions.py

elif api_version == "v1":
    from aiohttp import BasicAuth
    auth = BasicAuth(username, password)

The credentials are sourced from AIRFLOW_API_USERNAME and AIRFLOW_API_PASSWORD, with the request URL constructed via construct_api_url() to target the /v1/ endpoint path.

How Authentication Works in Airflow 3.x (API v2)

Airflow 3.x introduces JWT (JSON Web Token) Bearer authentication as the primary method, reducing the exposure of raw credentials and eliminating per-request authentication overhead.

JWT Bearer Token Flow

When AIRFLOW_API_VERSION is set to v2, the airflow_request() function first attempts to obtain a cached JWT token via the get_jwt_token() helper (defined in lines 81-124 of functions.py):


# Lines 55-58 in functions.py

if api_version == "v2":
    token = await get_jwt_token(username, password, base_url)
    if token:
        headers["Authorization"] = f"Bearer {token}"

The get_jwt_token() function:

  1. Checks for a cached token and validates its expiry (~23 hours)
  2. Calls the /auth/token endpoint using Basic Auth to exchange credentials for a JWT
  3. Caches the access_token and returns it for header injection

Fallback to Basic Auth

If JWT acquisition fails (e.g., the Airflow instance does not support tokens or the auth endpoint is unreachable), the system gracefully falls back to Basic Auth for backward compatibility:


# Lines 61-64 in functions.py (fallback logic)

if not token:
    from aiohttp import BasicAuth
    auth = BasicAuth(username, password)

This ensures the MCP tools function in mixed environments where some Airflow 3.x deployments may have JWT disabled.

Key Authentication Differences Between Airflow 2.x and 3.x

The authentication models differ in credential transmission, session management, and configuration requirements:

  • Credential Exposure: Airflow 2.x sends username and password with every request, while Airflow 3.x exchanges credentials once for a time-bound JWT that expires after approximately 23 hours.
  • Header Format: Version 2.x uses Authorization: Basic <base64_credentials>, whereas version 3.x uses Authorization: Bearer <jwt_token> when available.
  • Token Management: The get_jwt_token() function handles automatic caching and refresh for Airflow 3.x, requiring no manual token management from the user.
  • Environment Variables: Both versions require AIRFLOW_API_USERNAME, AIRFLOW_API_PASSWORD, and AIRFLOW_API_BASE_URL, but Airflow 3.x additionally checks AIRFLOW_API_VERSION to trigger JWT logic.
  • Endpoint Structure: API v1 paths contain /v1/, while v2 paths use /v2/, constructed via the shared construct_api_url() utility.

Implementation Details in the Source Code

The authentication abstraction is implemented across several key files in the call518/mcp-airflow-api repository:

src/mcp_airflow_api/functions.py

  • Contains the core airflow_request() function (lines 55-68) that branches based on api_version
  • Implements get_jwt_token() (lines 81-124) for JWT acquisition and caching
  • Handles the Basic Auth fallback logic for failed token requests

src/mcp_airflow_api/tools/v1_tools.py

  • Registers Airflow 2.x specific tools and binds airflow_request_v1 to force API v1 behavior (lines 13-21)

src/mcp_airflow_api/tools/v2_tools.py

  • Registers Airflow 3.x specific tools including asset-related endpoints and binds airflow_request_v2 (lines 18-22)

src/mcp_airflow_api/mcp_main.py

  • Bootstrap module that loads the appropriate tool set based on the configured API version, determining whether to use Basic Auth or JWT authentication

Summary

  • Airflow 2.x (API v1) uses HTTP Basic Auth with credentials sent on every request, implemented in airflow_request() via BasicAuth objects.
  • Airflow 3.x (API v2) prefers JWT Bearer tokens obtained via get_jwt_token() and cached for ~23 hours, falling back to Basic Auth when necessary.
  • The mcp-airflow-api package abstracts these authentication differences through environment variables (AIRFLOW_API_VERSION, AIRFLOW_API_USERNAME, AIRFLOW_API_PASSWORD) and automatic version detection in src/mcp_airflow_api/functions.py.

Frequently Asked Questions

How do I configure authentication for Airflow 2.x versus Airflow 3.x?

Set the AIRFLOW_API_VERSION environment variable to "v1" for Airflow 2.x or "v2" for Airflow 3.x. Both versions require AIRFLOW_API_USERNAME, AIRFLOW_API_PASSWORD, and AIRFLOW_API_BASE_URL. For Airflow 3.x, the system automatically exchanges these credentials for a JWT token via the /auth/token endpoint, while Airflow 2.x sends the credentials with every request using Basic Auth.

What happens if the JWT token expires during a long-running operation?

The get_jwt_token() function in src/mcp_airflow_api/functions.py caches the JWT token with an expiry timestamp set to approximately 23 hours. If the token expires or is invalid, the function automatically requests a new token from the Airflow server's /auth/token endpoint using the stored credentials. This refresh happens transparently before the next API request is made.

Can I force Basic Auth even when connecting to Airflow 3.x?

Yes, the airflow_request() function includes a fallback mechanism that activates when get_jwt_token() returns None or fails. If the JWT acquisition fails due to network issues, missing endpoints, or disabled token authentication on the Airflow server, the code falls back to creating a BasicAuth object and sending credentials directly. You can also force v1 behavior by setting AIRFLOW_API_VERSION=v1 or using the v1_tools registration path.

Where is the authentication logic implemented in the source code?

The core authentication logic resides in src/mcp_airflow_api/functions.py. Lines 55-68 contain the airflow_request() function that branches between JWT (v2) and Basic Auth (v1) logic. The get_jwt_token() helper (lines 81-124) handles JWT acquisition and caching. Version-specific tool registrations that bind the appropriate request functions are located in src/mcp_airflow_api/tools/v1_tools.py (lines 13-21) and src/mcp_airflow_api/tools/v2_tools.py (lines 18-22).

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →