---
title: Register OIDC services
summary: Registers OIDC middleware/services and delegates to web authentication setup.
status: draft
owner: Framework.NCore maintainers
last_reviewed: 2026-10-02
library: SAIF.NetCore.ServiceExtensions
reference_type: package
operation_id: OP-07
trigger: AppStartup (or a consumer) calls RegisterOidcServices(type, useJwtRefreshMiddleware) during service configuration.
---

# Register OIDC services

This operation registers the full set of OIDC-related middleware and services (claim synchronization, role transformation, JWT refresh, login redirects, data-protection certificates), then delegates to [Add web authentication](add-web-authentication.md) to configure the authentication schemes. It is used by [App startup configure services](app-startup-configure-services.md) when `HostLocation == External`.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-07 |
| **Trigger** | `AppStartup.ConfigureServices` (or a consumer) calls `RegisterOidcServices(type = Cookie, useJwtRefreshMiddleware = true)`. |
| **Inputs** | `type` — the authentication challenge type (`Cookie` or `OpenIdConnect`); `useJwtRefreshMiddleware` — whether `IJwtRefreshService` is registered in the service collection (this method performs DI registration only; it does not construct or alter the HTTP request pipeline). |
| **Outputs** | OIDC middleware/services registered; web authentication schemes configured via OP-08. |
| **Primary components** | `IServiceCollectionExtensions.RegisterOidcServices` |
| **Entry point** | `src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/IServiceCollectionExtensions.cs` (L137–168) |

## Steps

1. Register Scoped `JwtClaimMiddleware`, `LowerCaseUrlMiddleware`, `RefreshJwtMiddleware` unconditionally ([BR-SE-10](../business-rules.md#br-se-10-jwt-refresh-and-lower-case-url-middleware-are-conditionally-registered))
   - All three middleware types are registered in the container regardless of the `useJwtRefreshMiddleware` flag; that flag gates only the `IJwtRefreshService` registration in step 4. `AppStartup` separately passes its `UseJwtRefreshMiddleware` property into this parameter during service configuration and reads the same property again during `Configure` to decide whether to insert `RefreshJwtMiddleware` into the pipeline (see [App startup configure pipeline](app-startup-configure-pipeline.md)). This registration method itself performs no pipeline insertion.

   > **Origin**: `JwtClaimMiddleware`'s claim-synchronization logic is implemented in [`Sync JWT claims`](../../saif-netcore-oidc/operations/op-05-sync-jwt-claims.md) and `LowerCaseUrlMiddleware`'s logic in [`Enforce URL casing`](../../saif-netcore-oidc/operations/op-13-enforce-url-casing.md) (`SAIF.NetCore.OIDC`).
2. Register Singleton `IOidcSettings → OidcSettings`. `OidcSettings`'s constructor only stores its injected `IEnvironmentClient`/`ISystemHashService` dependencies; it does not itself require or call a secure environment client, and most of its property/indexer getters read ordinary (non-secure) settings. Only the later `AppCredentials` getter needs a usable (non-null) `ISystemHashService` — see the failure section below (see [Register secure environment client](register-secure-environment-client.md) for how a secure `ISystemHashService` is registered).

   > **Origin**: Settings resolution logic is implemented in [`Resolve OIDC settings`](../../saif-netcore-oidc/operations/op-11-resolve-oidc-settings.md) (`SAIF.NetCore.OIDC`); this step only registers the `OidcSettings` type. `OidcSettings`'s constructor only stores its injected `IEnvironmentClient`/`ISystemHashService` dependencies — it performs no environment/secure-setting reads itself. Construction happens once, at first resolution of `IOidcSettings`. Each individual setting (for example `DPCertName`, `CookieName`) is instead fetched lazily, on every call to its corresponding property or indexer getter, which can happen many times after construction, not just once "at first resolution."
3. Register Scoped `IUrlCaseService → UrlCaseService`; Scoped `RoleClaimTransformer`; Scoped `IRoleClaimService → RoleClaimService`; Singleton `IClaimTransformCacheService → ClaimTransformationCacheService`; Transient `ILoginRedirectService → LoginRedirectService`.

   > **Origin**: Role-claim transformation and caching is implemented in [`Transform role claims`](../../saif-netcore-oidc/operations/op-09-transform-role-claims.md) and [`Role transform cache`](../../saif-netcore-oidc/operations/op-10-role-transform-cache.md); login redirection in [`Redirect to login`](../../saif-netcore-oidc/operations/op-08-redirect-to-login.md) (`SAIF.NetCore.OIDC`).
4. If `useJwtRefreshMiddleware`: register Scoped `IJwtRefreshService → JwtRefreshService` ([BR-SE-10](../business-rules.md#br-se-10-jwt-refresh-and-lower-case-url-middleware-are-conditionally-registered))

   > **Origin**: Token refresh logic is implemented in [`Refresh tokens`](../../saif-netcore-oidc/operations/op-12-refresh-tokens.md) (`SAIF.NetCore.OIDC`); this step only registers the `JwtRefreshService` type, and its token-refresh logic runs later, at request time.
5. Register Scoped `IJwtAuthorizationCookieClaimService → JwtAuthorizationCookieClaimService`; Singleton `IDataProtectionCertService → DataProtectionCertService`.

   > **Origin**: Certificate resolution logic is implemented in [`Resolve DP certificate`](../../saif-netcore-oidc/operations/op-07-resolve-dp-certificate.md) (`SAIF.NetCore.OIDC`); this step only registers the `DataProtectionCertService` type. Its constructor stores the injected `IOidcSettings`/`ISaifLogService` and immediately calls `IEnvironmentClient.IsDeveloperMachine()` at construction time (first resolution of `IDataProtectionCertService`) — it does not inject or resolve `ISystemHashService` directly. The actual certificate lookup is in `GetCertificate()`, which is not called during construction: it only runs when `ConfigureCookieAuthenticationOptions.Configure(name, options)` (an `IConfigureNamedOptions<CookieAuthenticationOptions>`) executes, which itself happens later, the first time the framework resolves named `CookieAuthenticationOptions` for the `"Cookies"` scheme (typically on the first request that evaluates cookie authentication), not during this registration call or during `DataProtectionCertService` construction.
6. Call `AddWebAuthentication(type)`, a same-library registration operation that is executed synchronously as part of this call ([BR-SE-11](../business-rules.md#br-se-11-web-apps-default-to-cookie-authentication-with-optional-openidconnect-challenge)): it registers `ConfigureOptions<ConfigureCookieAuthenticationOptions>()`/`ConfigureOptions<ConfigureOpenIdConnectOptions>()` (deferred `IConfigureOptions<CookieAuthenticationOptions>`/`IConfigureOptions<OpenIdConnectOptions>`, each applied later, only the first time the framework resolves that named-scheme options instance), calls `AddAuthentication(options => ...)` to set `DefaultAuthenticateScheme = DefaultSignInScheme = "Cookies"` and `DefaultChallengeScheme = "OpenIdConnect"` if `type == OpenIdConnect` else `"Cookies"`, then `.AddCookie("Cookies").AddOpenIdConnect()` to register both handlers regardless of which one is the default challenge scheme.

   > **Origin**: `AddWebAuthentication(type)`'s full step-by-step registration behavior (the exact deferred-configurator and scheme-default wiring summarized above) is documented on [Add web authentication](add-web-authentication.md) (same library, `IServiceCollectionExtensions.AddWebAuthentication`); unlike the OIDC registrations in steps 1–5, this call executes immediately, synchronously, as part of `RegisterOidcServices` itself — it is not deferred to first resolution.

## Process flow

This diagram shows the one-time DI registration sequence performed when `RegisterOidcServices(type, useJwtRefreshMiddleware)` is called during service configuration, including the synchronous `AddWebAuthentication(type)` delegation in the final step; it does not depict per-request middleware/claim-transformation behavior.

```mermaid
flowchart TD
    Start([RegisterOidcServices called]) --> S1[Register OIDC middleware]
    S1 --> S2[Register IOidcSettings]
    S2 --> S3[Register role-claim / login-redirect services]
    S3 --> S4{useJwtRefreshMiddleware?}
    S4 -->|Yes| S4a[Register IJwtRefreshService]
    S4 -->|No| S5[Register cookie-claim / data-protection cert services]
    S4a --> S5
    S5 --> S6[AddWebAuthentication]
    S6 --> EndOk([OIDC services registered])
```

## Failure and edge-case behavior
- Resolving `IOidcSettings` itself never requires a secure environment client: `OidcSettings`'s constructor only stores its injected `IEnvironmentClient`/`ISystemHashService` dependencies. The secure-client requirement is lazy and property-specific — only the `AppCredentials` getter later calls `_systemHashService.ComputeHash(SystemType.Other, Assembly.GetExecutingAssembly().GetName().Name)` (OIDC's own executing assembly, not the caller's) and `_environmentClient.GetSecureSetting(Constants.AppCredentialsKey, Constants.EnvironmentSection, hash)`, so registering `RegisterOidcServices` without a prior secure environment-client registration (a null-hash `ISystemHashService`) only fails the first time code reads `OidcSettings.AppCredentials` — for example from `ConfigureOpenIdConnectOptions` — not at `IOidcSettings` resolution or construction. Every other `OidcSettings` property/indexer getter reads an ordinary (non-secure) setting and does not require a secure client (see [BR-SE-01](../business-rules.md#br-se-01-non-secure-environment-clients-cannot-retrieve-secure-settings)).

## Related documentation
- [Operations and processes](../operations-processes.md)
- [Business rules](../business-rules.md)

## Sources
- `src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/IServiceCollectionExtensions.cs`
