Skip to content

FEAT: Entra id authentication support for AQE - #870

Open
Subrata (subrata-ms) wants to merge 4 commits into
mainfrom
subrata-ms/AQEEntraIdAuth
Open

Subrata (subrata-ms) wants to merge 4 commits into
mainfrom
subrata-ms/AQEEntraIdAuth

Conversation

@subrata-ms

@subrata-ms Subrata (subrata-ms) commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Work Item / Issue Reference

AB#49671

GitHub Issue: #<ISSUE_NUMBER>


Summary

This pull request introduces Entra (Azure Active Directory) authentication support for asynchronous connections in the mssql_python package. It adds a new authentication helper, updates the async connection logic to handle various Entra authentication modes and token providers, and improves validation and error handling for authentication options. The tests have also been updated to reflect the new authentication flow.

Entra Authentication Support and Async Connection Updates:

  • Added a new module mssql_python/async_query/_auth.py that provides Entra authentication helpers, credential caching, and async token acquisition for async connections. It supports built-in Azure Identity credentials and custom token providers, with careful handling of multi-user scenarios.
  • Updated mssql_python/async_query/async_connection.py to:
    • Accept a new token_provider parameter for supplying a custom token provider.
    • Use the new prepare_async_authentication function to handle authentication and token acquisition before establishing a connection.
    • Expanded the connect method's docstring to explain Entra authentication modes, token provider usage, and limitations. [1] [2] [3] [4]

Authentication Validation Improvements:

  • Improved validation in mssql_python/async_query/_connection_context.py to accept supported Entra authentication modes and provide clearer error messages for unsupported modes. [1] [2]

Testing Updates:

  • Updated the test in tests/AsyncTest/test_002_async_connection.py to check that Entra authentication modes are accepted in the connection context without immediately acquiring a token, reflecting the new authentication flow.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 28, 2026 07:41
@github-actions github-actions Bot added the pr-size: large Substantial code update label Oct 28, 2026
@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

PR Performance Report

✅ No regression detected

No consistent slowdowns detected across all 2 environments.

0 IMPROVEMENTS 0 SLOWDOWNS 2/2 ENVIRONMENTS

Coverage: 2 of 2 environments completed. Advisory result; does not block merging.

Performance diagnostics

Phase times are inclusive diagnostics and must not be added together. They identify where measured time changed, not why it changed.

No affected phases or call-count changes were recorded.

All database tasks and timings

Unix / SQL Server 2022

Database task Before After Paired change Result
Connection opening 11.097 ms 10.999 ms -0.8% no signal
SELECT queries 1.112 ms 1.096 ms -0.1% no signal
Row insertion 32.801 ms 32.184 ms -2.0% no signal
Executemany inserts 160.074 ms 159.225 ms -0.4% no signal
Fetch-all queries 123.463 ms 123.965 ms +0.1% no signal
Row-by-row fetching 14.885 ms 14.639 ms -2.7% no signal
Batched row fetching 118.425 ms 118.595 ms +0.9% no signal
Transaction commit and rollback 106.754 ms 106.406 ms -0.3% no signal
Arrow row fetching 91.628 ms 91.634 ms -1.9% no signal
100,000-row insertion 449.875 ms 451.916 ms +1.7% no signal
Row fetching in batches of 100 122.091 ms 122.927 ms +0.4% no signal
Row fetching in batches of 10,000 141.572 ms 135.920 ms +1.5% no signal
Repeated positional queries 32.053 ms 31.923 ms -0.4% no signal
Repeated named-parameter queries 34.832 ms 34.258 ms -1.4% no signal
Legacy 100,000-row insertion 361.860 ms 366.127 ms +1.1% no signal
Insertion with explicit input sizes 516.212 ms 510.177 ms +0.2% no signal
Joined aggregation queries 177.539 ms 178.414 ms +0.9% no signal
Large joined-result fetching 197.489 ms 197.248 ms +0.3% no signal
1.2-million-row fetching 3491.591 ms 3494.845 ms +0.4% no signal
Common table expression queries 5.658 ms 5.642 ms +1.8% no signal
256 KiB VARCHAR(MAX) / fetchall() 1.653 ms 1.693 ms +3.0% no signal
10,000 scalar values / fetchval() (debug disabled) 108.816 ms 107.949 ms -0.2% no signal

Unix / SQL Server 2025

Database task Before After Paired change Result
Connection opening 97.262 ms 97.756 ms +0.3% no signal
SELECT queries 1.084 ms 1.052 ms -2.8% no signal
Row insertion 32.781 ms 32.460 ms -1.0% no signal
Executemany inserts 150.220 ms 150.078 ms -0.2% no signal
Fetch-all queries 118.659 ms 119.929 ms +1.0% no signal
Row-by-row fetching 14.599 ms 14.422 ms -1.2% no signal
Batched row fetching 115.409 ms 120.006 ms +3.3% no signal
Transaction commit and rollback 107.945 ms 108.616 ms +0.3% no signal
Arrow row fetching 88.268 ms 89.667 ms +3.0% no signal
100,000-row insertion 423.244 ms 418.769 ms +0.5% no signal
Row fetching in batches of 100 119.058 ms 121.808 ms +3.0% no signal
Row fetching in batches of 10,000 123.919 ms 138.693 ms +8.4% no signal
Repeated positional queries 31.903 ms 31.897 ms +0.2% no signal
Repeated named-parameter queries 34.176 ms 34.194 ms +0.0% no signal
Legacy 100,000-row insertion 337.829 ms 342.634 ms +1.2% no signal
Insertion with explicit input sizes 469.524 ms 477.971 ms +0.6% no signal
Joined aggregation queries 161.981 ms 162.365 ms +0.1% no signal
Large joined-result fetching 181.730 ms 185.841 ms -0.1% no signal
1.2-million-row fetching 3420.074 ms 3493.899 ms +0.3% no signal
Common table expression queries 5.096 ms 5.106 ms +1.8% no signal
256 KiB VARCHAR(MAX) / fetchall() 1.839 ms 2.019 ms +1.0% no signal
10,000 scalar values / fetchval() (debug disabled) 106.914 ms 107.649 ms +0.2% no signal
Build and measurement details

ADO build 187005

PR head: b8696f82fc22081333586f15f5dab32d9d5c073f
Base: 30c740d094ee1c3e1e291d845a3f0d7bc222d1d5
Measured merge: f1759d9b1e37ed9db41a6f563a79b3fdbcd3c335

  • Unix / SQL Server 2022: Python 3.12.3, x86_64, SQL 16.0.4305.5; 5 paired comparisons and 1 warmup.
  • Unix / SQL Server 2025: Python 3.12.3, x86_64, SQL 17.0.5035.5; 5 paired comparisons and 1 warmup.

A consistent change requires more than 20% median paired movement, at least 1 ms between the median runtimes, and at least 80% of pairs exceeding the relative threshold in the same direction. A slowdown without enough pair agreement is reported as inconsistent.

The displayed change is the median of paired before-and-after ratios. It is not recalculated from the two displayed median runtimes.

Both revisions use profiling-enabled builds on the same agent and database, with alternating order and discarded warmups. Results are diagnostic and do not represent production-wheel latency.

Raw samples and logs are attached to the ADO run as profiler-* artifacts.

Comment thread tests/AsyncTest/test_008_async_entraid_auth.py Dismissed
Comment thread tests/AsyncTest/test_008_async_entraid_auth.py Dismissed
Comment thread tests/AsyncTest/test_008_async_entraid_auth.py Dismissed
Comment thread tests/AsyncTest/test_008_async_entraid_auth.py Dismissed
Comment thread tests/AsyncTest/test_008_async_entraid_auth.py Dismissed
Comment thread tests/AsyncTest/test_008_async_entraid_auth.py Dismissed
Comment thread tests/AsyncTest/test_008_async_entraid_auth.py Dismissed
Comment thread tests/AsyncTest/test_008_async_entraid_auth.py Dismissed
Comment thread tests/AsyncTest/test_008_async_entraid_auth.py Dismissed
Comment thread tests/AsyncTest/test_008_async_entraid_auth.py Dismissed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Entra authentication depends on native Rust handshake and callback behavior that could not be runtime-validated with the pinned extension and a live Entra-enabled SQL endpoint.

No open issues 🎉

What changed in this PR

Adds Microsoft Entra ID authentication to the Rust-backed async connection layer, supporting built-in Azure Identity credentials and custom synchronous token providers.

Changes:

  • Adds credential caching, off-loop token acquisition, and service-principal callback setup.
  • Extends async connection options and validation, documenting authentication modes and limitations.
  • Adds tests for authentication flows, error handling, cancellation, credential reuse, and native integration.
File Description
tests/​AsyncTest/​test_008_async_entraid_auth.py Adds async Entra authentication and integration coverage.
tests/​AsyncTest/​test_002_async_connection.py Updates context validation expectations to accept Entra authentication.
mssql_python/​async_query/​async_connection.py Adds token_provider and prepares authentication before native login.
mssql_python/​async_query/​_connection_context.py Allows supported Entra modes through connection validation.
mssql_python/​async_query/​_auth.py Implements credential caching, token acquisition, and service-principal setup.

Review effort: Balanced

@subrata-ms Subrata (subrata-ms) changed the title FEAT: support for Entra id authentication FEAT: AQE support for Entra id authentication Oct 28, 2026
@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

📊 Code Coverage Report

🔥 Diff Coverage

100%


🎯 Overall Coverage

85%


📈 Total Lines Covered: 9575 out of 11231
📁 Project: mssql-python


Diff Coverage

Diff: main...HEAD, staged and unstaged changes

  • mssql_python/async_query/_auth.py (100%)
  • mssql_python/async_query/_connection_context.py (100%)
  • mssql_python/async_query/async_connection.py (100%)

Summary

  • Total: 65 lines
  • Missing: 0 lines
  • Coverage: 100%

📋 Files Needing Attention

📉 Files with overall lowest coverage (click to expand)
mssql_python.pybind.performance_counter.hpp: 0.7%
mssql_python.pybind.logger_bridge.cpp: 57.9%
mssql_python.pybind.ddbc_bindings.h: 62.6%
mssql_python.pybind.logger_bridge.hpp: 70.8%
mssql_python.pybind.connection.connection_pool.cpp: 81.3%
mssql_python.pybind.connection.connection.cpp: 84.1%
mssql_python.logging.py: 86.2%
mssql_python.pooling.py: 90.1%
mssql_python.pybind.fetch_temporal.hpp: 92.1%
mssql_python.cursor.py: 92.5%

🔗 Quick Links

⚙️ Build Summary 📋 Coverage Details

View Azure DevOps Build

Browse Full Coverage Report

@subrata-ms Subrata (subrata-ms) changed the title FEAT: AQE support for Entra id authentication FEAT: Entra id authentication support for AQE Oct 28, 2026
Copilot AI balanced review requested due to automatic review settings October 28, 2026 09:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The native authentication implementation is unavailable, and existing mocks cannot establish Entra SQL handshake success, callback threading, or cancellation behavior without live validation.

No open issues 🎉

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 29, 2026 08:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The test offloads the native hook itself with asyncio.to_thread, so its thread assertion cannot detect a regression that runs get_token() on the event loop during real async login.

1 open issue

Review effort: Balanced

Comment on lines +784 to +785
result = await asyncio.to_thread(
core._invoke_entra_id_token_factory,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Entra async-authentication review against the supplied AQE design and Azure Identity implementation. Posting the four approved inline comments; no approval or request-changes decision is submitted.

client_id = None
if auth_type == _AuthInternal.MSI:
client_id = (context.get("user_name") or "").strip() or None
raw_token = await asyncio.to_thread(_acquire_builtin_token, auth_type, client_id)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

§4 of the AQE design explicitly requires azure.identity.aio credentials and await credential.get_token(...). Azure Identity provides async DefaultAzureCredential and ManagedIdentityCredential, but this path runs their synchronous counterparts through asyncio's shared executor. The custom-provider branch also reuses the sync helper that rejects async credentials, so callers cannot supply the credential model required by the design.

Please use async acquisition for modes with supported SDK implementations and accept async token providers, preserving token validation and DB-API error mapping. Give driver-owned async credentials an explicit event-loop-compatible lifetime and await their cleanup; leave caller-owned providers caller-owned. Any necessary synchronous compatibility paths should be separate and explicitly documented rather than making every mode thread-backed.

raw_token, _ = await asyncio.to_thread(acquire_raw_token_from_credential, token_provider)
elif auth_type == _AuthInternal.SERVICE_PRINCIPAL:
try:
authenticated["entra_id_token_factory"] = ServicePrincipalAuth.make_token_factory(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This wires the synchronous bulk-copy token factory into the new async ServicePrincipal path. That factory calls synchronous ClientSecretCredential.get_token(), although Azure Identity provides aio.ClientSecretCredential. Offloading the factory prevents event-loop blocking, but still occupies a worker throughout token acquisition.

Please make this path await async acquisition after the server supplies the tenant/SPN, coordinating any required native callback-contract and dependency-pin changes. The inspected native bridge expects immediate bytes, so simply changing the Python factory to async def would not work. Preserve the existing synchronous bulk-copy behavior. If an awaitable callback cannot be supported in this slice, explicitly gate and document ServicePrincipal as an approved blocking exception rather than claiming end-to-end async compliance.

The native bridge was inspected at upstream commit bf415d10; this does not establish the exact repository-pinned wheel's implementation. The Python factory's synchronous token acquisition is directly verifiable here.



@pytest.mark.asyncio
async def test_live_configured_authentication_login_and_query(async_connection):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test accepts the generic DB_CONNECTION_STRING, so it can pass with SQL-password authentication without exercising an Entra handshake. The closed-port native tests and separately invoked callback hook do not establish successful FedAuth login.

Please add explicitly Entra-configured integration coverage for the pre-acquired-token and ServicePrincipal callback paths. Add async-provider acceptance, cancellation and credential-cleanup tests as well—the current test at line 457 instead requires async credentials to be rejected. For modes intended to be genuinely async, verify that the driver does not offload token acquisition to an executor; an event-loop heartbeat alone also passes synchronous acquisition moved to a worker.


Entra modes: ActiveDirectoryDefault, ActiveDirectoryMsi (optional UID
selects a user-assigned identity), ActiveDirectoryDeviceCode,
ActiveDirectoryInteractive, and client-secret ActiveDirectoryServicePrincipal.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please document a per-mode async capability matrix before describing this feature as end-to-end async.

Azure Identity's public async API supports Default, Managed Identity and client-secret authentication, but does not expose aio.DeviceCodeCredential or aio.InteractiveBrowserCredential in the checked versions (1.12.0 and 1.24.0). Those interactive modes need an explicitly approved compatibility exception or a clear unsupported status; they should not prevent genuinely async implementation of the other modes.

The matrix should also cover Default credential-chain behavior on supported event loops: the SDK's CLI credential has a synchronous fallback on Windows Selector loops. Please ensure the selected chain remains nonblocking, or explicitly approve and document that limitation.

Finally, declare the async HTTP transport requirement and credential ownership/cleanup policy. Azure Identity does not install an async transport automatically. Confirm how that prerequisite fits the design's “no new PyPI dep” requirement rather than relying on an incidental development-environment installation.

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

Labels

pr-size: large Substantial code update

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants