Skip to content

Can't connect Azure SQL with azure-active-directory-access-token method #1709

@norjoonas

Description

@norjoonas

Software versions

  • Tedious: 19.1.3
  • SQL Server: Microsoft SQL Azure (RTM) - 12.0.2000.8, Oct 2 2025 00:38:42
  • Node.js: v24.6.0

Additional Libraries Used and Versions
No additional libraries

Problem description
Cannot connect to Azure SQL with azure-active-directory-access-token method when using latest tedious version (v19.1.3). Connection works fine when using tedious v18.6.1
With v19 there is connection error, socket hang up.

Error message/stack trace
Here is output from test connection:

$ TEDIOUS_DEBUG=true node index.js
[tedious debug] State change: Initialized -> Connecting
[tedious debug] connected to ourdb.database.windows.net:1433
[tedious debug] State change: Connecting -> SentPrelogin
[tedious debug] State change: SentPrelogin -> SentTLSSSLNegotiation
[tedious debug] TLS negotiated (ECDHE-RSA-AES256-GCM-SHA384, TLSv1.2)
[tedious debug] State change: SentTLSSSLNegotiation -> SentLogin7WithStandardLogin
[tedious debug] socket ended
[tedious debug] State change: SentLogin7WithStandardLogin -> Final
Error:  ConnectionError: Connection lost - socket hang up
    at Connection.wrapSocketError (/home/user/testing/sql-test/node_modules/.pnpm/tedious@19.1.3/node_modules/tedious/lib/connection.js:1395:14)
    at Socket.onEnd (/home/user/testing/sql-test/node_modules/.pnpm/tedious@19.1.3/node_modules/tedious/lib/connection.js:1076:33)
    at Object.onceWrapper (node:events:622:28)
    ... 2 lines matching cause stack trace ...
    at process.processTicksAndRejections (node:internal/process/task_queues:90:21) {
  code: 'ESOCKET',
  [cause]: Error: socket hang up
      at Socket.onEnd (/home/user/testing/sql-test/node_modules/.pnpm/tedious@19.1.3/node_modules/tedious/lib/connection.js:1074:25)
      at Object.onceWrapper (node:events:622:28)
      at Socket.emit (node:events:520:35)
      at endReadableNT (node:internal/streams/readable:1701:12)
      at process.processTicksAndRejections (node:internal/process/task_queues:90:21) {
    code: 'ECONNRESET'
  }
}
Connection ended

Working example from v18.6.1
Here is same script output when using v18 tedious:

$ TEDIOUS_DEBUG=true node index.js
[tedious debug] State change: Initialized -> Connecting
[tedious debug] connected to ourdb.database.windows.net:1433
[tedious debug] State change: Connecting -> SentPrelogin
[tedious debug] State change: SentPrelogin -> SentTLSSSLNegotiation
[tedious debug] TLS negotiated (ECDHE-RSA-AES256-GCM-SHA384, TLSv1.2)
[tedious debug] State change: SentTLSSSLNegotiation -> SentLogin7WithStandardLogin
[tedious debug] Packet size changed from 4096 to 4096
[tedious debug] State change: SentLogin7WithStandardLogin -> ReRouting
[tedious debug] connection to ourdb.database.windows.net:1433 closed
[tedious debug] Rerouting to xxxxxx.yyyy.germanywestcentral1-a.worker.database.windows.net:11002
[tedious debug] State change: ReRouting -> Connecting
[tedious debug] connected to ourdb.database.windows.net:1433
[tedious debug] State change: Connecting -> SentPrelogin
[tedious debug] State change: SentPrelogin -> SentTLSSSLNegotiation
[tedious debug] TLS negotiated (ECDHE-RSA-AES256-GCM-SHA384, TLSv1.2)
[tedious debug] State change: SentTLSSSLNegotiation -> SentLogin7WithStandardLogin
[tedious debug] Packet size changed from 4096 to 4096
[tedious debug] State change: SentLogin7WithStandardLogin -> LoggedInSendingInitialSql
[tedious debug] State change: LoggedInSendingInitialSql -> LoggedIn
Connected
[tedious debug] State change: LoggedIn -> SentClientRequest
ping: 1
database_name: testdb
sql_version: Microsoft SQL Azure (RTM) - 12.0.2000.8
        Oct  2 2025 00:38:42
        Copyright (C) 2025 Microsoft Corporation

1 rows returned
[tedious debug] State change: SentClientRequest -> LoggedIn
1 rows returned
[tedious debug] State change: LoggedIn -> Final
Connection ended
[tedious debug] connection to ourdbdb.database.windows.net:1433 closed
[tedious debug] State is already Final

Any other details that can be helpful
Here is information about my test script, which can be ran on any Azure SQL without need to insert any data to database.

my package.json:

// package.json
{
  "name": "sqltest",
  "version": "1.0.0",
  "description": "",
  "license": "ISC",
  "author": "",
  "main": "index.js",
  "type": "CommonJS",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "dependencies": {
    "tedious": "^19.1.3"
  }
}

test script to repro problem:

// index.js
var Connection = require('tedious').Connection;
var Request = require('tedious').Request;
var fs = require('fs');

if (!process.env.AZURE_SQL_SERVER || !process.env.AZURE_SQL_DATABASE) {
  console.warn('Environment variables AZURE_SQL_SERVER and AZURE_SQL_DATABASE must be set.');
  process.exit(1);
}

function readToken() {
  try {
    return fs.readFileSync('/tmp/sql.token', 'utf8').trim();
  } catch (e) {
    console.error('Failed to read /tmp/sql.token file for AAD access token:', e.message);
    process.exit(1);
  }
}

var config = {
  server: process.env.AZURE_SQL_SERVER,
  authentication: {
    type : "azure-active-directory-access-token",
    options : {
      token : readToken()
    }
  },
  options: {
    database: process.env.AZURE_SQL_DATABASE,
    encrypt: true,
    port: parseInt(process.env.AZURE_SQL_PORT || '1433', 10),
    // Set to true only for local dev if certificate chain issues occur
    trustServerCertificate: process.env.TRUST_SQL_CERT === 'true',
    connectTimeout: 15000,
  }
};

var connection = new Connection(config);

// Capture low-level connection lifecycle events
connection.on('error', (err) => {
  if (!err) return;
  if (err instanceof AggregateError) {
    console.error('Aggregate connection error:');
    for (const e of err.errors) console.error('-', e.message || e);
  } else {
    console.error('Connection error event:', err.message || err);
  }
});

connection.on('end', () => {
  console.log('Connection ended');
});

connection.on('debug', (msg) => {
  if (process.env.TEDIOUS_DEBUG === 'true') {
    console.log('[tedious debug]', msg);
  }
});

connection.on('connect', function(err) {
  if(err) {
    console.log('Error: ', err);
    return;
  }
  console.log('Connected');
  executeStatement();
});

function executeStatement() {
  const request = new Request(
    "SELECT 1 AS ping, DB_NAME() AS database_name, @@VERSION AS sql_version",
    (err, rowCount) => {
      if (err) {
        console.error('Query error:', err);
      } else {
        console.log(rowCount + ' rows returned');
      }
    }
  );

  request.on('row', (columns) => {
    columns.forEach((column) => {
      if (column.value === null) {
        console.log('NULL');
      } else {
        console.log(column.metadata.colName + ": " + column.value);
      }
    });
  });

  request.on('doneInProc', (rowCount, more) => {
    console.log(rowCount + ' rows returned');
  });

  // Close connection after request completes
  request.on('requestCompleted', (rowCount, more) => {
    connection.close();
  });

  connection.execSql(request);
}

connection.connect();

Before running test script, env variables AZURE_SQL_SERVER and AZURE_SQL_DATABASE needs to be set, and token must be placed in /tmp/sql.token. Token can be acquired for example like this, if az cli is installed:

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

Problem is located to be in src/login7-payload.ts, in this section:

    // (cchUnused / cbExtension): 2-byte
    if (this.tdsVersion >= versions['7_4']) {
      const extensions = this.buildFeatureExt();
      offset = fixedData.writeUInt16LE(4 + extensions.length, offset);
      const extensionOffset = Buffer.alloc(4);
      extensionOffset.writeUInt32LE(dataOffset + 4, 0);
      dataOffset += 4 + extensions.length;
      buffers.push(extensionOffset, extensions);
    } else {
      // For TDS < 7.4, these are unused fields
      offset = fixedData.writeUInt16LE(0, offset);
    }

If that offset handling inside if-statement is returned to 18.6.x state (ie removing extensions.lenght addition), connection works again.

I am not confident enough that this is correct way to fix this issue, and would it cause problems elsewhere, so I can't really open up merge request. But I hope this helps to get quick fix for this.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions