You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Added per user connection pooling for OBO. (#3153)
## Why make this change?
- Implements per-user connection pooling for On-Behalf-Of (OBO)
authentication mode.
- When OBO is enabled, each user gets their own isolated SQL connection
pool, ensuring that connections with user-specific access tokens are not
shared across users.
- This is critical for row-level security scenarios where the database
uses the user's identity for authorization decisions.
## What is this change?
- **Automatic per-user pooling for OBO**: When `user-delegated-auth` is
enabled, the connection pool is automatically partitioned by user
identity. No additional configuration is required.
- **Pool isolation via Application Name**: Each user's pool is
identified by modifying the SQL Server `Application Name` connection
string property to include a user-specific hash:
`{baseAppName}|obo:{hash}`
- **Hash generation**: The hash is derived from the user's JWT claims
(`iss` + `oid` or `sub`) using SHA512, then encoded as URL-safe Base64.
- **Startup/metadata compatibility**: When no user context is present
(e.g., during startup metadata introspection), the base connection
string is used without the OBO suffix.
### Key implementation details:
- `_dataSourceBaseAppName` dictionary stores the base Application Name
for OBO-enabled data sources
- `GetUserPoolKeyHash()` extracts user claims and generates
deterministic hash
- `GetConnectionStringForCurrentUser()` appends user hash to create
pool-isolated connection strings
## How was this tested?
- [x] Integration Tests - E2E testing with Azure SQL and multiple users
verified different pool hashes
- [x] Unit Tests - 3 new comprehensive unit tests added:
- `TestOboWithUserClaims_ConnectionStringHasUserSpecificAppName` -
Verifies connection string contains `|obo:` with hash when user claims
present
- `TestObo_DifferentUsersGetDifferentPoolHashes` - Verifies different
users get different Application Names
- `TestOboNoUserContext_UsesBaseConnectionString` - Verifies startup
scenario uses base connection string without OBO suffix
### Manual E2E Testing
**Environment Setup:**
- Azure SQL Database with OBO authentication enabled
- DAB deployed to Azure Container Apps with Entra ID authentication
- Two test users with different Entra ID accounts
**Test Scenario 1: Single User Connection Pool Isolation**
1. Authenticated as User A and made multiple REST requests
2. Verified via Azure SQL `sys.dm_exec_sessions` that all connections
had the same Application Name with user-specific hash
3. Confirmed connection pooling was working (connection reuse observed)
**Test Scenario 2: Multi-User Pool Isolation**
1. Authenticated as User A, made requests → observed Application Name:
`DAB,dab_oss_2.0.0|obo:W8L-UzlymZJcRbqAoTRBBcDDvHBjKoEP1lo94PGpcp0`
2. Authenticated as User B, made requests → observed Application Name:
`DAB,dab_oss_2.0.0|obo:Xk9mPqRsT2vWxYz1AbCdEfGhIjKlMnOpQrStUvWx`
3. Verified both users had **different** hashes, confirming pool
isolation
4. Re-authenticated as User A → verified **same** hash as before,
confirming deterministic hashing
**Test Scenario 3: Startup Without User Context**
1. Restarted DAB service
2. Verified startup metadata introspection succeeded (no user context
required)
3. Confirmed base Application Name used during startup:
`DAB,dab_oss_2.0.0` (no `|obo:` suffix)
**Verification Query (run on Azure SQL):**
```sql
SELECT
session_id,
login_name,
program_name AS application_name,
login_time
FROM sys.dm_exec_sessions
WHERE program_name LIKE '%dab%'
ORDER BY login_time DESC;```
---------
Co-authored-by: Copilot <[email protected]>
@@ -114,9 +123,11 @@ public override SqlConnection CreateConnection(string dataSourceName)
114
123
thrownewDataApiBuilderException("Query execution failed. Could not find datasource to execute query against",HttpStatusCode.BadRequest,DataApiBuilderException.SubStatusCodes.DataSourceNotFound);
// Extract issuer claim - required for tenant isolation and connection pool security.
243
+
// The "iss" claim must be present along with a user identifier (oid/sub) for per-user pooling.
244
+
// Callers are responsible for enforcing fail-safe behavior when claims are missing.
245
+
string?iss=user.FindFirst("iss")?.Value;
246
+
247
+
// User identifier claim resolution (in priority order):
248
+
// 1. "oid" - Short claim name for object ID, used in Entra ID v2.0 tokens
249
+
// 2. Full URI form - "http://schemas.microsoft.com/identity/claims/objectidentifier"
250
+
// Used in Entra ID v1.0 tokens and some SAML-based flows
251
+
// 3. "sub" - Subject claim, unique per user per application. Used as fallback for
252
+
// guest/B2B users where oid may not be present or stable across tenants
253
+
// 4. ClaimTypes.NameIdentifier - .NET standard claim type (maps to various underlying claims)
254
+
// Acts as a last-resort fallback for non-Entra identity providers
0 commit comments