Authenticating with OAuth v7

The MCP server and the OpenTelemetry Protocol (OTLP) receiver can accept bearer tokens alongside HTTP Basic. MCP clients sign in through your identity provider (IdP) and send the token it issues. OTLP senders attach a token they already hold. The endpoint forwards each token to Postgres over the OAUTHBEARER mechanism of the Simple Authentication and Security Layer (SASL), and Postgres decides who the caller is. The endpoint never verifies a token's signature and keeps no identity between calls.

Requirements

  • PostgreSQL 18 or later. The supervisor refuses the OAuth settings on an older server, logs a warning, and doesn't start the endpoint.
  • A validator module in oauth_validator_libraries that checks the token signature, the issuer, and the scopes. AIDB doesn't ship one. pg_oidc_validator 1.1.0 is the one AIDB is tested against.
  • An oauth line in pg_hba.conf for the roles that authenticate with tokens. See Mapping identities to roles.
Note

pg_oidc_validator is third party, and its maintainers label it experimental. It doesn't check aud, so the endpoint checks it instead.

Enabling OAuth

  1. Install a validator module on the Postgres host, and list it in oauth_validator_libraries in postgresql.conf. If you use pg_oidc_validator, set pg_oidc_validator.authn_field to the claim that names the Postgres role, which is preferred_username by default:

    oauth_validator_libraries = 'pg_oidc_validator'
    pg_oidc_validator.authn_field = 'preferred_username'
  2. Create a login role for each identity, add an oauth line to pg_hba.conf, and map each IdP identity to its role in pg_ident.conf. See Mapping identities to roles for a complete example.

  3. In postgresql.conf, set the endpoint's OAuth settings. For MCP, set the issuer, resource URL, scopes, and audience together. Use the same scope as the scope= option on the pg_hba.conf line:

    edb.endpoints_mcp_oauth_issuer = 'https://idp.example.com/realms/mcp'
    edb.endpoints_mcp_oauth_resource_url = 'https://mcp.example.com/mcp'
    edb.endpoints_mcp_oauth_scopes = 'mcp'
    edb.endpoints_mcp_oauth_audience = '<audience>'

    For the OTLP server, set edb.endpoints_oauth_issuer and edb.endpoints_oauth_audience instead. See Settings for every parameter and Choosing the audience for the audience value.

  4. Reload Postgres. The reload applies the pg_hba.conf and pg_ident.conf changes and restarts the endpoint with the new settings:

    SELECT pg_reload_conf();
  5. Connect an MCP client that supports OAuth to the endpoint's URL. The client opens your IdP's sign-in page, and then tools/list returns the tools that the signed-in role can call. If the client keeps returning to the sign-in page, see MCP clients. The Postgres server log names the cause. For OTLP senders, see OTLP server.

Settings

The general settings apply to every server the endpoint process runs:

ParameterTypeDefaultContextDescription
edb.endpoints_oauth_issuerstringemptysighupIssuer URL (https://) of your IdP. Empty turns OAuth off, unless MCP has its own issuer.
edb.endpoints_oauth_scopesstringemptysighupScopes advertised to MCP clients when MCP uses the general settings. The OTLP server advertises none.
edb.endpoints_oauth_role_claimstringpreferred_usernamesighupToken claim that names the Postgres role.
edb.endpoints_oauth_audiencestringemptysighupThe exact aud value the IdP puts in tokens for these servers. See Choosing the audience.

MCP has its own set:

ParameterTypeDefaultContextDescription
edb.endpoints_mcp_oauth_issuerstringemptysighupIssuer URL (https://) for MCP. Empty uses the general settings.
edb.endpoints_mcp_oauth_resource_urlstringemptysighupThe MCP server's public https:// URL, ending in /mcp. Required with either issuer.
edb.endpoints_mcp_oauth_scopesstringemptysighupSpace-separated scopes advertised to MCP clients. Match the scope= option on the pg_hba.conf line.
edb.endpoints_mcp_oauth_role_claimstringpreferred_usernamesighupToken claim that names the Postgres role.
edb.endpoints_mcp_oauth_audiencestringemptysighupThe exact aud value the IdP puts in tokens for the MCP server.

MCP takes its whole set from the edb.endpoints_mcp_oauth_* settings when edb.endpoints_mcp_oauth_issuer is set, and from the general settings otherwise. The two sets never mix, so a general audience doesn't fill in a missing MCP one. edb.endpoints_mcp_oauth_resource_url applies in both cases.

With both issuers empty, the endpoint behaves exactly as it does without OAuth. Changing any OAuth setting restarts the endpoint at the next pg_reload_conf().

MCP clients

For MCP, the issuer, resource URL, scopes, and audience are required together. The supervisor refuses a configuration that leaves one out and names the missing setting in its warning.

  • The resource URL's path must be exactly /mcp. A reverse proxy that serves the endpoint under a subpath, such as /aidb/mcp, isn't supported.
  • Tokens must be JSON Web Tokens (JWTs), because the endpoint reads the role claim to choose the login role. An opaque access token always gets 401 with error="invalid_token".
  • A request with no usable credentials gets a 401 whose WWW-Authenticate header points at the protected-resource metadata for the path it called. MCP clients that support OAuth use it to start the sign-in.
  • An expired, malformed, or wrong-audience token gets a 401 with error="invalid_token".
  • HTTP Basic keeps working alongside bearer tokens.

Every bearer request is also tried against Postgres with a connect-only login. A token that Postgres refuses, because of a bad signature, a revoked token, or an unmapped identity, gets a 401 with error="invalid_token", so the client signs in again. Each tool call still logs in on its own connection, so a bearer request costs one extra login. If Postgres is unreachable during that check, the request goes through and the tool calls report the error.

The check can't tell a bad token from a server misconfiguration, so these problems also send the client back to the IdP sign-in:

  • A missing oauth line in pg_hba.conf.
  • A role that doesn't exist.
  • A validator failure.

The endpoint logs each refusal as a warning. Check the Postgres server log for the cause.

When the endpoint serves several databases, bare /mcp and each /mcp/<database> are separate protected resources:

PathMetadata URLresource value
/mcp/.well-known/oauth-protected-resource/mcpThe resource URL
/mcp/sales/.well-known/oauth-protected-resource/mcp/salesThe resource URL with /sales appended

Database names with characters outside A-Z a-z 0-9 - . _ ~ are percent-encoded in these URLs. One audience covers every database.

OTLP senders

The OTLP server uses the general settings and only checks tokens. OpenTelemetry exporters send a token they already hold and never run a sign-in, so the receiver serves no metadata and needs no resource URL or scopes. To authenticate OTLP senders, set the issuer and audience. For how the receiver answers and how to attach the token to an exporter, see OTLP server.

Mapping identities to roles

The endpoint logs in as the role the role claim names. Postgres then maps the identity the validator verified through pg_ident.conf to decide whether that login is allowed. Create the roles first. An OAuth login role needs no password:

-- The group role that the oauth line in pg_hba.conf matches
CREATE ROLE mcp_oauth_users NOLOGIN;
-- A login role for the IdP identity alice, which is a member of the group
CREATE ROLE agent_alice LOGIN IN ROLE mcp_oauth_users;
-- Lets the role list and call tools. See Configuring your agent role.
GRANT aidb_users TO agent_alice;

Then add the pg_hba.conf and pg_ident.conf lines:

# pg_hba.conf: the supervised endpoint connects over the Unix socket
# TYPE  DATABASE  USER                METHOD
local   all       +mcp_oauth_users    oauth issuer="https://idp.example.com/realms/mcp" scope="mcp" map=mcp_oauth
# agent_bob keeps password login through HTTP Basic
local   all       agent_bob           scram-sha-256

# pg_ident.conf: IdP identity to Postgres role
# MAPNAME     SYSTEM-USERNAME   DATABASE-USERNAME
mcp_oauth     alice             agent_alice
  • The + prefix on +mcp_oauth_users matches every member of that role. alice is the identity that the validator verifies, and agent_alice is the role the endpoint logs in as.
  • Postgres uses the first pg_hba.conf line that matches, so each role authenticates one way. A role on the oauth line can't also log in with a password through HTTP Basic.
  • Use local for the endpoint the supervisor starts, and hostssl for an edb-endpoints you launch yourself that reaches Postgres over TCP.
  • Without map=, the identity and the role must be equal. map= maps identities to roles when they differ.
  • A bearer token connection that a line other than oauth admits gets the error PostgreSQL admitted it without checking the token. See Passwords are always checked.

The role claim and the validator have to agree. pg_oidc_validator reads sub by default, so set pg_oidc_validator.authn_field = 'preferred_username' to pair it with the default role claim. Keycloak puts preferred_username in access tokens through the profile scope.

The endpoint never verifies the role claim. Point it only at a claim your IdP sets, never one that users can edit.

Warning

Don't use delegate_ident_mapping=1 with pg_oidc_validator. It authorizes on scopes alone and ignores the requested role, so any valid token could log in as any role on that pg_hba.conf line.

Warning

The endpoint passes the caller's token straight to Postgres, and its aud check guards only the HTTP path. Because pg_oidc_validator doesn't check aud, any token from the same issuer with the configured scope and a mapped identity can also log in to Postgres directly, including one issued for a different service. To keep tokens scoped to the endpoint, limit the oauth line to local or to the endpoint's address, give the endpoint a scope no other client gets, or use a validator that enforces aud.

Choosing the audience

Set the audience to the aud value your IdP actually puts in access tokens for the endpoint. A wrong value gets a 401 on every bearer request.

  • In Keycloak, the value comes from an audience mapper on the client.
  • In Microsoft Entra ID v2 tokens, it's the API's application (client) ID GUID, not the application ID URI or the resource URL. v1 tokens carry either the GUID or the application ID URI.

pg_oidc_validator 1.1.0 accepts only Entra ID v1 tokens. It rewrites any login.microsoftonline.com issuer to https://sts.windows.net/<tenant>/ before it checks iss, so a v2 token fails even with the right audience and role claim. Entra ID puts preferred_username only in v2 tokens, so with v1 tokens, set both the role claim and pg_oidc_validator.authn_field to upn. Entra ID v2 needs a validator that accepts the v2 issuer.