Skip to main content

Dex IdP custom token claims from Postgres

· 2 min read
Moazzem Hossen
building edge - an open-source, cloud-native, enterprise-ready Postgres backend

Consider a multi-tenant SaaS app. After login, your app still needs to know:

  • which org is this user in
  • what's their role
  • are they on a paid plan
  • ... ...

And users may authenticate through different connectors (password, LDAP, external IdP) - the token needs consistent user context regardless.

This post covers a Dex fork that enriches issued tokens with custom claims - statically or from a Postgres query.

Checkout the changes at github.com/edgeflare/dex. Or use the image right-away ghcr.io/edgeflare/dex with the environment variables.

How it works

A CustomClaims map[string]any field is added to storage.Claims. Enrichers write into it; the token pipeline reads it out. Any type implementing this interface participates in token issuance:

type ClaimsEnricher interface {
Enrich(ctx context.Context, claims *storage.Claims) error
}

Multiple enrichers can be chained with NewClaimsEnricherChain - later enrichers overwrite earlier ones on key conflicts. Errors abort the chain and block token issuance entirely.

Static enricher

Set DEX_CUSTOM_CLAIMS_STATIC to a JSON object:

DEX_CUSTOM_CLAIMS_STATIC='{"policy": {"pgrole": "authn"}}'

Postgres enricher

Configured via environment variables. Each column in the result row becomes a claim.

VariableDescriptionExample
DEX_CUSTOM_CLAIMS_POSTGRES_URIlibpq connection uripostgres://dex:secret@pg:5432/users?sslmode=require
DEX_CUSTOM_CLAIMS_POSTGRES_QUERYSQL returning one rowSELECT role, org, plan FROM users WHERE user_id = $1 AND email = $2 LIMIT 1
DEX_CUSTOM_CLAIMS_POSTGRES_QUERY_ARGSJSON array of Go templates["{{.UserID}}","{{.Email}}"]

Available template fields (used in QUERY_ARGS):

{{.UserID}} {{.Email}} {{.Username}} {{.PreferredUsername}}

The resulting token includes the query columns alongside standard claims:

{
"iss": "https://dex.example.com",
"sub": "Cg0xMjM0NTY3ODkwEgVsb2NhbA",
"email": "[email protected]",
"email_verified": true,
"role": "owner",
"org": "acme",
"plan": "enterprise"
}

Reserved claims (iss, sub, aud, etc.) are always protected - enrichers cannot overwrite them.