SAIF.NetCore.OIDC architecture¶
SAIF.NetCore.OIDC is an implementation-layer library (per Architecture.md) that supplies turnkey Okta OpenID Connect and cookie-authentication building blocks for externally-hosted SAIF ASP.NET Core applications — option configurators, authentication middleware, role-claim transformation, data-protection certificate lookup, login redirection, and an Okta refresh-token client — and it depends only on the foundation layer (SAIF.NetCore.ServiceContracts), as implementation libraries are expected to do.
Responsibilities¶
- Configure the ASP.NET Core cookie and OpenID Connect authentication handlers against Okta (
ConfigureCookieAuthenticationOptions,ConfigureOpenIdConnectOptions). - Enforce auth-cookie rewriting and JWT claim propagation through
IMiddlewarecomponents (JwtClaimMiddleware,RefreshJwtMiddleware,LowerCaseUrlMiddleware). - Refresh expired access/ID tokens against the Okta token endpoint (
JwtRefreshService). - Transform and cache role claims on the authenticated principal (
RoleClaimTransformer,RoleClaimService,ClaimTransformationCacheService). - Resolve data-protection certificates from the local certificate store for cookie key-ring protection (
DataProtectionCertService). - Redirect unauthenticated requests to the login flow, preserving the originating URL (
LoginRedirectService). - Resolve all
NetCore_OIDCenvironment settings throughIEnvironmentClient(OidcSettings).
Component boundaries¶
Options/configurators: own the one-time construction ofCookieAuthenticationOptionsandOpenIdConnectOptions, including the Okta authority, token validation parameters, and theStrictSecurityTokenValidator/OktaHttpMessageHandlerwiring fromOkta.AspNetCore.Middleware/: own per-request concerns — rewriting the auth cookie after claim updates, redirecting to an external token-refresh application when the access token has already expired (RefreshJwtMiddlewareonly detects and redirects; it does not callIJwtRefreshServiceand does not refresh the token in-process), and normalizing request URL casing.Services/: own settings resolution (OidcSettings), the Okta token refresh call (JwtRefreshService), role-claim transformation and its cache, data-protection certificate lookup, and login redirection.Contracts//Exceptions/: own theRefreshTokenResultDTO and the library's three custom exception types.- All service interfaces this library implements (
IOidcSettings,IJwtRefreshService,IRoleClaimService,IClaimTransformCacheService,ILoginRedirectService,IJwtAuthorizationCookieClaimService,IUrlCaseService,IDataProtectionCertService) are defined inSAIF.NetCore.ServiceContracts, not in this library. - This library ships no DI registration extension method; composing and registering these types into
IServiceCollection/IApplicationBuilderis owned entirely by the consumer,SAIF.NetCore.ServiceExtensions(RegisterOidcServices,AddWebAuthentication)*.
Interaction model¶
- In the one
ServiceExtensionscomposition path inspected for this documentation, consumers do not reference this library's types directly for registration;SAIF.NetCore.ServiceExtensionscomposes and registers every type in this library as part ofRegisterOidcServices/AddWebAuthentication*. This library itself ships no DI registration guard, so a consumer could register its public OIDC types directly instead; whether any does is unknown**. - At request time, the registered ASP.NET Core authentication pipeline invokes
ConfigureOpenIdConnectOptions/ConfigureCookieAuthenticationOptionsto challenge/validate against Okta, and the registered middleware (JwtClaimMiddleware,RefreshJwtMiddleware,LowerCaseUrlMiddleware) runs per request to rewrite cookies, redirect requests with an already-expired access token to an external token-refresh application (ending that request with a 301; it does not callIJwtRefreshService), and normalize casing. OidcSettingsresolves everyNetCore_OIDCsetting lazily fromIEnvironmentClienton each property access; it is not cached locally beyond whatIEnvironmentClient's own section cache provides.JwtRefreshService.Refreshis a separately callable service that exchanges a refresh token for a new access/ID token pair by calling out toIHttpJsonClient(implemented bySAIF.NetCore.HttpClients) to POST to the Okta token endpoint; no in-repository caller for this service was found, so which process invokes it (for example, a separate JwtRefresh application) is unknown**.OidcSettings.AppCredentialsseparately callsISystemHashService(implemented bySAIF.NetCore.SystemHash) to compute a hash used as a secure-setting key.- All five runtime-required services (
IEnvironmentClient,ISystemHashService,ISaifLogService,IHttpJsonClient,IHttpContextAccessor) are resolved from the DI container; none are referenced as compile-time package dependencies of this library — they are satisfied throughSAIF.NetCore.ServiceContractsinterfaces and bound to concrete implementations only by the consuming host*.
Architecture diagram¶
The following diagram shows how this library's components depend on SAIF.NetCore.ServiceContracts, how SAIF.NetCore.ServiceExtensions composes and registers them, and how the runtime services they require are supplied by sibling implementation libraries at the consumer's composition root.
flowchart TD
SC["SAIF.NetCore.ServiceContracts<br>(IOidcSettings, IJwtRefreshService, etc.)"]
OIDC_OPT["OIDC Options<br>ConfigureCookieAuthenticationOptions<br>ConfigureOpenIdConnectOptions"]
OIDC_MW["OIDC Middleware<br>JwtClaimMiddleware, RefreshJwtMiddleware<br>LowerCaseUrlMiddleware"]
OIDC_SVC["OIDC Services<br>OidcSettings, JwtRefreshService<br>RoleClaim*, LoginRedirectService<br>DataProtectionCertService"]
SE["SAIF.NetCore.ServiceExtensions<br>RegisterOidcServices / AddWebAuthentication"]
ENV["SAIF.NetCore.EnvironmentClient<br>(IEnvironmentClient)"]
HASH["SAIF.NetCore.SystemHash<br>(ISystemHashService)"]
HTTP["SAIF.NetCore.HttpClients<br>(IHttpJsonClient)"]
LOG["SAIF.NetCore.Logging<br>(ISaifLogService)"]
OKTA["Okta<br>(external OIDC provider)"]
APP["Consuming application<br>(externally hosted BOL)"]
OIDC_OPT -.->|NuGet ref| SC
OIDC_MW -.->|NuGet ref| SC
OIDC_SVC -.->|NuGet ref| SC
SE -.->|NuGet ref| OIDC_OPT
SE -.->|NuGet ref| OIDC_MW
SE -.->|NuGet ref| OIDC_SVC
APP -.->|NuGet ref| SE
OIDC_SVC -->|DI-resolved at host composition root| ENV
OIDC_SVC -->|DI-resolved at host composition root| HASH
OIDC_SVC -->|DI-resolved at host composition root| HTTP
OIDC_MW -->|DI-resolved at host composition root| LOG
OIDC_OPT -->|authorize/token/userinfo| OKTA
OIDC_SVC -->|POST refresh token| OKTA
Sequence diagram¶
The following diagram shows the primary request sequence for an authenticated request whose access token has already expired: RefreshJwtMiddleware decodes the token (without signature validation), detects ValidTo < UtcNow, and ends the request with a 301 redirect to the external token-refresh application — it never calls IJwtRefreshService or JwtRefreshService.Refresh.
sequenceDiagram
participant Client
participant RefreshJwtMiddleware
participant JwtRefreshApp as "External JwtRefresh application"
Client->>RefreshJwtMiddleware: Request with expired access token
RefreshJwtMiddleware->>RefreshJwtMiddleware: Decode token (no signature check), detect ValidTo < UtcNow
RefreshJwtMiddleware-->>Client: 301 redirect to RefreshTokenRedirectUrl (refreshToken + origin in query)
Note over Client,JwtRefreshApp: Request ends here. Any downstream call this<br>external application makes back into this library's<br>JwtRefreshService is unknown/inferred** — no in-repo caller exists.
JwtRefreshService.Refresh is documented separately as a standalone, callable service (see JwtRefreshService and OP-12); it exchanges a refresh token for a new JwtSet by posting to Okta's token endpoint, but this library has no in-repository caller for it, so the sequence by which any consumer (for example, the JwtRefresh application referenced above) invokes it is not verified here**.
Design constraints¶
- The library takes no NuGet dependency on any other SAIF implementation library — only on
SAIF.NetCore.ServiceContracts— consistent with the repo-wide rule that implementation libraries depend solely on the foundation layer*. - Okta is a hard-coded external identity provider dependency (
Okta.AspNetCore); there is no abstraction allowing a different OIDC provider without replacing the options configurators. - The role-transform cache (
ClaimTransformationCacheService) is an in-process singleton backed by a plain, non-thread-safeDictionary, so it does not survive process restarts and is not safe for unsynchronized concurrent access. RefreshJwtMiddleware/JwtRefreshServicesend the OAuth refresh grant in the query string, not the request body, and the refresh token value is not URL-encoded.- The data-protection key ring depends on local file-system and Windows certificate-store access, coupling the library's cookie-protection behavior to the host machine's certificate configuration.
Related documentation¶
- Index
- Overview
- Dependencies
- Configuration
- Operations and processes
- Business rules
- Security and authorization
- Service registration
- API reference
Note: Statements marked with * were inferred from code structure, naming conventions, or contextual evidence where direct confirmation was not available in source artifacts. Statements marked with ** could not be confirmed; treat as unknown until verified.
Sources¶
src/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/SAIF.NetCore.OIDC.csprojsrc/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Constants.cssrc/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Contracts/RefreshTokenResult.cssrc/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Exceptions/HttpContextAuthenticateException.cssrc/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Exceptions/JwtRefreshException.cssrc/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Exceptions/MissingDataProtectionCertificateException.cssrc/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Extensions/ClaimsPrincipalExtensions.cssrc/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Extensions/HttpContextExtensions.cssrc/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Middleware/JwtClaimMiddleware.cssrc/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Middleware/LowerCaseUrlMiddleware.cssrc/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Middleware/RefreshJwtMiddleware.cssrc/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Options/ConfigureCookieAuthenticationOptions.cssrc/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Options/ConfigureOpenIdConnectOptions.cssrc/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Services/ClaimTransformationCacheService.cssrc/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Services/DataProtectionCertService.cssrc/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Services/JwtAuthorizationCookieClaimService.cssrc/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Services/JwtRefreshService.cssrc/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Services/LoginRedirectService.cssrc/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Services/OidcSettings.cssrc/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Services/RoleClaimService.cssrc/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Services/RoleClaimTransformer.cssrc/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Services/UrlCaseService.cs- Architecture.md