In this post, let's have a look at how we can connect a
.NET application to
Azure DocumentDB (with MongoDB compatibility) using a
Microsoft Entra ID Managed Identity instead of a password in the
connection string. Idea is Azure hands the identity a token at runtime, the
driver passes it to the cluster, and the cluster validates it against
Microsoft Entra ID. For local development,
DefaultAzureCredential falls back to your local identity as in any
other services.
What is Azure DocumentDB (with MongoDB compatibility)?
If the name is new to you: Azure DocumentDB (with MongoDB compatibility) is
the rebrand of what you may know as Azure Cosmos DB for MongoDB (vCore). It's
a fully managed, MongoDB-compatible document database, around
99% compatible via the MongoDB wire protocol and BSON. So our existing
MongoDB drivers and tools (the C# driver, mongosh, Compass, Studio 3T) will
work against it as-is.
Prerequisites
A few things to have in place before we start:
- An Azure Cosmos DB for MongoDB (vCore) cluster on a paid tier (M10 or higher). Entra auth is not available on Free.
- A user-assigned managed identity assigned to your app (App Service, Container Apps, AKS, etc.).
- For local development, Azure CLI, Azure Developer CLI etc logged in to the right tenant.
Step 1: Enable Entra auth on the cluster
First, we need to enable Microsoft Entra ID as an allowed auth mode on
the cluster.
This currently lives behind a preview API surface, so we need
--latest-include-preview.
# Set these once for the session. $RESOURCE_GROUP = "<resource-group>" $CLUSTER = "<cluster-name>" $LOCATION = "<region>" # Enable Entra ID auth (keep NativeAuth). '{"authConfig":{"allowedModes":["MicrosoftEntraID","NativeAuth"]}}'|
Set-Content authConfig.json -Encoding ascii -NoNewline az resource patch ` --resource-group $RESOURCE_GROUP ` --name $CLUSTER ` --resource-type Microsoft.DocumentDB/mongoClusters ` --properties "@authConfig.json" ` --latest-include-preview
Step 2: Register the principal as a cluster user
Now he cluster needs to know which principals are allowed in and what they can
do. We register each principal (by its object id) as a cluster user
with a role on a database.
For the managed identity, register it as a
ServicePrincipal.
# App's managed identity, registered as a ServicePrincipal. $PRINCIPAL_ID = az identity show ` --resource-group $RESOURCE_GROUP ` --name <identity-name> ` --query principalId ` --output tsv '{"identityProvider":{"type":"MicrosoftEntraID","properties":{"principalType":"ServicePrincipal"}},"roles":[{"db":"admin","role":"root"}]}' | Set-Content user.json -Encoding ascii -NoNewline az resource create ` --resource-group $RESOURCE_GROUP ` --name "$CLUSTER/users/$PRINCIPAL_ID" ` --resource-type Microsoft.DocumentDB/mongoClusters/users ` --location $LOCATION ` --properties "@user.json" ` --latest-include-preview
A couple of notes:
- principalType is either ServicePrincipal or User. A managed identity is registered as a ServicePrincipal and User for your own account during local development.
- vCore currently only allows the cluster root role on the admin database for Entra principals, there is no per-database readWrite option, so Entra access is effectively cluster-admin.
For local development, register your own Entra user the same way, object id
from your signed-in CLI session, and the type is User:
If you prefer Portal, Step 1 and 2 are basically these.
# Your own user (local dev). $PRINCIPAL_ID = az ad signed-in-user show ` --query id ` --output tsv '{"identityProvider":{"type":"MicrosoftEntraID","properties":{"principalType":"User"}},"roles":[{"db":"admin","role":"root"}]}' | Set-Content user.json -Encoding ascii -NoNewline az resource create ` --resource-group $RESOURCE_GROUP ` --name "$CLUSTER/users/$PRINCIPAL_ID" ` --resource-type Microsoft.DocumentDB/mongoClusters/users ` --location $LOCATION ` --properties "@user.json" ` --latest-include-preview
|
|
| Authentication |
Step 3: The .NET code
Now the code. We need a tiny shim that fetches an Entra access token and feeds it to the driver. The driver exposes an IOidcCallback interface (from the MongoDB.Driver.Authentication.Oidc namespace), and we back it with an Azure.Core.TokenCredential.
The two facts that are easy to get wrong:
- The token scope is https://ossrdbms-aad.database.windows.net/.default.
- For a guest or local user, you typically need to pin the tenantId in the TokenRequestContext. A managed identity ignores it, so leaving it null is fine in Azure.
Here's the callback. It implements both the sync and async variants,
returning an OidcAccessToken with the token and its remaining
lifetime:
using Azure.Core; using MongoDB.Driver.Authentication.Oidc; internal sealed class EntraOidcCallback(TokenCredential credential, string? tenantId) : IOidcCallback { // vCore validates a token issued for the Azure OSS RDBMS resource. private static readonly string[] _scopes = ["https://ossrdbms-aad.database.windows.net/.default"]; public OidcAccessToken GetOidcAccessToken(OidcCallbackParameters parameters, CancellationToken cancellationToken) { TokenRequestContext tokenRequestContext = BuildContext(); AccessToken accessToken = credential.GetToken(tokenRequestContext, cancellationToken); return ToOidcAccessToken(accessToken); } public async Task<OidcAccessToken> GetOidcAccessTokenAsync(OidcCallbackParameters parameters, CancellationToken cancellationToken) { TokenRequestContext tokenRequestContext = BuildContext(); AccessToken accessToken = await credential.GetTokenAsync(tokenRequestContext, cancellationToken); return ToOidcAccessToken(accessToken); } private static OidcAccessToken ToOidcAccessToken(AccessToken accessToken) => new(accessToken.Token, accessToken.ExpiresOn - DateTimeOffset.UtcNow);
private TokenRequestContext BuildContext() { // A guest/local user needs the home tenant pinned.
// A managed identity ignores it. if (string.IsNullOrEmpty(tenantId)) { return new TokenRequestContext(_scopes);
} return new TokenRequestContext(_scopes, tenantId: tenantId);
} }
And now let's build the MongoClient.
using Azure.Identity; using MongoDB.Driver; string host = "<HOST_NAME>.mongocluster.cosmos.azure.com"; string tenantId = "<TENANT_ID>"; MongoClientSettings settings = MongoClientSettings.FromUrl(MongoUrl.Create($"mongodb+srv://{host}/")); settings.UseTls = true; settings.RetryWrites = false; settings.MaxConnectionIdleTime = TimeSpan.FromMinutes(2); TokenCredential credential = new DefaultAzureCredential(); settings.Credential = MongoCredential.CreateOidcCredential(new EntraOidcCallback(credential, tenantId)); MongoClient mongoClient = new(settings); // Omitted for brevity
Hope this helps.
Happy Coding.
Regards,
Jaliya