Login to Azure SQL with JWT token in python – [SQL Server]Login failed for user ‘

I try to connect to Azure SQL using SQLAlchemy in python, authenticating with JWT generated for App registration. Despite checking almost any combination of parameters and granting all potentially meaningful permissions, I still get following error:

[28000] [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Login failed for user ”. (18456)

I did the following:

  1. Created App registration ‘test-app’.
  2. Executed on the db:
   CREATE USER [test-app] FROM EXTERNAL PROVIDER;
   EXEC sp_addrolemember N'db_datareader', N'test-app';
   GRANT SELECT ON test_vw to [test-app];
  1. Added ‘test-app’ to Contributor and Reader roles for the SQL Server.
  2. Whitelisted IP in the portal – I’m able to log in through SSMS and ‘AAD Universal with MFA’ using personal account.
  3. Generated token with (tenant ID matches the one used by the organization):

POST login.microsoftonline.com/9ff8761-1be4-4729-b88df-e421e19d92f/oauth2/v2.0/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded

client_id=[client_id]
client_secret=[client_secret]
scope=https://database.windows.net/.default
grant_type=client_credentials

  1. Used generated JWT token in following python code:
from sqlalchemy import create_engine
import urllib
import struct

server = "test-server.database.windows.net"
database = "test-db"
driver = "{ODBC Driver 17 for SQL Server}"
jwt_token = "<jwt_token>"


SQL_COPT_SS_ACCESS_TOKEN = 1256
exptoken = b""
for i in bytes(jwt_token, "UTF-8"):
    exptoken += bytes({i})
    exptoken += bytes(1)
tokenstruct = struct.pack("=i", len(exptoken)) + exptoken

params = urllib.parse.quote_plus(
    f"Driver={driver};Server={server};Database={database};Encrypt=yes"
)
conn_str = f"mssql+pyodbc:///?odbc_connect={params}"
engine_azure = create_engine(
    conn_str,
    connect_args={"attrs_before": {SQL_COPT_SS_ACCESS_TOKEN: tokenstruct}},
    echo=True,
)

with engine_azure.connect() as con:
    rs = con.execute("SELECT TOP 100 * FROM test_vw")
    for row in rs:
        print(row)

Any idea what I’ve missed?

Read more here: Source link