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

> Discover authentication differences between Airflow 2.x and 3.x in MCP Airflow API. Learn about automatic switching between Basic Auth and JWT Bearer tokens.

- Repository: [JungJungIn/mcp-airflow-api](https://github.com/call518/mcp-airflow-api)
- Tags: deep-dive
- Published: 2026-02-26

---

**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`](https://github.com/call518/mcp-airflow-api/blob/main/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:

```python

# 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`](https://github.com/call518/mcp-airflow-api/blob/main/functions.py)):

```python

# 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:

```python

# 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`](https://github.com/call518/mcp-airflow-api/blob/main/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`](https://github.com/call518/mcp-airflow-api/blob/main/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`](https://github.com/call518/mcp-airflow-api/blob/main/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`](https://github.com/call518/mcp-airflow-api/blob/main/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`](https://github.com/call518/mcp-airflow-api/blob/main/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`](https://github.com/call518/mcp-airflow-api/blob/main/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`](https://github.com/call518/mcp-airflow-api/blob/main/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`](https://github.com/call518/mcp-airflow-api/blob/main/src/mcp_airflow_api/tools/v1_tools.py) (lines 13-21) and [`src/mcp_airflow_api/tools/v2_tools.py`](https://github.com/call518/mcp-airflow-api/blob/main/src/mcp_airflow_api/tools/v2_tools.py) (lines 18-22).