Skip to main content
Version: Next

Azure AD Authentication for MSSQL Extension

Connect to Azure SQL Database and Microsoft Fabric using Azure Active Directory authentication.

Table of Contents​


Quick Start​

-- 1. Install and load required extensions
INSTALL azure;
LOAD azure;
INSTALL mssql FROM community;
LOAD mssql;

-- 2. Create Azure secret (service principal example)
CREATE SECRET my_azure (
TYPE azure,
PROVIDER service_principal,
TENANT_ID 'your-tenant-id',
CLIENT_ID 'your-client-id',
CLIENT_SECRET 'your-client-secret'
);

-- 3. Connect to Azure SQL Database
ATTACH 'Server=myserver.database.windows.net;Database=mydb' AS azuredb (
TYPE mssql,
AZURE_SECRET 'my_azure'
);

-- 4. Query data
SELECT * FROM azuredb.dbo.customers LIMIT 10;

Authentication Methods​

Best for CI/CD pipelines, scheduled jobs, and server applications.

CREATE SECRET azure_sp (
TYPE azure,
PROVIDER service_principal,
TENANT_ID 'your-tenant-id',
CLIENT_ID 'your-client-id',
CLIENT_SECRET 'your-client-secret'
);

ATTACH 'Server=myserver.database.windows.net;Database=mydb' AS db (
TYPE mssql,
AZURE_SECRET 'azure_sp'
);

Azure Setup Required:

  1. Register an application in Azure AD (Entra ID)
  2. Create a client secret for the application
  3. Grant database access:
    -- Run in Azure SQL Database
    CREATE USER [your-app-name] FROM EXTERNAL PROVIDER;
    ALTER ROLE db_datareader ADD MEMBER [your-app-name];
    ALTER ROLE db_datawriter ADD MEMBER [your-app-name];

Uses your existing az login credentials.

CREATE SECRET azure_cli (
TYPE azure,
PROVIDER credential_chain,
CHAIN 'cli'
);

ATTACH 'Server=myserver.database.windows.net;Database=mydb' AS db (
TYPE mssql,
AZURE_SECRET 'azure_cli'
);

Prerequisites:

az login
az account set --subscription "Your Subscription Name"

3. Environment Variables (For CI/CD)​

Uses Azure SDK standard environment variables—compatible with GitHub Actions, Azure DevOps, and all Azure SDKs.

# Set environment variables
export AZURE_TENANT_ID="your-tenant-id"
export AZURE_CLIENT_ID="your-client-id"
export AZURE_CLIENT_SECRET="your-client-secret"
CREATE SECRET azure_env (
TYPE azure,
PROVIDER credential_chain,
CHAIN 'env'
);

ATTACH 'Server=myserver.database.windows.net;Database=mydb' AS db (
TYPE mssql,
AZURE_SECRET 'azure_env'
);

4. Interactive (Device Code Flow)​

Best for interactive sessions, MFA-enabled accounts, and personal accounts.

CREATE SECRET azure_interactive (
TYPE azure,
PROVIDER credential_chain,
CHAIN 'interactive'
);

ATTACH 'Server=myserver.database.windows.net;Database=mydb' AS db (
TYPE mssql,
AZURE_SECRET 'azure_interactive'
);
-- Output: To sign in, use a web browser to open https://microsoft.com/devicelogin
-- and enter the code ABCD1234 to authenticate.

Example Session:

D> ATTACH 'Server=xyz.datawarehouse.fabric.microsoft.com;Database=my_warehouse' AS wh (
TYPE mssql, AZURE_SECRET 'azure_interactive'
);
To sign in, use a web browser to open https://microsoft.com/devicelogin
and enter the code LYBT74YQB to authenticate.
100% ▕████████████████████████████████████▏ (00:00:20.85 elapsed)

D> SHOW ALL TABLES;
┌──────────┬─────────┬────────────┬──────────────────┐
│ database │ schema │ name │ column_names │
├──────────┼─────────┼────────────┼──────────────────┤
│ wh │ dbo │ Date │ [DateID, Date..] │
│ wh │ dbo │ Geography │ [GeographyID..] │
│ wh │ dbo │ Trip │ [DateID, Medal.] │
└──────────┴─────────┴────────────┴──────────────────┘

5. Manual Access Token​

For external token management or pre-obtained tokens.

-- Direct token in ATTACH
ATTACH 'Server=myserver.database.windows.net;Database=mydb' AS db (
TYPE mssql,
ACCESS_TOKEN 'eyJ0eXAiOi...your-jwt-token'
);

Get a token:

az account get-access-token --resource https://database.windows.net/ --query accessToken -o tsv

Token Requirements:

  • Audience must be https://database.windows.net/
  • Token must not be expired (validated with 5-minute margin)

6. Reusable Access Token (via Azure Secret)​

Store a pre-obtained token in an Azure secret and reuse it across multiple connections.

-- Create Azure secret with pre-provided token
CREATE SECRET my_token (
TYPE azure,
PROVIDER access_token,
ACCESS_TOKEN 'eyJ0eXAiOi...your-jwt-token'
);

-- Reuse the same token for multiple databases
ATTACH 'Server=server1.database.windows.net;Database=db1' AS db1 (
TYPE mssql,
AZURE_SECRET 'my_token'
);
ATTACH 'Server=server2.database.windows.net;Database=db2' AS db2 (
TYPE mssql,
AZURE_SECRET 'my_token'
);

You can also combine it with an MSSQL secret:

CREATE SECRET my_token (
TYPE azure,
PROVIDER access_token,
ACCESS_TOKEN 'eyJ0eXAiOi...your-jwt-token'
);

CREATE SECRET azure_sql_conn (
TYPE mssql,
HOST 'myserver.database.windows.net',
DATABASE 'mydb',
AZURE_SECRET 'my_token'
);

ATTACH '' AS mydb (TYPE mssql, SECRET azure_sql_conn);

When to use this over direct ACCESS_TOKEN:

  • You need to connect to multiple servers/databases with the same token
  • You want to manage the token separately from connection details

Connection Examples​

Azure SQL Database​

ATTACH 'Server=myserver.database.windows.net;Database=mydb' AS azuresql (
TYPE mssql,
AZURE_SECRET 'my_azure'
);

Microsoft Fabric Data Warehouse​

ATTACH 'Server=xyz.datawarehouse.fabric.microsoft.com;Database=my_warehouse' AS fabric (
TYPE mssql,
AZURE_SECRET 'my_azure'
);

Azure SQL Managed Instance​

ATTACH 'Server=myinstance.public.abc123.database.windows.net,3342;Database=mydb' AS mi (
TYPE mssql,
AZURE_SECRET 'my_azure'
);

Using MSSQL Secrets​

You can store connection details and Azure credentials together in an MSSQL secret:

-- Create Azure secret
CREATE SECRET my_azure (
TYPE azure,
PROVIDER service_principal,
TENANT_ID 'your-tenant-id',
CLIENT_ID 'your-client-id',
CLIENT_SECRET 'your-client-secret'
);

-- Create MSSQL secret referencing Azure secret
CREATE SECRET azure_sql_conn (
TYPE mssql,
HOST 'myserver.database.windows.net',
DATABASE 'mydb',
AZURE_SECRET 'my_azure'
);

-- Attach using just the MSSQL secret
ATTACH '' AS mydb (TYPE mssql, SECRET azure_sql_conn);

With ACCESS_TOKEN:

CREATE SECRET mssql_token (
TYPE mssql,
HOST 'myserver.database.windows.net',
DATABASE 'mydb',
ACCESS_TOKEN 'eyJ0eXAiOi...your-jwt-token'
);

ATTACH '' AS mydb (TYPE mssql, SECRET mssql_token);

Troubleshooting​

Common Errors​

ErrorCauseSolution
Azure extension requiredAzure extension not loadedINSTALL azure; LOAD azure;
Azure secret 'xyz' not foundSecret doesn't existCheck SELECT * FROM duckdb_secrets();
AADSTS7000215: Invalid client secretWrong or expired secretGenerate new secret in Azure Portal
AADSTS700016: Application not foundWrong client IDVerify Application (client) ID in Azure AD
Azure CLI credentials expiredCLI token expiredRun az login
Connection reset by peerToken too large (older versions)Update to v0.1.11+
Invalid access token formatMalformed JWTVerify token format
Access token audienceWrong token audienceToken must be for https://database.windows.net/
Access token expiredToken has expiredGet a fresh token

Testing Credentials​

Test your Azure credentials without connecting to a database:

SELECT mssql_azure_auth_test('my_azure_secret');
-- Returns: eyJ0eXAiOi... [1634 chars]

-- For interactive auth, pass tenant_id:
SELECT mssql_azure_auth_test('azure_interactive', 'your-tenant-id');

Token Caching​

Tokens are cached automatically:

  • Cache is per secret name (and per DuckDB instance), thread-safe
  • Tokens refresh automatically 5 minutes before expiration
  • First call: ~200ms (acquires from Azure AD)
  • Subsequent calls: ~0ms (uses cache)

Certificates​

Azure SQL Database, Azure SQL Managed Instance and Fabric Warehouse present publicly-chained certificates, so the default — the chain checked against the platform trust store and the subject against the host you connect to — passes with nothing added, including through the login-time redirect Fabric and the Redirect connection policy use (the redirected host is the one checked). Two cases need a word:

  • A private endpoint or an SSH tunnel reached under a name the certificate was not issued for: add HostNameInCertificate=<the server's public name> (URI hostnameincertificate, secret host_name_in_certificate).
  • TrustServerCertificate=yes turns verification off and is never needed for Azure. See TLS/SSL Configuration.

Token Lifetime and Long-Running Sessions​

An Azure AD access token lives 60 minutes. Two things follow, and the extension handles both (issue #302):

  • A connection that is already open keeps working. SQL Server validates the token at login only; a live session is never re-authenticated. A catalog attached for hours is fine as long as it keeps reusing its pooled connections.
  • A new connection needs a token that is still valid. The pool creates one whenever a query needs more connections than it has, or after an idle connection was closed (mssql_idle_timeout). The pool resolves the token when it creates the connection, not when the catalog was attached: the cached token is used while it is good, and past the refresh margin the secret is read again and a new token minted. service_principal, cli and env refresh silently.

Two kinds of secret cannot refresh, and they say so by name rather than timing out — Azure SQL does not answer an expired token with an error, it drops the connection, so the client has to diagnose expiry itself:

Azure AD access token in secret 'x' expired at 2026-09-10 08:49:00 UTC;
a fixed token cannot be refreshed -- DETACH and ATTACH with a new one

Azure AD token for secret 'x' has expired and its credential chain needs
interactive authentication, which cannot run from a pooled connection;
DETACH and ATTACH to authenticate again

That is PROVIDER access_token (a fixed string) and the interactive device-code chain (nobody is at a terminal inside a query on a worker thread). For a script that runs longer than an hour against Azure, use service_principal or cli.

Before this, an expired token surfaced as Failed to acquire connection from pool (timeout) after the full mssql_acquire_timeout, and only DETACH / ATTACH fixed it.


Microsoft Fabric​

Microsoft Fabric Data Warehouses have some differences from Azure SQL Database:

What a Warehouse Is, and What Follows​

A Fabric Data Warehouse stores its tables as Delta Parquet. Almost every difference below follows from that one fact, and the extension enforces them before generating any DDL rather than letting the server reject it.

OperationAzure SQLMicrosoft Fabric
CTASBCPBCP
COPY TOBCPBCP
INSERT / SELECTFullFull

BCP works on Fabric. Earlier releases forced CTAS onto an INSERT fallback and refused COPY TO outright; both are gone. Microsoft still labels bcp on Warehouse a preview, so if you hit a tenant where it is unavailable, the escape hatch is SET mssql_ctas_use_bcp = false.

No NVARCHAR​

There is no UTF-16 type in Parquet, so a warehouse has no nvarchar or nchar at all — CREATE TABLE (v nvarchar(50)) is refused with "not supported in this edition of SQL Server". Since nvarchar(max) is the extension's default for string columns everywhere else, CTAS and COPY force VARCHAR on a Fabric catalog whatever mssql_ctas_text_type says. Nothing is lost: a warehouse collation is UTF-8, so a varchar column holds every character an nvarchar would — counting bytes rather than UTF-16 units.

MSSQL_NVARCHAR(n) is refused with an error naming MSSQL_VARCHAR(n) as the alternative.

varchar(max) is supported (16 MB), contrary to older guidance.

Two collations, both UTF-8​

A warehouse accepts only Latin1_General_100_BIN2_UTF8 (the default) and Latin1_General_100_CI_AS_KS_WS_SC_UTF8, and the choice is fixed when the warehouse is created. The extension emits no COLLATE clause for a permanent table — inheriting the warehouse's own is right — and refuses any other collation named in MSSQL_VARCHAR(n, 'collation') before the DDL is built.

Because every string column on a warehouse is a UTF-8 varchar, BCP sends UTF-8 bytes there rather than transcoding to UTF-16, which halves the wire for ASCII-ish data.

No indexes, no page compression​

CREATE TABLE ... WITH (table_kind = ...), clustered_index and data_compression are all refused on a Fabric catalog: Delta Parquet has no indexes, and carries its own compression. A warehouse is already columnar.

Types with no warehouse equivalent​

Beyond nvarchar/nchar: datetimeoffset, datetime, smalldatetime, money, smallmoney, tinyint, text, ntext, image, xml, geometry and geography. Note datetimeoffset in particular — the extension maps DuckDB's TIMESTAMP WITH TIME ZONE onto it, so such a column cannot be created on a warehouse.

Temp tables​

A #temp table works on a warehouse but not as a BCP target: the load fails inside Fabric with an I/O error against a parquet file. Load into a permanent table instead. Note also that a temp table lives in tempdb and does not inherit the warehouse's UTF-8 collation, so the extension names it explicitly on any temp target it creates.

Performance Tips for Fabric​

  1. Size your string columns — MSSQL_VARCHAR(n), or mssql_default_string_length
  2. Break large loads into smaller batches
  3. Keep mssql_copy_tablock in mind for large single-writer loads

Reference​

Supported Azure Services​

ServiceConnection FormatBCP Support
Azure SQL Databasename.database.windows.net✅ Full
Azure SQL Managed Instancename.public.xyz.database.windows.net,3342✅ Full
Microsoft Fabric DWxyz.datawarehouse.fabric.microsoft.com✅ Full (preview upstream)
Azure Synapse Serverlessname-ondemand.sql.azuresynapse.net⚠️ Limited

Chain Priority​

When using multiple providers (e.g., CHAIN 'env;cli'), they're tried in order:

  1. env — Environment variables
  2. cli — Azure CLI credentials
  3. interactive — Device code flow

This matches Azure SDK's DefaultAzureCredential behavior.

Token Sizes​

MethodToken SizeUTF-16LE SizeTDS Packets
Service Principal~1632 chars~3264 bytes1
Azure CLI~2091 chars~4182 bytes2
Interactive~2000+ chars~4000+ bytes1-2

Security Best Practices​

  1. Never commit secrets to source control
  2. Use service principals for production (not interactive auth)
  3. Rotate secrets every 90 days
  4. Grant least privilege database permissions
  5. Enable Azure AD audit logs

See Also​