Skip to content

Comments

Added per user connection pooling for OBO.#3153

Open
anushakolan wants to merge 1 commit intodev/anushakolan/obo-delegated-identityfrom
dev/anushakolan/per-user-connection-pooling
Open

Added per user connection pooling for OBO.#3153
anushakolan wants to merge 1 commit intodev/anushakolan/obo-delegated-identityfrom
dev/anushakolan/per-user-connection-pooling

Conversation

@anushakolan
Copy link
Contributor

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 SHA256, 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?

  • Integration Tests - E2E testing with Azure SQL and multiple users verified different pool hashes
  • 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):

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;```

@anushakolan
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines could not run because the pipeline triggers exclude this branch/path.

@anushakolan anushakolan linked an issue Feb 24, 2026 that may be closed by this pull request
return baseConnectionString;
}

// Extract user pool key from current HTTP context using iss:sub
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update the comment to match the real behavior so the next reader doesn’t get confused. the comment says “iss:sub”, but GetUserPoolKeyHash actually prefers oid then falls back to sub.

ClaimsPrincipal user = HttpContextAccessor.HttpContext.User;

// Extract issuer claim - required for tenant isolation
string? iss = user.FindFirst("iss")?.Value;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some identity setups may not emit a literal "iss" claim in the principal (even if the token had an issuer). You already do good fallbacks for oid/sub; you could do similar for issuer if there’s an established claim type you use elsewhere in the repo. add a fallback source for issuer (if you can), or at least mention in a comment that "iss" must be present for pooling to activate.

if (string.IsNullOrEmpty(iss) || string.IsNullOrEmpty(userKey))
{
// Cannot create a pool key without both claims
QueryExecutorLogger.LogDebug("Cannot create per-user pool key: missing iss or user identifier claim.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe include datasource name (still safe) to help debugging. Remember to use structured logs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[OBO] Per‑User (Identity‑Scoped) SQL Connection Pooling

2 participants