Azure Database for PostgreSQL with Entra ID
Azure Database for PostgreSQL flexible server can authenticate database connections with Microsoft Entra ID (formerly Azure AD) instead of a stored password. In that model there is no long-lived password at all: the "password" is an Entra access token that expires roughly every 60 to 90 minutes and has to be re-acquired for new connections.
A static connection string cannot do that — opts.Connection("Host=...;Password=...") bakes the credential in once at bootstrapping time. The supported way to plug a rotating credential into Marten is to build an NpgsqlDataSource yourself with Npgsql's UsePeriodicPasswordProvider(), then hand that data source to Marten through UseNpgsqlDataSource() or opts.Connection(NpgsqlDataSource) (see Bootstrapping Marten). The data source owns connection pooling and token refresh, and Marten simply opens connections from it.
Azure prerequisites
- An Azure Database for PostgreSQL flexible server with Entra ID authentication enabled.
- A managed identity (or service principal) that has been granted a PostgreSQL role on the server via the
pgaadauthfunctions, e.g.SELECT * FROM pgaadauth_create_principal('my-app-identity', false, false);run by an Entra administrator. - A connection string whose
Usernameis that role name — and noPasswordentry.
Configuring the data source
Use Azure.Identity to acquire tokens and feed them to Npgsql as the password. The token audience (scope) for Azure Database for PostgreSQL is https://ossrdbms-aad.database.windows.net/.default:
using Azure.Core;
using Azure.Identity;
using Npgsql;
// Token audience for Azure Database for PostgreSQL
const string TokenScope = "https://ossrdbms-aad.database.windows.net/.default";
var credential = new DefaultAzureCredential(
new DefaultAzureCredentialOptions { ManagedIdentityClientId = managedIdentityClientId });
// Username = the PostgreSQL role granted to the identity; no Password in the string
var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionString);
dataSourceBuilder.UsePeriodicPasswordProvider(
async (_, ct) =>
(await credential.GetTokenAsync(new TokenRequestContext([TokenScope]), ct)).Token,
// Entra tokens live ~60-90 minutes, so refresh comfortably inside that window
successRefreshInterval: TimeSpan.FromMinutes(50),
failureRefreshInterval: TimeSpan.FromSeconds(5));
builder.Services.AddSingleton(dataSourceBuilder.Build());
builder.Services.AddMarten(opts =>
{
// All the normal Marten configuration, but *no* opts.Connection() call --
// the connection comes from the registered NpgsqlDataSource
opts.DatabaseSchemaName = "myapp";
})
.UseLightweightSessions()
.UseNpgsqlDataSource();Npgsql calls the password provider on the schedule you give it and stamps the current token onto new physical connections, so pooled connections keep working as tokens roll over. ManagedIdentityClientId is only needed when the host has more than one user-assigned identity; DefaultAzureCredential also lets the same code run locally against your az login or Visual Studio credentials.
Why register the data source in the container?
You could pass the built data source directly to opts.Connection(dataSource) and skip the container registration. Registering it as a singleton and using UseNpgsqlDataSource() is the better default though, because everything that builds your IHost gets the token provider for free — including the command line tooling. dotnet run -- db-apply, db-assert, and resources setup all bootstrap the same host, so schema management against an Entra-only server works without any extra wiring.
Sharing the data source with ancillary stores
UseNpgsqlDataSource() applies to the default store from AddMarten(). Ancillary stores registered with AddMartenStore<T>() can share the exact same data source instance — same pool, same token provider:
var dataSource = dataSourceBuilder.Build();
builder.Services.AddSingleton(dataSource);
builder.Services.AddMarten(opts => { /* main store configuration */ })
.UseLightweightSessions()
.UseNpgsqlDataSource();
builder.Services.AddMartenStore<IInvoicingStore>(opts =>
{
opts.Connection(dataSource);
opts.DatabaseSchemaName = "invoicing";
});Data source ownership
Marten treats a caller-supplied NpgsqlDataSource — whether it arrives through UseNpgsqlDataSource() or opts.Connection(NpgsqlDataSource) — as owned by the caller. Marten will never dispose it, so a single instance is safe to share across the main store, ancillary stores, and your own non-Marten usage. Dispose it yourself when the application shuts down (the container does this for you when the data source is registered as a singleton).
Database provisioning with a rotating credential
Marten's tenant database provisioning (StoreOptions.CreateDatabasesForTenants, see database management) opens a separate maintenance connection to create or drop tenant databases. By default that maintenance connection is built from a connection string, which — just like opts.Connection(string) — cannot carry a rotating credential. On a server that only accepts Entra ID authentication, provisioning would then fail to log in.
Supply the maintenance connection as an NpgsqlDataSource instead. The MaintenanceDatabase() option has an overload that takes a caller-owned data source, so the same periodic password provider that authenticates your store connections also authenticates provisioning:
// Hand provisioning a caller-owned NpgsqlDataSource carrying, for example, an Entra ID
// token provider. Point it at an administrative database ('postgres') whose principal
// holds the CREATEDB privilege. Marten never disposes this data source.
c.MaintenanceDatabase(maintenanceDataSource);Point that data source at an administrative database (typically postgres) whose Entra principal holds the CREATEDB privilege — this is frequently a different, more privileged identity than the one your application uses at runtime, which is exactly why the maintenance data source is configured separately from the store connection. As with every caller-supplied NpgsqlDataSource, Marten never disposes it; dispose it yourself when the application shuts down.

