Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,19 +48,23 @@ By adhering to the DB API 2.0 specification, the mssql-python module ensures com

### Support for Microsoft Entra ID Authentication

The Microsoft mssql-python driver enables Python applications to connect to Microsoft SQL Server, Azure SQL Database, or Azure SQL Managed Instance using Microsoft Entra ID identities. It supports various authentication methods, including username and password, Microsoft Entra managed identity, and Integrated Windows Authentication in a federated, domain-joined environment. Additionally, the driver supports Microsoft Entra interactive authentication and Microsoft Entra managed identity authentication for both system-assigned and user-assigned managed identities.
The Microsoft mssql-python driver enables Python applications to connect to Microsoft SQL Server, Azure SQL Database, or Azure SQL Managed Instance using Microsoft Entra ID identities. It supports a variety of authentication methods, including username and password, Microsoft Entra managed identity (system-assigned and user-assigned), Integrated Windows Authentication in a federated, domain-joined environment, interactive authentication via browser, device code flow for environments without browser access, and the default authentication method based on environment and configuration. This flexibility allows developers to choose the most suitable authentication approach for their deployment scenario.

EntraID authentication is now fully supported on MacOS and Linux but with certain limitations as mentioned in the table:

| Authentication Method | Windows Support | macOS/Linux Support | Notes |
|----------------------|----------------|---------------------|-------|
| ActiveDirectoryPassword | ✅ Yes | ✅ Yes | Username/password-based authentication |
| ActiveDirectoryInteractive | ✅ Yes | ❌ No | Only works on Windows |
| ActiveDirectoryInteractive | ✅ Yes | ✅ Yes | Interactive login via browser; requires user interaction |
| ActiveDirectoryMSI (Managed Identity) | ✅ Yes | ✅ Yes | For Azure VMs/containers with managed identity |
| ActiveDirectoryServicePrincipal | ✅ Yes | ✅ Yes | Use client ID and secret or certificate |
| ActiveDirectoryIntegrated | ✅ Yes | ❌ No | Only works on Windows (requires Kerberos/SSPI) |
| ActiveDirectoryDeviceCode | ✅ Yes | ✅ Yes | Device code flow for authentication; suitable for environments without browser access |
| ActiveDirectoryDefault | ✅ Yes | ✅ Yes | Uses default authentication method based on environment and configuration |

> **NOTE**: For using Access Token, the connection string *must not* contain `UID`, `PWD`, `Authentication`, or `Trusted_Connection` keywords.

> **NOTE**: For using Access Token, the connection string **must not** contain `UID`, `PWD`, `Authentication`, or `Trusted_Connection` keywords.
> **NOTE**: For using ActiveDirectoryDeviceCode, make sure to specify a `Connect Timeout` that provides enough time to go through the device code flow authentication process.

### Enhanced Pythonic Features

Expand Down
84 changes: 46 additions & 38 deletions mssql_python/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,82 +103,90 @@ def add_driver_name_to_app_parameter(connection_string):
# Initialize variables
app_found = False
modified_parameters = []
has_aad_interactive = False
has_aad_device_code = False
# Track authentication types
auth_type = None

# Iterate through the key-value pairs
for param in parameters:
param = param.strip()
if not param:
continue

if param.lower().startswith("authentication="):
# Handle AAD Interactive authentication
key, auth_value = param.split("=", 1)
if auth_value.lower() == "activedirectoryinteractive":
has_aad_interactive = True
# Only keep the auth parameter on Windows

key_value = param.split("=", 1)
if len(key_value) != 2:
continue
key, value = key_value
key_lower = key.lower()
value_lower = value.lower()

if key_lower == "authentication":
if value_lower == "activedirectoryinteractive":
auth_type = "interactive"
if platform.system().lower() != "windows":
modified_parameters.append(param)
continue
if auth_value.lower() == "activedirectorydevicecode":
has_aad_device_code = True

if param.lower().startswith("app="):
elif value_lower == "activedirectorydevicecode":
auth_type = "devicecode"
continue
elif value_lower == "activedirectorydefault":
auth_type = "default"
continue

if key_lower == "app":
app_found = True
key, _ = param.split("=", 1)
modified_parameters.append(f"{key}=MSSQL-Python")
modified_parameters.append("APP=MSSQL-Python")
else:
modified_parameters.append(param)

# If APP key is not found, append it
if not app_found:
modified_parameters.append("APP=MSSQL-Python")

if has_aad_device_code:

# Remove Uid, Pwd, Connection Timeout, Encrypt, TrustServerCertificate
# Remove sensitive parameters for AAD auth
if auth_type in ("default", "devicecode", "interactive"):
exclude_keys = [
"uid=", "pwd=", "encrypt=", "trustservercertificate=", "authentication="
]
modified_parameters = [
param for param in modified_parameters
if not any(key in param.lower() for key in ["uid=", "pwd=", "connection timeout=", "encrypt=", "trustservercertificate=", "authentication="])
if not any(param.lower().startswith(exclude) for exclude in exclude_keys)
]
modified_parameters.append("Connection Timeout=180") # Add default connection timeout
# Handle AAD Device Code auth

# Handle each AAD authentication type
if auth_type == "default":
try:
from azure.identity import DeviceCodeCredential
from azure.identity import DefaultAzureCredential
import struct
except ImportError:
raise ImportError("Please install azure-identity: pip install azure-identity")
credential = DefaultAzureCredential()
token_bytes = credential.get_token("https://database.windows.net/.default").token.encode("UTF-16-LE")
token_struct = struct.pack(f"<I{len(token_bytes)}s", len(token_bytes), token_bytes)
return ";".join(modified_parameters) + ";", {1256: token_struct}

if auth_type == "devicecode":
try:
from azure.identity import DeviceCodeCredential
import struct
except ImportError:
raise ImportError("Please install azure-identity: pip install azure-identity")
credential = DeviceCodeCredential()
token_bytes = credential.get_token("https://database.windows.net/.default").token.encode("UTF-16-LE")
token_struct = struct.pack(f"<I{len(token_bytes)}s", len(token_bytes), token_bytes)
conn_str = ";".join(modified_parameters) + ";", {1256: token_struct}
return conn_str

# Handle AAD Interactive auth for non-Windows platforms
if has_aad_interactive and platform.system().lower() != "windows":

# Remove Uid, Pwd, Connection Timeout, Encrypt, TrustServerCertificate
modified_parameters = [
param for param in modified_parameters
if not any(key in param.lower() for key in ["uid=", "pwd=", "connection timeout=", "encrypt=", "trustservercertificate=", "authentication="])
]
return ";".join(modified_parameters) + ";", {1256: token_struct}

if auth_type == "interactive" and platform.system().lower() != "windows":
try:
from azure.identity import InteractiveBrowserCredential
import struct
except ImportError:
raise ImportError("Please install azure-identity: pip install azure-identity")

credential = InteractiveBrowserCredential()
token_bytes = credential.get_token("https://database.windows.net/.default").token.encode("UTF-16-LE")
token_struct = struct.pack(f"<I{len(token_bytes)}s", len(token_bytes), token_bytes)
conn_str = ";".join(modified_parameters) + ";", {1256: token_struct}
return conn_str
return ";".join(modified_parameters) + ";", {1256: token_struct}

conn_str = ";".join(modified_parameters) + ";"
return conn_str
return ";".join(modified_parameters) + ";"


def detect_linux_distro():
Expand Down