Skip to content
rw3iss Auth

Provider identity

Before a relying party can render a “Sign in with {Provider}” button, it needs three things this server alone knows: what the provider is called, what it looks like, and which sign-in methods are actually wired up here.

That is one public document.

Terminal window
curl -s https://auth.civicgate.org/api/v1/provider
{
"slug": "civic-gate",
"name": "CivicGate",
"shortName": "CivicGate",
"iconUrl": "https://www.civicgate.org/icon-192.png",
"backgroundColor": "#0f1115",
"color": "#e6e6ea",
"issuer": "https://auth.civicgate.org",
"methods": ["password", "oidc", "fedcm"]
}

Access

PathGET /api/v1/provider
AliasGET /.well-known/auth-provider — identical response
AuthNone. Public.
CORSAccess-Control-Allow-Origin: *
CacheCache-Control: public, max-age=300

Both paths serve the same handler; use whichever suits the caller. The .well-known alias is the one to reach for when you only know a provider’s origin.

It is deliberately credential-free: * CORS with no cookie involved, so any page anywhere can read it to build a button, and there is nothing in it worth protecting.

Fields

FieldTypeAlways presentWhat it is for
slugstringyesStable machine id. Use it as a config key / CSS hook — never as display text.
namestringyesFull display name. "Sign in with {name}".
shortNamestringomitted when unsetCompact label for narrow buttons and mobile. Fall back to name.
iconUrlstringomitted when unsetLogo. Also the FedCM chooser icon: Chrome wants ≥25px passive / ≥40px button mode.
backgroundColorstringomitted when unsetButton/chooser background.
colorstringomitted when unsetButton/chooser foreground.
issuerstringomitted when unsetThe OIDC issuer origin. Everything else — discovery, JWKS, the FedCM config URL — is derived from it.
methodsstring[]yesWhich sign-in paths are live on this deployment.

Note the casing: this document is camelCase (shortName, iconUrl, backgroundColor), unlike the snake_case OIDC payloads elsewhere. It is a UI document, not an OAuth one.

Fields that are unset are omitted, not returned empty. Code against ?. and supply your own fallbacks.

methods — render only what will work

The whole reason this endpoint exists. methods reports what is actually wired on this deployment, so a client renders only buttons that will work:

ValuePresent whenThe RP should
passwordalwaysOffer a credentials form — first-party apps only.
oidcA usable OIDC signing key + client store existOffer the redirect/popup flow.
fedcmSame conditionTry navigator.credentials.get() first, with a fallback.
const p = await fetch(`${AUTH_ORIGIN}/api/v1/provider`).then(r => r.json());
if (p.methods.includes('fedcm')) { /* try the browser API first */ }
if (p.methods.includes('oidc')) { /* the universal fallback */ }

Two honest caveats:

  • oidc and fedcm currently move together. Both are gated by the same condition — a usable signing key plus a client store — so today you will see either ["password"] or ["password","oidc","fedcm"]. Test for each one anyway; the array is the contract, the coupling is an implementation detail.
  • fedcm in methods does not mean the browser supports it. It means the server offers it. Chrome and Edge support FedCM; Safari and Firefox do not fully. The RP still feature-detects ('IdentityCredential' in window) and still needs the popup fallback. See FedCM → the limits.

One provider per installation

This server is the provider. There is no per-tenant provider switch and no directory of providers to choose from — the document describes this deployment. Two providers means two installations.

That is why an unconfigured deployment does not guess:

{ "slug": "auth", "name": "Auth Server",
"issuer": "https://…", "methods": ["password"] }

Generic and unbranded on purpose. An unconfigured deployment should look unconfigured, rather than silently wearing someone else’s name.

Configuration

Set these in the auth-server’s environment. Each falls back to the older FEDCM_BRAND_* variable — those were the FedCM chooser’s branding before the provider document existed and are still honoured — and then to a generic default.

VariableFalls back toFinal default
AUTH_PROVIDER_NAMEFEDCM_BRAND_NAME"Auth Server"
AUTH_PROVIDER_SLUGderived by slugifying the resolved name"auth"
AUTH_PROVIDER_SHORT_NAME—(omitted)
AUTH_PROVIDER_ICON_URLFEDCM_BRAND_ICON_URL(omitted)
AUTH_PROVIDER_BACKGROUNDFEDCM_BRAND_BACKGROUND(omitted)
AUTH_PROVIDER_COLORFEDCM_BRAND_COLOR(omitted)
issuer— from OIDC_ISSUER—
methods— computed, not configurable["password"]

Slugification lowercases, keeps [a-z0-9], collapses every other run of characters to a single -, and trims leading/trailing dashes. So "Test Organization Name" → test-organization-name.

Worked example

A deployment for an organisation called Test Organization Name, displayed compactly as TestOrgName:

Terminal window
# Identity of this provider
AUTH_PROVIDER_SLUG=testorgname
AUTH_PROVIDER_NAME="Test Organization Name"
AUTH_PROVIDER_SHORT_NAME=TestOrgName
AUTH_PROVIDER_ICON_URL=https://www.testorgname.com/icon-192.png
AUTH_PROVIDER_BACKGROUND=#101418
AUTH_PROVIDER_COLOR=#f2f4f8
# Federation endpoints — set these too, or you inherit CivicGate's
OIDC_ISSUER=https://auth.testorgname.com
OIDC_LOGIN_URL=https://www.testorgname.com/login

AUTH_PROVIDER_SLUG is set explicitly here rather than left to the slugifier: the derived value would have been the long test-organization-name, and the slug is the key everything else keys off. Set it once, deliberately, and it never drifts.

Result:

{
"slug": "testorgname",
"name": "Test Organization Name",
"shortName": "TestOrgName",
"iconUrl": "https://www.testorgname.com/icon-192.png",
"backgroundColor": "#101418",
"color": "#f2f4f8",
"issuer": "https://auth.testorgname.com",
"methods": ["password", "oidc", "fedcm"]
}

Which renders as “Sign in with Test Organization Name” on a wide button and “Sign in with TestOrgName” on a narrow one — from one fetch, with no provider name hard-coded in the RP.

Verify a deployment

Terminal window
curl -s https://auth.testorgname.com/api/v1/provider | jq
curl -s https://auth.testorgname.com/.well-known/auth-provider | jq # identical
# Headers: expect access-control-allow-origin: * and max-age=300
curl -sI https://auth.testorgname.com/api/v1/provider \
| grep -iE 'access-control|cache-control'

Check three things: name is yours (not the built-in default), issuer points at this host, and methods lists what you expect.