Skip to main content

How the OpenID Connect Provider Works

@nauth-toolkit/oidc-provider turns your nauth-toolkit application into an OAuth 2.0 authorization server and OpenID Connect provider, so other applications can offer "Sign in with your app".

The protocol itself is implemented by oidc-provider — OpenID Certified, MIT licensed. nauth-toolkit owns no protocol code. It supplies three things and nothing else: storage, accounts, and a login and consent bridge.

Sample apps

The provider is wired up end to end in the nauth-toolkit repository — see examples/demo-nestjs for the backend and examples/demo-angular for the consent screen and a third-party relying party built on a certified client library.

When to use this — and when not to​

This is for letting other applications sign users in with accounts they already hold here: "Sign in with your app", a partner integration, a first-party mobile or desktop client that wants standard OAuth rather than the nauth-toolkit SDK.

Not a replacement for your own login

Do not route your own application's sign-in through the OpenID Connect provider. Use AuthService and the frontend SDK for that, as you already do.

The protocol endpoints do not carry nauth-toolkit's controls:

nauth-toolkit's own loginThe provider's protocol endpoints
Rate limitingPer-identifier and per-IP, per operation, with account lockout — see Rate LimitingA coarse per-endpoint limiter you mount yourself (createOIDCRateLimiter); no lockout, no per-account counters
Audit trailEvery event type in AuthAuditEventType — attempts, failures, MFA, sessions, devicesThe four OIDC_* events below. Token issuance, refresh, introspection and revocation are not recorded
Adaptive risk, device trust, lockoutFullNot applied to the protocol endpoints
HooksFull lifecycle hooksNot invoked by the protocol endpoints

The login inside the flow is a different matter — that is nauth-toolkit's own login, and it keeps everything: the challenge chain, adaptive MFA, lockout, rate limits and audit records all apply, exactly as they would if the user had signed in directly. What is missing is coverage of the OAuth surface around it.

What you keep​

Your existing login is unchanged. When the provider needs a user, it redirects to your own application, where the full challenge chain runs — forced password change, email and phone verification, MFA setup and verification, adaptive risk — and only then is the login reported as complete. You do not modify a challenge component or special-case a route.

The flow​

Note the two interaction ids. The login step and the consent step are separate interactions, so a consent screen must tolerate being entered under an id it has not seen before.

The pieces​

PieceWhat it does
createNAuthOIDCProvider()Builds the provider on nauth-toolkit's storage and users
mountOIDCProviderNest() / mountOIDCProviderExpress()Attaches it to the platform instance, before your body parsers
OIDCInteractionBridgeDrives the login and consent steps from your own routes
createOIDCInteractionController()The four NestJS interaction routes, registered for you
OIDCSessionTerminatorEnds the provider's own SSO sessions and grants when a user logs out
createOIDCRateLimiter()Rate limits the provider's endpoints, which your own limiter does not reach

What the provider's endpoints bypass​

They are attached to the platform instance, not to your framework's router, so guards, interceptors, pipes and filters never see them. Two consequences you have to handle: rate limiting is yours to mount (createOIDCRateLimiter), and with NestJS setGlobalPrefix('api') does not apply to them.

The interaction routes are the opposite — ordinary routes inside your guard chain, because the session check reads the current user from request context.

Session verdicts​

Before an authorization request is released, the account is re-read from the database rather than trusted from the access token, so a user disabled, locked, or newly required to change their password mid-session cannot have an id_token minted for a third party. The verdict arrives on the consent screen as gate:

VerdictMeaningWhat your page does
authenticatedA completed login stands behind the requestContinue
login_requiredRecoverable — no session, forced password change, unverified emailStash the interaction id, send the user through login, return
deniedAccount disabled, locked, or goneResolve the interaction with access_denied so the relying party is told

Audit events​

The consent flow writes to your ordinary audit trail, with the relying party attached, unless you set auditLogs.enabled: false. Client info (IP, device, user agent) is filled in as it is for any other event.

EventWhenMetadata
OIDC_ACCESS_DENIEDThe gate refused the accountclientId, interactionUid, requestedScopes, reason
OIDC_CONSENT_DENIEDThe user refused the requestclientId, interactionUid, requestedScopes
OIDC_CONSENT_GRANTEDThe user approvedclientId, interactionUid, requestedScopes, grantedScopes
OIDC_LOGIN_COMPLETEDA login was released to a relying partyclientId, interactionUid, requestedScopes

Query them like any other event through AuthAuditService.

Hardened away from upstream defaults​

oidc-provider defaultHere
Response typescode, id_token, code id_token, nonecode only — no implicit or hybrid flow
PKCERequired for public clientsRequired for every client (RFC 9700)
Introspection / revocationDisabledEnabled, and a client may reach only its own tokens
Refresh tokensRequire the offline_access scopeIssued whenever the client registers the grant
Artifact lifetimesWarn-on-defaultSet explicitly
Built-in dev login screensEnabledDisabled — nauth-toolkit owns login

Storage​

Every provider artifact — sessions, grants, authorization codes, access and refresh tokens, interactions — lives in your existing StorageAdapter. No new tables and no migrations.

Use Redis in production. The database adapter is fine for low traffic; the in-memory adapter is single-instance only and loses every session on restart.

Default artifact lifetimes​

ArtifactTTL
AuthorizationCode60 seconds
AccessToken1 hour
IdToken1 hour
Interaction15 minutes
RefreshToken30 days
Session14 days
Grant14 days

Override any of them through extraConfiguration.

What's Next​