Skip to main content

Registering OIDC Clients

Every application that wants to sign users in through your provider is a client. Clients are registered statically, in the clients array you pass to the provider. Keep secrets in environment variables, never in source.

A confidential client​

An application with a backend that can keep a secret.

src/config/oidc.config.ts
{
client_id: 'partner',
client_secret: process.env.PARTNER_CLIENT_SECRET!,
client_name: 'Partner App',
client_uri: 'https://myapp.com',
logo_uri: 'https://myapp.com/logo.png',
redirect_uris: ['https://myapp.com/callback'],
grant_types: ['authorization_code', 'refresh_token'],
response_types: ['code'],
token_endpoint_auth_method: 'client_secret_basic',
post_logout_redirect_uris: ['https://myapp.com'],
}

A public client​

A single-page app or a mobile app, which cannot keep a secret. Omit client_secret and set token_endpoint_auth_method: 'none'. PKCE is what binds the authorization code to the client.

src/config/oidc.config.ts
{
client_id: 'partner-spa',
client_name: 'Partner SPA',
redirect_uris: ['https://myapp.com/callback'],
post_logout_redirect_uris: ['https://myapp.com'],
grant_types: ['authorization_code', 'refresh_token'],
response_types: ['code'],
token_endpoint_auth_method: 'none',
}

Client metadata​

FieldTypeRequiredDescription
client_idstringYesPublic identifier, sent as client_id
client_namestringNoShown on the consent screen. Absent unless you set it, so have your page fall back to client_id
client_secretstringNoOmit for a public client
client_uristringNoClient home page, shown on the consent screen. Must be an absolute http(s) URL
grant_typesstring[]NoDefault ['authorization_code', 'refresh_token']
logo_uristringNoShown on the consent screen. Must be an absolute http(s) URL
post_logout_redirect_urisstring[]NoAllowed targets for RP-initiated logout
redirect_urisstring[]YesMatched as exact strings — no wildcards, no path prefixes
response_types'code'[]NoOnly ['code'] is available
token_endpoint_auth_methodstringNoclient_secret_basic (default), client_secret_post, or none

Any other metadata oidc-provider accepts can be set alongside these. See NAuthOIDCClient.

Redirect URIs are matched exactly

https://myapp.com/callback does not match https://myapp.com/callback/ or https://myapp.com/callback?x=1. Register every URI the client will actually use, including the localhost ones your partners develop against.

Scopes and claims​

The provider releases these scopes, and no others:

ScopeClaims released
openidsub
emailemail, email_verified
profilename, given_name, family_name, preferred_username, updated_at
phonephone_number, phone_number_verified
offline_accessNone — signals that the client wants long-lived access

sub is nauth-toolkit's external user identifier. An internal row id is never exposed.

A client asks for scopes in its authorization request; nothing needs registering per client. The consent screen shows the user what was asked for, and the grant records what they allowed.

PKCE is required for every client​

Not only public ones — this is stricter than the oidc-provider default. Clients must send code_challenge with code_challenge_method=S256; plain is rejected. Every certified client library does this by default.

Refresh tokens​

A refresh token is issued whenever the client registers the refresh_token grant. Unlike stock OpenID Connect, the offline_access scope is not additionally required — so a client that should not get refresh tokens must leave the grant out of grant_types.

Only the authorization code flow​

response_types accepts ['code'] and nothing else — a client configured for the implicit or hybrid flow will not work here.

Rotating a client secret​

Change client_secret in configuration and restart. There is no secret history, so coordinate the change with the client, or register a second client_id and retire the first once traffic has moved.

What's Next​