Configuration settings¶
This library reads no IConfiguration/appsettings.json keys directly. Every OIDC-related setting is read live from the Environment API section NetCore_OIDC through IEnvironmentClient, via the typed facade OidcSettings: 20 static keys in total — 16 string-type, 3 integer-type (ClockSkewSeconds, CookieExpireMinutes, RoleTransformCacheOffset), and 1 secure key (BOLAppCredentials) — plus a separate dynamic indexer-based read (OidcSettings.this[key], with no in-repository caller) that is not part of the 20-key static catalog. The library defines no options classes bound from appsettings.json; its two IConfigureNamedOptions<T> implementations source every value from IOidcSettings, not from IConfiguration.
Configuration retrieval methods¶
| Method | Class / interface | Namespace | Usage pattern | Call sites |
|---|---|---|---|---|
IEnvironmentClient.GetSettingValue(string key, string section) |
IEnvironmentClient (impl. BaseEnvironmentClient/SecureEnvironmentClient) |
SAIF.NetCore.Services.Contracts |
Live read on every property access, section fixed to NetCore_OIDC. The composed lookup key is "netcore_oidc.<key>", lower-cased. A missing key returns "". |
OidcSettings string properties and the this[key] indexer |
IEnvironmentClient.GetSettingValue<int>(string key, string section) |
same | same | Typed read. A missing key throws EnvironmentClientException. |
OidcSettings.RoleTransformCacheOffset, CookieExpireMinutes, ClockSkewSeconds |
IEnvironmentClient.GetSecureSetting(string key, string section, string systemHashValue) |
same; real decryption only in SecureEnvironmentClient |
same | Decrypted read. The hash comes from ISystemHashService.ComputeHash(SystemType.Other, Assembly.GetExecutingAssembly().GetName().Name) — the executing assembly is this library, so every consuming app shares one hash identity. On BaseEnvironmentClient this always throws. |
OidcSettings.AppCredentials |
IEnvironmentClient.IsDeveloperMachine() |
same | same | Read once in the singleton constructor; relaxes the missing-certificate failure. | DataProtectionCertService..ctor |
OidcSettings indexer this[string key] |
IOidcSettings |
SAIF.NetCore.Services.Contracts |
Passes any caller-supplied key through to the string retrieval method in section NetCore_OIDC. No caller in this repository uses it. |
— |
Read timing.
| Consumer | Lifetime | When the setting is read | Effect of an Environment API change |
|---|---|---|---|
ConfigureCookieAuthenticationOptions, ConfigureOpenIdConnectOptions |
options configurators | When the named options for "Cookies"/"OpenIdConnect" are first built, then cached by the options system |
Requires an app restart* |
ClaimTransformationCacheService (singleton) |
singleton | RoleTransformCacheOffset, read once in the constructor |
Requires an app restart |
DataProtectionCertService (singleton) |
singleton | IsDeveloperMachine() in the constructor; DPCertName on each GetCertificate() call |
Developer flag: restart. Certificate name: live |
LoginRedirectService, RefreshJwtMiddleware, JwtRefreshService, RoleClaimTransformer |
transient/scoped | Per call | Live, governed by the EnvironmentClient refresh cycle* |
Environment API settings catalog¶
All keys belong to section NetCore_OIDC, so the composed key is NetCore_OIDC:<key>. EnvironmentClient lower-cases the composed key, so matching is case-insensitive but not spelling-tolerant.
Key (NetCore_OIDC:) |
Secure | Type / format | Seed default | Missing → | Required | Consuming class (member) |
|---|---|---|---|---|---|---|
BOLAppCredentials |
Yes | clientId\|clientSecret |
secure (vault reference; not reproduced) | On the standard SecureEnvironmentClient, a null/default cached or fetched value throws EnvironmentClientException ("No crypto data found for requested secure setting") before OidcAppCredentials is ever constructed; the non-secure BaseEnvironmentClient throws EnvironmentClientException for a different reason ("Operation not allowed"). Independently of those client-layer errors, OidcAppCredentials's own constructor (in SAIF.NetCore.ServiceContracts) rejects a null raw value with ArgumentNullException, and a non-null value without exactly one \| with ArgumentException. SecureEnvironmentClient validates only for null/default, not pipe-field shape, so a non-null malformed secret (for example an empty string, or a string missing/with extra \| characters) reaches this parser through the standard client and throws ArgumentException there; only the ArgumentNullException branch requires a custom IEnvironmentClient implementation that returns null |
Required (OIDC challenge) | OidcSettings.AppCredentials → ConfigureOpenIdConnectOptions.Configure (ClientId/ClientSecret) |
CallbackPath |
No | URL path | /authorization-code/callback |
"" → new PathString(""); the OIDC handler rejects an empty callback path during options validation* |
Required | ConfigureOpenIdConnectOptions.Configure |
ClockSkewSeconds |
No | int seconds | 5 |
EnvironmentClientException |
Required | ConfigureOpenIdConnectOptions.Configure → TokenValidationParameters.ClockSkew |
CookieAccessDeniedPath |
No | URL path | /unknown |
"" → empty AccessDeniedPath |
Recommended | ConfigureCookieAuthenticationOptions.Configure |
CookieExpireMinutes |
No | int minutes | 60 |
EnvironmentClientException |
Required | ConfigureCookieAuthenticationOptions.Configure → ExpireTimeSpan |
CookieLoginPath |
No | URL path (login app) | /OnlineLoginV2 |
"": login redirect goes to https://{host}{CookieSigninUri}, and the loop guard matches every path, so origin is never appended* |
Required | ConfigureCookieAuthenticationOptions.Configure; LoginRedirectService.Redirect |
CookieName |
No | cookie name; must be shared across SSO apps | SAIF.Asp.AuthenticationCookie |
"": an empty cookie name breaks shared-cookie SSO* |
Required | ConfigureCookieAuthenticationOptions.Configure |
CookieSigninUri |
No | URL path | /account/signin |
"" → redirect to the login app root |
Required | LoginRedirectService.Redirect |
DefaultOidcScope |
No | CSV of scopes (no trim) | profile,openid,email,offline_access |
"" → [""], adding one empty scope* |
Required | OidcSettings.DefaultScopes → ConfigureOpenIdConnectOptions.Configure |
DPApplicationName |
No | data-protection app discriminator; must be identical across apps sharing the cookie | SAIFBusinessOnline |
"": both the cookie and OIDC (-oidc) key rings use a blank or near-blank discriminator* |
Required | ConfigureCookieAuthenticationOptions.Configure; ConfigureOpenIdConnectOptions.Configure (as {value}-oidc) |
DPCertName |
No | exact X.509 Subject in LocalMachine\My | CN=BOL_OIDC_DP |
"": no certificate matches → MissingDataProtectionCertificateException (non-developer machines); developer machines get null and an unencrypted key ring |
Required (servers) | DataProtectionCertService.GetCertificate → ConfigureCookieAuthenticationOptions |
DPKeyDirectory |
No | file-system path | C:\OIDC\DP |
"" → new DirectoryInfo("") → ArgumentException at options build* |
Required | ConfigureCookieAuthenticationOptions.Configure; ConfigureOpenIdConnectOptions.Configure |
OktaAuthorizationServerUrl |
No | URL path segment | /oauth2/default |
"" → authority = Okta org root |
Required | ConfigureOpenIdConnectOptions.Configure (authority); JwtRefreshService.Refresh |
OktaBaseUrl |
No | absolute HTTPS URL of the Okta tenant | present only in EnvironmentSettings.json/.enttest7.json; absent from .global.json |
"" → relative authority → OIDC metadata retrieval fails* |
Required | ConfigureOpenIdConnectOptions.Configure; JwtRefreshService.Refresh |
RefreshTokenQueryString |
No | query-string prefix ending in refresh_token= |
?grant_type=refresh_token&redirect_uri=&scope=offline_access openid&refresh_token= (unencoded space) |
"" → token POST without a grant → Okta 400 → JwtRefreshException* |
Required when IJwtRefreshService is used |
JwtRefreshService.Refresh |
RefreshTokenRedirectUrl |
No | URL path of the JwtRefresh app | /JwtRefresh/token/refresh |
"" → 301 to a relative URL made of the query only* |
Required when UseJwtRefreshMiddleware |
RefreshJwtMiddleware.InvokeAsync |
RefreshTokenUrl |
No | URL path | /v1/token |
"" → POST to the authorization-server root* |
Required when IJwtRefreshService is used |
OidcSettings.RefresnTokenUrl (property-name typo) → JwtRefreshService.Refresh |
RoleClaimIdentitifier (code key, typo) |
No | substring matched against lower-cased claim types | seed key is RoleClaimIdentifier (correct spelling) / _roles_ — the code key has no seed entry |
"" (likely in every seeded environment): "anything".Contains("") is true, so every non-array claim becomes a ClaimTypes.Role |
Required (security-relevant) | OidcSettings.RoleClaimIdentifier → RoleClaimTransformer.TransformAsync → RoleClaimService.AddRoleClaims |
RoleTransformCacheOffset |
No | int seconds | 30 |
EnvironmentClientException, while resolving the singleton IClaimTransformCacheService, failing the scoped RoleClaimService resolution |
Required | ClaimTransformationCacheService..ctor |
SignedOutCallbackPath |
No | URL path | /signout/callback |
"" → PathString("") |
Required | ConfigureOpenIdConnectOptions.Configure |
(dynamic) NetCore_OIDC:<caller key> |
No | any | — | "" |
— | OidcSettings.this[key] — no in-repo caller |
Declared but never read (orphaned constants). The removed SigningKeys* services left these behind; no code reads them and the seed files do not list them: JwtSigningKeysUrl, JwtSigninKeysExpireMinutes.
Host-level toggles that affect this library come from SAIF.NetCore.ServiceExtensions/AppStartup, not this library: HostLocation (must be External), ChallengeType (OpenIdConnect), UseJwtRefreshMiddleware.
Developer overrides¶
| Override | Applied when | Effect on this library |
|---|---|---|
IEnvironmentClient.IsDeveloperMachine() |
Evaluated once when the DataProtectionCertService singleton is constructed |
A missing DPCertName certificate returns null instead of throwing; the cookie key ring in DPKeyDirectory is then written without certificate encryption |
Developer settings override file (AppSettings:EnvApiDeveloperSettingsOverride, conventionally developersettings.json) |
Developer machine and the file exists; re-read on each non-secure lookup | Can override any non-secure NetCore_OIDC.* value by entries with section: "NetCore_OIDC", matched case-insensitively. It cannot override BOLAppCredentials, because secure reads bypass the override. It can be used locally to supply the typo key RoleClaimIdentitifier* |
The library ships no developer settings file of its own.
Environment variables¶
The library reads no environment variables directly. These are read by packages and the host that activate or support OIDC, and are listed for completeness.
| Variable | Read by | Scope | Accepted values | Default when unset | Effect on this library |
|---|---|---|---|---|---|
ASPNETCORE_HOST_LOCATION |
AppStartup.HostLocation (ServiceExtensions) |
Process, then Machine (Windows-only) | EXTERNAL/INTERNAL (case-insensitive) |
NotDefined |
Only EXTERNAL registers and wires OIDC; RegisterOidcServices and the LowerCaseUrl/JwtClaim/RefreshJwt middleware run only then |
ASPNETCORE_REDIRECT_SSL |
AppStartup.EnforceSslRedirect |
Machine only | true/false |
true |
HTTPS redirection runs after the OIDC middleware, so it is relevant to the cookie SecurePolicy risk |
EnvironmentApiUrl |
BaseEnvironmentClient field initializer |
Process | absolute URL | none → MissingEnvironmentUrlVariableException |
No OIDC setting can be resolved without it |
COREAPIENABLED (usually ASPNETCORE_COREAPIENABLED) |
SecureEnvironmentClient ctor |
via IConfiguration |
1 |
unset → secure client throws unless on a developer machine | AppStartup registers the secure client, and BOLAppCredentials needs it |
Database connection configuration¶
Not applicable. This library has no database access, no IDBConnectionClient usage, and no connection strings. Persistence is limited to the file system (data-protection key ring at DPKeyDirectory) and the Windows certificate store (DPCertName, LocalMachine\My, read-only).
Secure settings¶
| Setting key | Retrieval | Hash method | Application type | Application name (hash input) | Credential provided |
|---|---|---|---|---|---|
NetCore_OIDC:BOLAppCredentials |
IEnvironmentClient.GetSecureSetting(key, section, systemHashValue) |
ISystemHashService.ComputeHash(SystemType, string) |
SystemType.Other |
Assembly.GetExecutingAssembly().GetName().Name = SAIF.NetCore.OIDC (the library assembly, not the host app) |
Okta OIDC client credentials, clientId\|clientSecret, parsed by OidcAppCredentials |
Notes: the hash input is the OIDC assembly name, so every consuming application presents the same system hash for this secret — crypto-API authorization cannot distinguish one consuming app from another. The secure client is required (BaseEnvironmentClient.RetrieveSecureSetting always throws EnvironmentClientException); AppStartup registers SecureEnvironmentClient. On the standard SecureEnvironmentClient, RetrieveSecureSetting<T> itself throws EnvironmentClientException if the cached/fetched value is null or the type default, before the value is ever returned to OidcSettings.AppCredentials — so the credential-parsing constructor OidcAppCredentials (declared in SAIF.NetCore.ServiceContracts, not in this library's OidcSettings.cs) never observes a null raw value through that path. OidcAppCredentials separately and independently rejects a null raw value with ArgumentNullException and a non-null but malformed (wrong pipe-field-count) raw value with ArgumentException. Because SecureEnvironmentClient validates only for null/default and performs no pipe-field validation, a non-null malformed secret reaches this parser through the standard client and triggers the ArgumentException branch there; only the ArgumentNullException branch requires a custom IEnvironmentClient implementation that returns null where the standard client would have thrown first. The seed marks BolAppCredentials as SecureFlag: true; its value is a reference and is not reproduced here.
Transitive configuration (from dependencies)¶
| Key / input | Required by | Why this library needs it |
|---|---|---|
EnvironmentApiUrl (env var) |
SAIF.NetCore.EnvironmentClient |
All 20 Environment API reads |
COREAPIENABLED=1 (server) |
SecureEnvironmentClient |
BOLAppCredentials secure read |
AppSettings:ApplicationHost, AppSettings:EnvApiDeveloperOverrideAllowed, AppSettings:EnvApiDeveloperSettingsOverride |
HostResolver/BaseEnvironmentClient |
Track resolution; developer flag; override file |
EnvironmentClient:GetHostsByTrack:RetryCount/:RetrySleepMs (optional) |
BaseEnvironmentClient |
Startup resilience of the client this library depends on |
ISystemHashService registration (assembly-based) |
SAIF.NetCore.SystemHash |
Hash for BOLAppCredentials |
ISaifLogService and its configuration |
SAIF.NetCore.Logging |
Error/warning logging in options, middleware, and the certificate service |
IHttpJsonClient |
SAIF.NetCore.HttpClients |
JwtRefreshService POST to Okta |
Host toggles AppStartup.AuthenticationChallengeType (default Cookie) and AppStartup.UseJwtRefreshMiddleware (default true) |
SAIF.NetCore.ServiceExtensions |
Code properties, not configuration keys. LowerCaseUrlMiddleware and RefreshJwtMiddleware are always registered as concrete scoped types by RegisterOidcServices, regardless of either toggle; AuthenticationChallengeType instead decides only whether LowerCaseUrlMiddleware is added to the pipeline by AppStartup.Configure, and UseJwtRefreshMiddleware decides both whether IJwtRefreshService is registered in DI and whether RefreshJwtMiddleware is added to the pipeline — see Service registration |
This library has no ProjectReferences and only one internal package reference, SAIF.NetCore.ServiceContracts, which is contracts-only with no configuration reads of its own.
Environment variance assessment¶
The seed files (EnvironmentSettings.json, .global.json, .enttest7.json) are solution items, not compiled, and imply a pattern of global defaults plus a per-track OktaBaseUrl. Only one track, enttest7, is represented in this repository.
| Setting key | Varies by environment | Classification basis |
|---|---|---|
BOLAppCredentials |
Yes | Credential; Okta app per tenant* |
OktaBaseUrl |
Yes | Tenant host URL; the only key with a per-track seed file |
OktaAuthorizationServerUrl |
No (typically) | Static path /oauth2/default, same in all seeds |
RefreshTokenUrl, RefreshTokenQueryString |
No | Okta protocol constants* |
RefreshTokenRedirectUrl, CookieLoginPath, CookieSigninUri, CookieAccessDeniedPath, CallbackPath, SignedOutCallbackPath |
No | Relative app paths, identical across hosts* |
CookieName, DPApplicationName |
No — must be identical across all SSO apps in an environment | Shared-cookie SSO depends on equality* |
DPKeyDirectory |
Possibly (per server farm) | File-system path; must be shared or synchronized per farm* |
DPCertName |
Possibly | Certificate subject may differ per environment PKI* |
CookieExpireMinutes, ClockSkewSeconds, RoleTransformCacheOffset |
No | Numeric thresholds* |
DefaultOidcScope |
No | Static scope list* |
RoleClaimIdentitifier (code) / RoleClaimIdentifier (seed) |
No | Static claim pattern _roles_; see CR-01 for the key mismatch |
ASPNETCORE_HOST_LOCATION (transitive) |
Yes (per server role) | Decides whether OIDC is active at all |
Configuration dependencies (risks)¶
| ID | Risk | Severity |
|---|---|---|
| CR-01 | RoleClaimIdentitifier/RoleClaimIdentifier key mismatch: the code requests netcore_oidc.roleclaimidentitifier; the seed defines RoleClaimIdentifier. Lower-casing does not reconcile a spelling difference, so the read returns "", and RoleClaimService matches every non-array claim type. Impact is limited today only because the transformer is registered as a concrete type, not as IClaimsTransformation (see Security and authorization). |
High (conditional) |
| CR-02 | OktaBaseUrl absent from the global seed; any track without its own override has an empty authority, so OIDC metadata retrieval and JwtRefreshService both fail. |
Medium |
| CR-03 | Silent empty-string defaults on 16 string keys; misconfiguration surfaces later as broken redirects, empty scopes, or an invalid authority rather than a clear startup error. | Medium |
| CR-04 | Int keys throw at singleton/options construction: a missing RoleTransformCacheOffset breaks DI resolution of the claim-transform chain; a missing CookieExpireMinutes or ClockSkewSeconds breaks the auth options build, failing every authenticated request. |
Medium |
| CR-05 | No cookie SecurePolicy; HTTPS redirect is controlled by ASPNETCORE_REDIRECT_SSL and runs after the OIDC middleware. |
Medium |
| CR-06 | Single secure-hash identity (SystemType.Other + "SAIF.NetCore.OIDC") for every consuming app's client secret. |
Design / Medium |
| CR-07 | Settings captured for the process lifetime in singletons and options; Environment API changes need an app restart. | Low |
| CR-08 | The developer-machine flag disables certificate encryption of the cookie key ring; a misdetected developer flag on a server would silently weaken data protection. | Medium |
| CR-09 | DPKeyDirectory must be shared across farm nodes and readable by the app pool, or cookies fail to decrypt on other nodes. |
Medium |
| CR-10 | RefreshTokenQueryString contains an unencoded space and is concatenated with an unencoded refresh token. |
Low |
| CR-11 | Orphaned keys JwtSigningKeysUrl and JwtSigninKeysExpireMinutes are declared but never read. |
Low (tech debt) |
| CR-12 | This library is activated by an environment variable (ASPNETCORE_HOST_LOCATION=EXTERNAL); a missing or incorrect value silently leaves the site without OIDC. |
Medium |
Related documentation¶
- Index
- Overview
- Architecture
- Dependencies
- 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/Services/OidcSettings.cssrc/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Constants.cssrc/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Services/DataProtectionCertService.cssrc/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/OidcAppCredentials.cs(consumer dependency; owns theclientId|clientSecretparsing and theArgumentNullException/ArgumentExceptioncredential-parser failure modes documented on this page)src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/BaseEnvironmentClient.cs(consumer dependency;GetSettingValue/GetSettingValue<int>lookup, lower-casing, and missing-key behavior documented on this page)src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SecureEnvironmentClient.cs(consumer dependency;GetSecureSetting/RetrieveSecureSettingdecryption and null/default failure behavior forBOLAppCredentials)src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/HostResolver.cs(consumer dependency; developer-override and track-resolution behavior)src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/IOidcSettings.cs(consumer dependency; defines theRefresnTokenUrlproperty-name typo referenced on this page)src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/Startup/AppStartup.cs(consumer composition;ASPNETCORE_HOST_LOCATION/ASPNETCORE_REDIRECT_SSL/AuthenticationChallengeType/UseJwtRefreshMiddlewarehost toggles documented on this page)src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/IServiceCollectionExtensions.cs(consumer composition;RegisterOidcServicesregistration conditions documented on this page)src/SAIF.NetCore.OIDC/src/EnvironmentSettings.json(seed defaults declaring source)src/SAIF.NetCore.OIDC/src/EnvironmentSettings.global.json(seed defaults declaring source; absence ofOktaBaseUrldocumented on this page)src/SAIF.NetCore.OIDC/src/EnvironmentSettings.enttest7.json(track-specific seed override declaring source)