tediousjs / tedious

Node TDS module for connecting to SQL Server databases.

Home Page:http://tediousjs.github.io/tedious/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Login failed for user '<token-identified principal>' when using authentication.type = 'azure-active-directory-service-principal-secret'

thomasholknielsen opened this issue · comments

I'm trying to connect to an Azure SQL DB with the following config object

config: {
      server: process.env.SQL_SERVER,
      authentication: {
        type: 'azure-active-directory-service-principal-secret',
        options: {
          clientId: process.env.AZURE_CLIENT_ID,
          clientSecret: process.env.AZURE_CLIENT_SECRET,
          tenantId: process.env.AZURE_TENANT_ID
        },
      },
      options: {
        database: process.env.SQL_DATABASE,
        port: 1433,  
        encrypt: true,
        enableArithAbort: true,        
      }
    } 

When using the default authentication type supplied with userName and password option, connectivity works as expected.
However, when using type= 'azure-active-directory-service-principal-secret' I get an error stating that: Login failed for user token-identified principal

At first, I thought the issue was related to permissions, missing SQL USER on the database, but surprisingly the following PowerShell script allows me to connect successfully.

Import-Module MSAL.PS

$tenantId = "redacted"   # tenantID (Azure Directory ID) were AppSP resides
$clientId = "redacted"   # AppID also ClientID for AppSP     
$clientSecret = "redacted"   # Client secret for AppSP 
$scopes = "https://database.windows.net/.default" # The end-point

$result = Get-MsalToken -RedirectUri $uri -ClientId $clientId -ClientSecret (ConvertTo-SecureString $clientSecret -AsPlainText -Force) -TenantId $tenantId -Scopes $scopes

$Tok = $result.AccessToken
#Write-host "token"
$Tok

$SQLServerName = "myserver"    # Azure SQL logical server name 
$DatabaseName = "mydb"     # Azure SQL database name

Write-Host "Create SQL connection string"
$conn = New-Object System.Data.SqlClient.SQLConnection 
$conn.ConnectionString = "Data Source=$SQLServerName.database.windows.net;Initial Catalog=$DatabaseName;Connect Timeout=30"
$conn.AccessToken = $Tok

Write-host "Connect to database and execute SQL script"
$conn.Open() 
$ddlstmt = 'SELECT name AS TABLE_NAME FROM sys.tables;'
$command = New-Object -TypeName System.Data.SqlClient.SqlCommand($ddlstmt, $conn)       
$result = $command.ExecuteReader()
$result
$conn.Close()

The only notable difference I found between the two approaches is the $scopes = "https://database.windows.net/.default" that is included in the PowerShell script. I wasn't able to find any references to such a variable in the Tedious documentation.

Is this a bug or am I missing something obvious? Help is much appreciated.

Regards
Thomas

Hi @thomasholknielsen, Thank you for bring this up, and the detail explanation. We will try to reproduce this on our side, and will get back to you if we found the issue or we have a solution.

Hi @thomasholknielsen, we've been looking into this and it seems like it could be a permissions issue on the SQL server side. That error message is from the DB server side, usually indicating that the client does not have permission to connect to the DB it's trying to connect to. Tedious actually does use the same "https://database.windows.net/.default" scope internally so we don't believe that is the issue. Is it possible to verify on the Azure side that this app registration should be able to connect to this database properly?

Also, what version of Tedious are you using?

Hi @MichaelSun90 and @mShan0, thanks for taking the time to look at this.

I figured out what the problem was - the tedious library was not the issue.

I was using the package node-mssql, which utilizes tedious behind the scenes. However, node-mssql holds the database property at the root of the config object, whereas tedious has it placed under root.options.database. Since my original config didn't contain the root.database property, it was trying to connect to the master database. And since I was connecting to an Azure SQL DB which utilizes contained database users, the user didn't have access to the master database.

For anyone else who runs into this, allow me to share the steps to get it working:

  1. Create Azure SQL database, make sure you add an active directory user as SQL server admin
  2. Ensure that the SQL server Managed Identity has Directory Reader privilege on the active directory otherwise the following step will result in an error.
  3. Log into the newly created SQL database using the active directory account that was specified as SQL server admin in step 1.
  4. Create contained SQL user on Azure SQL database: CREATE USER [MyExampleSpn] FROM EXTERNAL PROVIDER
  5. When connecting using node-mssql make sure you add the database property and make sure you add it the root-level as shown here:
config: 
      database: process.env.SQL_DATABASE, <-- **ADD HERE**
      server: process.env.SQL_SERVER,
      authentication: {
        type: 'azure-active-directory-service-principal-secret',
        options: {
          clientId: process.env.AZURE_CLIENT_ID,
          clientSecret: process.env.AZURE_CLIENT_SECRET,
          tenantId: process.env.AZURE_TENANT_ID
        },
      },
      options: {
        database: process.env.SQL_DATABASE, <-- **ADDING HERE HAS NO EFFECT**
        port: 1433,  
        encrypt: true,
        enableArithAbort: true,        
      }
    } 

Hi @thomasholknielsen,

Thanks so much for getting back to us with such a detailed solution. I'm glad you got it figured out.