Tuesday, June 9, 2026

Connecting to Azure DocumentDB (with MongoDB compatibility) using Microsoft Entra ID Managed Identity

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:
# 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
If you prefer Portal, Step 1 and 2 are basically these.
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

No comments:

Post a Comment