Skip to content

ADSP auth fails against Dataverse TDS endpoint in 7.0.0+, tenant parsed as literal "authorize" from STSURL #4496

Description

@sbjessee

Describe the bug

Dataverse's FEDAUTHINFO returns an ADAL-style STSURL: "https://login.microsoftonline.com/{tenantId}/oauth2/authorize"

ActiveDirectoryAuthenticationProvider.AcquireTokenAsync splits the authority at the last / (src/Microsoft.Data.SqlClient.Extensions/Azure/src/ActiveDirectoryAuthenticationProvider.cs: separatorIndex = parameters.Authority.LastIndexOf('/')), so ClientSecretCredential receives tenantId: "authorize" and AuthorityHost: .../oauth2/. Azure SQL endpoints return "https://login.microsoftonline.com/{tenantId}" so only multi-segment STSURLs (like from Dataverse) break. The same URL format split exists in 6.x, where the older Azure.Identity/MSAL combination tolerated the malformed authority, the 7.0.0 refactor does not.

Exception message: 'Failed to authenticate the user [App Registration Client ID] in Active Directory (Authentication=ActiveDirectoryServicePrincipal).
Error code 0xUnknown
Failed to acquire access token for ActiveDirectoryServicePrincipal: Azure.Identity error: ClientSecretCredential authentication failed: '

Stack trace:
Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.GetFedAuthToken(SqlFedAuthInfo fedAuthInfo)
   at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo)
   at Microsoft.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
   at Microsoft.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
   at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.CompleteLogin(Boolean enlistOK)
   at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.AttemptOneLogin(ServerInfo serverInfo, String newPassword, SecureString newSecurePassword, TimeoutTimer timeout, Boolean withFailover)
   at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.LoginNoFailover(ServerInfo serverInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SqlConnectionString connectionOptions, SqlCredential credential, TimeoutTimer timeout)
   at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.OpenLoginEnlist(TimeoutTimer timeout, SqlConnectionString connectionOptions, SqlCredential credential, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance)
   at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal..ctor(DbConnectionPoolIdentity identity, SqlConnectionString connectionOptions, SqlCredential credential, DbConnectionPoolGroupProviderInfo providerInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SqlConnectionString userConnectionOptions, SessionData reconnectSessionData, Boolean applyTransientFaultHandling, String accessToken, IDbConnectionPool pool, Func`3 accessTokenCallback, SspiContextProvider sspiContextProvider)
   at Microsoft.Data.SqlClient.SqlConnectionFactory.CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, IDbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions)
   at Microsoft.Data.SqlClient.SqlConnectionFactory.CreatePooledConnection(DbConnection owningConnection, IDbConnectionPool pool, DbConnectionPoolKey poolKey, DbConnectionOptions options, DbConnectionOptions userOptions)
   at Microsoft.Data.SqlClient.ConnectionPool.WaitHandleDbConnectionPool.CreateObject(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection)
   at Microsoft.Data.SqlClient.ConnectionPool.WaitHandleDbConnectionPool.UserCreateRequest(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection)
   at Microsoft.Data.SqlClient.ConnectionPool.WaitHandleDbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, DbConnectionOptions userOptions, DbConnectionInternal& connection)
   at Microsoft.Data.SqlClient.ConnectionPool.WaitHandleDbConnectionPool.WaitForPendingOpen()

I blame this source:

int separatorIndex = parameters.Authority.LastIndexOf('/');
string authority = parameters.Authority.Remove(separatorIndex + 1);
string audience = parameters.Authority.Substring(separatorIndex + 1);

^^ Where parameters.Authority is "https://login.microsoftonline.com/{tenantId}/oauth2/authorize", from the Dataverse authentication

To reproduce

Try to open a connection to a Dataverse (Dynamics 365) TDS endpoint with SqlConnection, using a connection string in the format of: Server=[org-name].crm.dynamics.com;Authentication=Active Directory Service Principal;Encrypt=True;User Id=[Client ID];Password=[Client Secret].

var connectionString = "..."; // Specifically using "Authentication=Active Directory Service Principal" and a Dataverse server address
var sql = "...";
using var connection = new SqlConnection(connectionString);
using var command = new SqlCommand(sql, connection);
using var adapter = new SqlDataAdapter(command);
var dataSet = new DataSet();
await connection.OpenAsync();
adapter.Fill(dataSet);
return dataSet;

Expected behavior

Should be able to authenticate using the client credentials of an Azure App Registration. This works if using the same exact connection string, but with the Server set to a Fabric SQL endpoint (e.g., [endpoint-id].datawarehouse.fabric.microsoft.com). This also worked in version 6.1.1, before Azure/Entra dependencies were split off into Microsoft.Data.SqlClient.Extensions.Azure.

Suggested fix: parse the tenant as the first path segment of the STSURL (or use Uri segments) rather than the last, and ignore trailing /oauth2/authorize-style suffixes.

Further technical details

Microsoft.Data.SqlClient version: 7.0.2 (+ Microsoft.Data.SqlClient.Extensions.Azure 7.0.2)
.NET target: .NET 10
SQL Server version: N/A, using Dataverse TDS connection
Operating system: Windows 11, Docker container (Ubuntu, alpine)

Additional context
N/A

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    To triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions