Repository: tediousjs/node-mssql
Pull request #1461 (2023-01-20 11:11:40)

Fixed issues related to previous PR regarding AAD authentication via connection string
This PR is aimed to fix a couple of issues with the previous PR: https://github.com/tediousjs/node-mssql/pull/1436 in attempt to accomodate issue #1400 

Changes include:
 1. Authentication now uses the standard values defined on [.NET Platform Extension 7](https://learn.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlconnection.connectionstring?view=dotnet-plat-ext-7.0)
 2. Fixes the breakage of standard/basic connection which has been reported in #1460 

Shin-Aska: I tested this using the following:

Basic SQL Login:
```javascript
var sql = require("../../node-mssql/tedious")
var main = async () => {
	var server = `${process.env.server}`
	var username = `${process.env.username}`
	var database = `${process.env.database}`
	var password = `${process.env.password}`
	var table = `${process.env.table}`

	var conn_str = `Server=${server};Database=${database};User Id=${username};Password=${password};`
	console.log(conn_str)
	await sql.connect(conn_str).then(async(pool) => {
		var result = await pool.request().query(`select top 1 * from ${table}`).catch((err) => {
			console.warn(err)
			throw err
		});
		console.log(result)
	})
}

main()
```

![image](https://user-images.githubusercontent.com/8596711/213841315-b162e872-f8df-464d-b7b5-cdc19352fbe0.png)

The other one is using azure-active-directory-service-principal-secret. (Have to redact starting here)

```javascript
var sql = require("../../node-mssql/tedious")
var main = async () => {

	var server = `${process.env.server}`
	var database = `${process.env.database}`
	var conn_str = `Server=${server};Database=${database};Authentication=Active Directory Integrated;client id=${process.env.AZURE_CLIENT_ID};client secret=${process.env.AZURE_CLIENT_SECRET};tenant id=${process.env.AZURE_TENANT_ID}`;
	var table = `${process.env.table}`

	await sql.connect(conn_str).then(async(pool) => {
		var result = await pool.request().query(`select top 1 * from ${table}`).catch((err) => {
			console.warn(err)
			throw err
		});
		console.log(result)
	});
}

main();
```

![image](https://user-images.githubusercontent.com/8596711/213841757-ed0cf76c-afde-4ef2-b0db-162b9bfc0fe1.png)

And lastly is azure-active-directory-access-token
```javascript
var sql = require("../../node-mssql/tedious")
const { EnvironmentCredential } = require('@azure/identity')
const tokenUrl = 'https://database.windows.net/.default'

var main = async () => {
	const credential = new EnvironmentCredential()
	const { token } = await credential.getToken(tokenUrl);
	var server = `${process.env.server}`
	var database = `${process.env.database}`
	var table = `${process.env.table}`

	var conn_str = `Server=${server};Database=${database};Authentication=Active Directory Integrated;Token=${token};`;
	await sql.connect(conn_str).then(async(pool) => {
		var result = await pool.request().query(`select top 1 * from ${table}`).catch((err) => {
			console.warn(err)
			throw err
		});
		console.log(result)
	});
}

main();
```
![image](https://user-images.githubusercontent.com/8596711/213842142-d5785354-e3dd-4255-8646-1a0f9c8fc093.png)


On the basic login, I tried both SQL servers hosted elsewhere outside Azure (SQL Server 2017-2019) and Azure. While the AAD one is Azure only.
dhensby: Thanks for that. Whilst that manual testing is really helpful, what I was referring to was the automated test suite. We need some tests that the connection string gets correctly parsed and the output shape looks right for our internal config structure.

The [unit tests](https://github.com/tediousjs/node-mssql/blob/v9.1.1/test/common/unit.js) is the best place to add this. Something like:

```js
describe('connection string auth', () => {
  it('parses basic login', () => {
    const config = BaseConnection._parseConnectionString('test string here')
    assert.equal(config, { expected shape })
  })
  ... more tests here
})
```
Shin-Aska: Ok cool, I do have some questions though, for example this connection string:

```javascript
'Server=database.test.com;Database=test;User Id=admin;Password=admin'
```

In the BaseConnectionPool under /lib/base/connection-pool.js, the object becomes:
```javascript
{
  options: { instanceName: undefined },
  pool: {},
  port: 1433,
  server: 'database.test.com',
  database: 'test',
  domain: undefined,
  user: 'test',
  password: 'admin'
}
```

But if it passes through /lib/tedious/connection-pool.js, the config above becomes:
```javascript
{
  server: 'database.test.com',
  options: {
    encrypt: true,
    trustServerCertificate: false,
    instanceName: undefined,
    database: 'test',
    port: 1433,
    connectTimeout: 15000,
    requestTimeout: 15000,
    tdsVersion: '7_4',
    rowCollectionOnDone: false,
    rowCollectionOnRequestCompletion: false,
    useColumnNames: false,
    appName: 'node-mssql'
  },
  authentication: {
    type: 'default',
    options: {
      userName: 'admin',
      password: 'admin',
      domain: undefined,
      clientId: undefined,
      clientSecret: undefined,
      tenantId: undefined,
      token: undefined
    }
  }
}
```

Which one should we start testing, the base one? tedious or both? and if both or tedious is included, would it be fine if we seperate the config generation away from _poolCreate() so we can reuse them on the unit-test file?
Shin-Aska: Hey @dhensby, I made some changes today, I decided to add both unit tests (Base and Tedious) comparison instead of just base or tedious. Seems to be a good idea to test the two in-case some changes are made either in the connection-pool of either base library or tedious, we can check if it gets affected.

Patch:
diff --git a/README.md b/README.md
index 63f2fa2..9a69c86 100644
--- a/README.md
+++ b/README.md
@@ -574,11 +574,50 @@ In addition to configuration object there is an option to pass config as a conne
 
 ##### Classic Connection String
 
+###### Standard configuration using tedious driver
+
 ```
 Server=localhost,1433;Database=database;User Id=username;Password=password;Encrypt=true
+```
+###### Standard configuration using msnodesqlv8 driver
+```
 Driver=msnodesqlv8;Server=(local)\INSTANCE;Database=database;UID=DOMAIN\username;PWD=password;Encrypt=true
 ```
 
+##### Azure Active Directory Authentication Connection String
+
+Several types of Azure Authentication are supported:
+
+###### Authentication using Active Directory Integrated
+```
+Server=*.database.windows.net;Database=database;Authentication=Active Directory Integrated;Client secret=clientsecret;Client Id=clientid;Tenant Id=tenantid;Encrypt=true
+```
+Note: Internally, the 'Active Directory Integrated' will change its type depending on the other parameters you add to it. On the example above, it will change to azure-active-directory-service-principal-secret because we supplied a Client Id, Client secret and Tenant Id.
+
+If you want to utilize Authentication tokens (azure-active-directory-access-token) Just remove the unnecessary additional parameters and supply only a token parameter, such as in this example:
+
+```
+Server=*.database.windows.net;Database=database;Authentication=Active Directory Integrated;token=token;Encrypt=true
+```
+
+Finally if you want to utilize managed identity services such as managed identity service app service you can follow this example below:
+```
+Server=*.database.windows.net;Database=database;Authentication=Active Directory Integrated;msi endpoint=msiendpoint;Client Id=clientid;msi secret=msisecret;Encrypt=true
+```
+or if its managed identity service virtual machines, then follow this:
+```
+Server=*.database.windows.net;Database=database;Authentication=Active Directory Integrated;msi endpoint=msiendpoint;Client Id=clientid;Encrypt=true
+```
+
+We can also utilizes Active Directory Password but unlike the previous examples, it is not part of the Active Directory Integrated Authentication.
+
+###### Authentication using Active Directory Password
+```
+Server=*.database.windows.net;Database=database;Authentication=Active Directory Password;User Id=username;Password=password;Client Id=clientid;Tenant Id=tenantid;Encrypt=true
+```
+
+For more reference, you can consult [here](https://tediousjs.github.io/tedious/api-connection.html#function_newConnection). Under the authentication.type parameter.
+
 ## Drivers
 
 ### Tedious
diff --git a/lib/base/connection-pool.js b/lib/base/connection-pool.js
index 6dcc6fa..80b151a 100644
--- a/lib/base/connection-pool.js
+++ b/lib/base/connection-pool.js
@@ -99,6 +99,28 @@ class ConnectionPool extends EventEmitter {
     return this._parseConnectionString(connectionString)
   }
 
+  static _parseAuthenticationType (type, entries) {
+    switch (type.toLowerCase()) {
+      case 'active directory integrated':
+        if (entries.includes('token')) {
+          return 'azure-active-directory-access-token'
+        } else if (['client id', 'client secret', 'tenant id'].every(entry => entries.includes(entry))) {
+          return 'azure-active-directory-service-principal-secret'
+        } else if (['client id', 'msi endpoint', 'msi secret'].every(entry => entries.includes(entry))) {
+          return 'azure-active-directory-msi-app-service'
+        } else if (['client id', 'msi endpoint'].every(entry => entries.includes(entry))) {
+          return 'azure-active-directory-msi-vm'
+        }
+        return 'azure-active-directory-default'
+      case 'active directory password':
+        return 'azure-active-directory-password'
+      case 'ntlm':
+        return 'ntlm'
+      default:
+        return 'default'
+    }
+  }
+
   static _parseConnectionString (connectionString) {
     const parsed = parseSqlConnectionString(connectionString, true, true)
     return Object.entries(parsed).reduce((config, [key, value]) => {
@@ -115,6 +137,9 @@ class ConnectionPool extends EventEmitter {
         case 'attachdbfilename':
           break
         case 'authentication':
+          Object.assign(config, {
+            authentication_type: this._parseAuthenticationType(value, Object.keys(parsed))
+          })
           break
         case 'column encryption setting':
           break
@@ -134,6 +159,16 @@ class ConnectionPool extends EventEmitter {
           break
         case 'context connection':
           break
+        case 'client id':
+          Object.assign(config, {
+            clientId: value
+          })
+          break
+        case 'client secret':
+          Object.assign(config, {
+            clientSecret: value
+          })
+          break
         case 'current language':
           Object.assign(config.options, {
             language: value
@@ -173,9 +208,11 @@ class ConnectionPool extends EventEmitter {
             port,
             server
           })
-          Object.assign(config.options, {
-            instanceName
-          })
+          if (instanceName) {
+            Object.assign(config.options, {
+              instanceName
+            })
+          }
           break
         }
         case 'encrypt':
@@ -204,6 +241,16 @@ class ConnectionPool extends EventEmitter {
             min: value
           })
           break
+        case 'msi endpoint':
+          Object.assign(config, {
+            msiEndpoint: value
+          })
+          break
+        case 'msi secret':
+          Object.assign(config, {
+            msiSecret: value
+          })
+          break
         case 'multipleactiveresultsets':
           break
         case 'multisubnetfailover':
@@ -231,6 +278,16 @@ class ConnectionPool extends EventEmitter {
           break
         case 'replication':
           break
+        case 'tenant id':
+          Object.assign(config, {
+            tenantId: value
+          })
+          break
+        case 'token':
+          Object.assign(config, {
+            token: value
+          })
+          break
         case 'transaction binding':
           Object.assign(config.options, {
             enableImplicitTransactions: value.toLowerCase() === 'implicit unbind'
@@ -253,10 +310,16 @@ class ConnectionPool extends EventEmitter {
             domain = domainUser[1]
             user = domainUser[2]
           }
-          Object.assign(config, {
-            domain,
-            user
-          })
+          if (domain) {
+            Object.assign(config, {
+              domain
+            })
+          }
+          if (user) {
+            Object.assign(config, {
+              user
+            })
+          }
           break
         }
         case 'user instance':
diff --git a/lib/tedious/connection-pool.js b/lib/tedious/connection-pool.js
index d3c8ec1..014c0d4 100644
--- a/lib/tedious/connection-pool.js
+++ b/lib/tedious/connection-pool.js
@@ -8,6 +8,61 @@ const shared = require('../shared')
 const ConnectionError = require('../error/connection-error')
 
 class ConnectionPool extends BaseConnectionPool {
+  _config () {
+    const cfg = {
+      server: this.config.server,
+      options: Object.assign({
+        encrypt: typeof this.config.encrypt === 'boolean' ? this.config.encrypt : true,
+        trustServerCertificate: typeof this.config.trustServerCertificate === 'boolean' ? this.config.trustServerCertificate : false
+      }, this.config.options),
+      authentication: Object.assign({
+        type: this.config.domain !== undefined ? 'ntlm' : this.config.authentication_type !== undefined ? this.config.authentication_type : 'default',
+        options: Object.entries({
+          userName: this.config.user,
+          password: this.config.password,
+          domain: this.config.domain,
+          clientId: this.config.clientId,
+          clientSecret: this.config.clientSecret,
+          tenantId: this.config.tenantId,
+          token: this.config.token,
+          msiEndpoint: this.config.msiEndpoint,
+          msiSecret: this.config.msiSecret
+        }).reduce((acc, [key, val]) => {
+          if (typeof val !== 'undefined') {
+            return { ...acc, [key]: val }
+          }
+          return acc
+        }, {})
+      }, this.config.authentication)
+    }
+
+    cfg.options.database = cfg.options.database || this.config.database
+    cfg.options.port = cfg.options.port || this.config.port
+    cfg.options.connectTimeout = cfg.options.connectTimeout ?? this.config.connectionTimeout ?? this.config.timeout ?? 15000
+    cfg.options.requestTimeout = cfg.options.requestTimeout ?? this.config.requestTimeout ?? this.config.timeout ?? 15000
+    cfg.options.tdsVersion = cfg.options.tdsVersion || '7_4'
+    cfg.options.rowCollectionOnDone = cfg.options.rowCollectionOnDone || false
+    cfg.options.rowCollectionOnRequestCompletion = cfg.options.rowCollectionOnRequestCompletion || false
+    cfg.options.useColumnNames = cfg.options.useColumnNames || false
+    cfg.options.appName = cfg.options.appName || 'node-mssql'
+
+    // tedious always connect via tcp when port is specified
+    if (cfg.options.instanceName) delete cfg.options.port
+
+    if (isNaN(cfg.options.requestTimeout)) cfg.options.requestTimeout = 15000
+    if (cfg.options.requestTimeout === Infinity || cfg.options.requestTimeout < 0) cfg.options.requestTimeout = 0
+
+    if (!cfg.options.debug && this.config.debug) {
+      cfg.options.debug = {
+        packet: true,
+        token: true,
+        data: true,
+        payload: true
+      }
+    }
+    return cfg
+  }
+
   _poolCreate () {
     return new shared.Promise((resolve, reject) => {
       const resolveOnce = (v) => {
@@ -18,49 +73,9 @@ class ConnectionPool extends BaseConnectionPool {
         reject(e)
         resolve = reject = () => {}
       }
-      const cfg = {
-        server: this.config.server,
-        options: Object.assign({
-          encrypt: typeof this.config.encrypt === 'boolean' ? this.config.encrypt : true,
-          trustServerCertificate: typeof this.config.trustServerCertificate === 'boolean' ? this.config.trustServerCertificate : false
-        }, this.config.options),
-        authentication: Object.assign({
-          type: this.config.domain !== undefined ? 'ntlm' : 'default',
-          options: {
-            userName: this.config.user,
-            password: this.config.password,
-            domain: this.config.domain
-          }
-        }, this.config.authentication)
-      }
-
-      cfg.options.database = cfg.options.database || this.config.database
-      cfg.options.port = cfg.options.port || this.config.port
-      cfg.options.connectTimeout = cfg.options.connectTimeout ?? this.config.connectionTimeout ?? this.config.timeout ?? 15000
-      cfg.options.requestTimeout = cfg.options.requestTimeout ?? this.config.requestTimeout ?? this.config.timeout ?? 15000
-      cfg.options.tdsVersion = cfg.options.tdsVersion || '7_4'
-      cfg.options.rowCollectionOnDone = cfg.options.rowCollectionOnDone || false
-      cfg.options.rowCollectionOnRequestCompletion = cfg.options.rowCollectionOnRequestCompletion || false
-      cfg.options.useColumnNames = cfg.options.useColumnNames || false
-      cfg.options.appName = cfg.options.appName || 'node-mssql'
-
-      // tedious always connect via tcp when port is specified
-      if (cfg.options.instanceName) delete cfg.options.port
-
-      if (isNaN(cfg.options.requestTimeout)) cfg.options.requestTimeout = 15000
-      if (cfg.options.requestTimeout === Infinity || cfg.options.requestTimeout < 0) cfg.options.requestTimeout = 0
-
-      if (!cfg.options.debug && this.config.debug) {
-        cfg.options.debug = {
-          packet: true,
-          token: true,
-          data: true,
-          payload: true
-        }
-      }
       let tedious
       try {
-        tedious = new tds.Connection(cfg)
+        tedious = new tds.Connection(this._config())
       } catch (err) {
         rejectOnce(err)
         return


Test patch:
diff --git a/test/common/unit.js b/test/common/unit.js
index 03418ad..bf25b21 100644
--- a/test/common/unit.js
+++ b/test/common/unit.js
@@ -6,6 +6,7 @@ const sql = require('../../')
 const assert = require('assert')
 const udt = require('../../lib/udt')
 const BasePool = require('../../lib/base/connection-pool')
+const ConnectionPool = require('../../lib/tedious/connection-pool')
 
 describe('Unit', () => {
   it('table', done => {
@@ -422,3 +423,316 @@ describe('connection string parser', () => {
     })
   })
 })
+
+describe('connection string auth - base', () => {
+  it('parses basic login', () => {
+    const config = BasePool._parseConnectionString('Server=database.test.com;Database=test;User Id=test;Password=admin')
+    assert.deepEqual(config, {
+      database: 'test',
+      options: {},
+      password: 'admin',
+      pool: {},
+      port: 1433,
+      server: 'database.test.com',
+      user: 'test'
+    })
+  })
+
+  it('parses basic login with explicit Sql Password as Authentication', () => {
+    const config = BasePool._parseConnectionString('Authentication=Sql Password;Server=database.test.com;Database=test;User Id=test;Password=admin')
+    assert.deepEqual(config, {
+      authentication_type: 'default',
+      database: 'test',
+      options: {},
+      password: 'admin',
+      pool: {},
+      port: 1433,
+      server: 'database.test.com',
+      user: 'test'
+    })
+  })
+
+  it('parses active directory password', () => {
+    const config = BasePool._parseConnectionString('Server=*.database.windows.net;Database=test;Authentication=Active Directory Password;User Id=username;Password=password;Client Id=clientid;Tenant Id=tenantid;Encrypt=true')
+    assert.deepEqual(config, {
+      authentication_type: 'azure-active-directory-password',
+      database: 'test',
+      options: {
+        encrypt: true
+      },
+      password: 'password',
+      pool: {},
+      port: 1433,
+      server: '*.database.windows.net',
+      user: 'username',
+      clientId: 'clientid',
+      tenantId: 'tenantid'
+    })
+  })
+
+  it('parses active directory integrated token authentication', () => {
+    const config = BasePool._parseConnectionString('Server=*.database.windows.net;Database=test;Authentication=Active Directory Integrated;token=token;Encrypt=true')
+    assert.deepEqual(config, {
+      authentication_type: 'azure-active-directory-access-token',
+      database: 'test',
+      options: {
+        encrypt: true
+      },
+      pool: {},
+      port: 1433,
+      server: '*.database.windows.net',
+      token: 'token'
+    })
+  })
+
+  it('parses active directory integrated client secret authentication', () => {
+    const config = BasePool._parseConnectionString('Server=*.database.windows.net;Database=test;Authentication=Active Directory Integrated;Client secret=clientsecret;Client Id=clientid;Tenant Id=tenantid;Encrypt=true')
+    assert.deepEqual(config, {
+      authentication_type: 'azure-active-directory-service-principal-secret',
+      database: 'test',
+      options: {
+        encrypt: true
+      },
+      pool: {},
+      port: 1433,
+      server: '*.database.windows.net',
+      clientId: 'clientid',
+      tenantId: 'tenantid',
+      clientSecret: 'clientsecret'
+    })
+  })
+
+  it('parses active directory integrated managed service identity app vm', () => {
+    const config = BasePool._parseConnectionString('Server=*.database.windows.net;Database=test;Authentication=Active Directory Integrated;msi endpoint=msiendpoint;Client Id=clientid;Encrypt=true')
+    assert.deepEqual(config, {
+      authentication_type: 'azure-active-directory-msi-vm',
+      database: 'test',
+      options: {
+        encrypt: true
+      },
+      pool: {},
+      port: 1433,
+      server: '*.database.windows.net',
+      clientId: 'clientid',
+      msiEndpoint: 'msiendpoint'
+    })
+  })
+
+  it('parses active directory integrated managed service identity app service', () => {
+    const config = BasePool._parseConnectionString('Server=*.database.windows.net;Database=test;Authentication=Active Directory Integrated;msi endpoint=msiendpoint;Client Id=clientid;msi secret=msisecret;Encrypt=true')
+    assert.deepEqual(config, {
+      authentication_type: 'azure-active-directory-msi-app-service',
+      database: 'test',
+      options: {
+        encrypt: true
+      },
+      pool: {},
+      port: 1433,
+      server: '*.database.windows.net',
+      clientId: 'clientid',
+      msiEndpoint: 'msiendpoint',
+      msiSecret: 'msisecret'
+    })
+  })
+})
+
+describe('connection string auth - tedious', () => {
+  it('parses basic login', () => {
+    const baseConfig = BasePool._parseConnectionString('Server=database.test.com;Database=test;User Id=test;Password=admin')
+    const config = new ConnectionPool(baseConfig)._config()
+    assert.deepEqual(config, {
+      server: 'database.test.com',
+      options: {
+        encrypt: true,
+        trustServerCertificate: false,
+        database: 'test',
+        port: 1433,
+        connectTimeout: 15000,
+        requestTimeout: 15000,
+        tdsVersion: '7_4',
+        rowCollectionOnDone: false,
+        rowCollectionOnRequestCompletion: false,
+        useColumnNames: false,
+        appName: 'node-mssql'
+      },
+      authentication: {
+        type: 'default',
+        options: {
+          userName: 'test',
+          password: 'admin'
+        }
+      }
+    })
+  })
+
+  it('parses basic login with explicit Sql Password as Authentication', () => {
+    const baseConfig = BasePool._parseConnectionString('Authentication=Sql Password;Server=database.test.com;Database=test;User Id=test;Password=admin')
+    const config = new ConnectionPool(baseConfig)._config()
+    assert.deepEqual(config, {
+      server: 'database.test.com',
+      options: {
+        encrypt: true,
+        trustServerCertificate: false,
+        database: 'test',
+        port: 1433,
+        connectTimeout: 15000,
+        requestTimeout: 15000,
+        tdsVersion: '7_4',
+        rowCollectionOnDone: false,
+        rowCollectionOnRequestCompletion: false,
+        useColumnNames: false,
+        appName: 'node-mssql'
+      },
+      authentication: {
+        type: 'default',
+        options: {
+          userName: 'test',
+          password: 'admin'
+        }
+      }
+    })
+  })
+
+  it('parses active directory password', () => {
+    const baseConfig = BasePool._parseConnectionString('Server=*.database.windows.net;Database=test;Authentication=Active Directory Password;User Id=username;Password=password;Client Id=clientid;Tenant Id=tenantid;Encrypt=true')
+    const config = new ConnectionPool(baseConfig)._config()
+    assert.deepEqual(config, {
+      server: '*.database.windows.net',
+      options: {
+        encrypt: true,
+        trustServerCertificate: false,
+        database: 'test',
+        port: 1433,
+        connectTimeout: 15000,
+        requestTimeout: 15000,
+        tdsVersion: '7_4',
+        rowCollectionOnDone: false,
+        rowCollectionOnRequestCompletion: false,
+        useColumnNames: false,
+        appName: 'node-mssql'
+      },
+      authentication: {
+        type: 'azure-active-directory-password',
+        options: {
+          userName: 'username',
+          password: 'password',
+          clientId: 'clientid',
+          tenantId: 'tenantid'
+        }
+      }
+    })
+  })
+
+  it('parses active directory integrated token authentication', () => {
+    const baseConfig = BasePool._parseConnectionString('Server=*.database.windows.net;Database=test;Authentication=Active Directory Integrated;token=token;Encrypt=true')
+    const config = new ConnectionPool(baseConfig)._config()
+    assert.deepEqual(config, {
+      server: '*.database.windows.net',
+      options: {
+        encrypt: true,
+        trustServerCertificate: false,
+        database: 'test',
+        port: 1433,
+        connectTimeout: 15000,
+        requestTimeout: 15000,
+        tdsVersion: '7_4',
+        rowCollectionOnDone: false,
+        rowCollectionOnRequestCompletion: false,
+        useColumnNames: false,
+        appName: 'node-mssql'
+      },
+      authentication: {
+        type: 'azure-active-directory-access-token',
+        options: {
+          token: 'token'
+        }
+      }
+    })
+  })
+
+  it('parses active directory integrated client secret authentication', () => {
+    const baseConfig = BasePool._parseConnectionString('Server=*.database.windows.net;Database=test;Authentication=Active Directory Integrated;Client secret=clientsecret;Client Id=clientid;Tenant Id=tenantid;Encrypt=true')
+    const config = new ConnectionPool(baseConfig)._config()
+    assert.deepEqual(config, {
+      server: '*.database.windows.net',
+      options: {
+        encrypt: true,
+        trustServerCertificate: false,
+        database: 'test',
+        port: 1433,
+        connectTimeout: 15000,
+        requestTimeout: 15000,
+        tdsVersion: '7_4',
+        rowCollectionOnDone: false,
+        rowCollectionOnRequestCompletion: false,
+        useColumnNames: false,
+        appName: 'node-mssql'
+      },
+      authentication: {
+        type: 'azure-active-directory-service-principal-secret',
+        options: {
+          clientId: 'clientid',
+          clientSecret: 'clientsecret',
+          tenantId: 'tenantid'
+        }
+      }
+    })
+  })
+
+  it('parses active directory integrated managed service identity app vm', () => {
+    const baseConfig = BasePool._parseConnectionString('Server=*.database.windows.net;Database=test;Authentication=Active Directory Integrated;msi endpoint=msiendpoint;Client Id=clientid;Encrypt=true')
+    const config = new ConnectionPool(baseConfig)._config()
+    assert.deepEqual(config, {
+      server: '*.database.windows.net',
+      options: {
+        encrypt: true,
+        trustServerCertificate: false,
+        database: 'test',
+        port: 1433,
+        connectTimeout: 15000,
+        requestTimeout: 15000,
+        tdsVersion: '7_4',
+        rowCollectionOnDone: false,
+        rowCollectionOnRequestCompletion: false,
+        useColumnNames: false,
+        appName: 'node-mssql'
+      },
+      authentication: {
+        type: 'azure-active-directory-msi-vm',
+        options: {
+          clientId: 'clientid',
+          msiEndpoint: 'msiendpoint'
+        }
+      }
+    })
+  })
+
+  it('parses active directory integrated managed service identity app service', () => {
+    const baseConfig = BasePool._parseConnectionString('Server=*.database.windows.net;Database=test;Authentication=Active Directory Integrated;msi endpoint=msiendpoint;Client Id=clientid;msi secret=msisecret;Encrypt=true')
+    const config = new ConnectionPool(baseConfig)._config()
+    assert.deepEqual(config, {
+      server: '*.database.windows.net',
+      options: {
+        encrypt: true,
+        trustServerCertificate: false,
+        database: 'test',
+        port: 1433,
+        connectTimeout: 15000,
+        requestTimeout: 15000,
+        tdsVersion: '7_4',
+        rowCollectionOnDone: false,
+        rowCollectionOnRequestCompletion: false,
+        useColumnNames: false,
+        appName: 'node-mssql'
+      },
+      authentication: {
+        type: 'azure-active-directory-msi-app-service',
+        options: {
+          clientId: 'clientid',
+          msiEndpoint: 'msiendpoint',
+          msiSecret: 'msisecret'
+        }
+      }
+    })
+  })
+})