Resolve OIDC settings¶
| Field | Value |
|---|---|
| Operation ID | OP-11 |
| Trigger | Property access on IOidcSettings, on demand throughout this library |
| Inputs | Environment API section NetCore_OIDC |
| Outputs | String, int, scope array, or credential value |
| Primary components | OidcSettings |
| Entry point | src/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Services/OidcSettings.cs |
Steps¶
- String properties →
_env.GetSettingValue(key, "NetCore_OIDC"), wherekeyis the property's ownConstants.*Keyconstant — for exampleCallbackPathreadsConstants.CallbackPathKey,CookieNamereadsConstants.CookieNameKey, andOktaBaseUrlreadsConstants.OktaBaseUrlKey(every string property follows the same one-key-per-property pattern) (BR-OIDC-22). GetSettingValueresolves the section to"NetCore_OIDC", lower-cases the composed"netcore_oidc.<key>"dictionary key, loads the section from the Environment API (at most once per cache generation), and checks the developer-override file before the Environment API-backed dictionary.- On branch: once the underlying section has loaded successfully, a missing key resolves to
""from the string overload rather than throwing; this does not cover a failed load —GetConfigSetting/LoadSectioncan still propagateEnvironmentClientException(or a transport exception during the refresh probe) before the missing-key fallback is ever reached — see Get setting value and Load section.Origin: Steps 1, 2, and 5 are implemented in
Get setting value(SAIF.NetCore.EnvironmentClient) and executed on behalf of this operation. - Int properties (
RoleTransformCacheOffset,CookieExpireMinutes,ClockSkewSeconds) →_env.GetSettingValue<int>(key, "NetCore_OIDC")forConstants.RoleTransformCacheOffsetKey,Constants.CookieExpireMinutesKey,Constants.ClockSkewSecondsKey(BR-OIDC-22). - On failure: the generic overload calls
ThrowExceptionIfNullOnGenericfirst; a missing key throwsEnvironmentClientException("Environment setting does not exist. It must exist when calling the generic implementation of GetSettingValue.")before any type conversion is attempted — see Get setting value. DefaultScopes→_env.GetSettingValue(Constants.DefaultOidcScopeKey, "NetCore_OIDC").Split(',')(BR-OIDC-22).- On branch: no trimming is applied to the split values; a missing key yields
[""](one empty scope), following the same string-read behavior as step 1. AppCredentials→new OidcAppCredentials(_env.GetSecureSetting(Constants.AppCredentialsKey, Constants.EnvironmentSection, _hash.ComputeHash(SystemType.Other, Assembly.GetExecutingAssembly().GetName().Name)))(BR-OIDC-04).- 4.1. The hash is computed by
ISystemHashService.ComputeHash(SystemType.Other, "SAIF.NetCore.OIDC"):GetHashValuebuilds the identity string"SAIF.NetCore.OIDC-{(int)SystemType.Other}", UTF-16LE-encodes it, SHA-256-hashes it, and Base64-encodes the digest;ComputeHashthen passes that digest tonew SaltShaker(DateTime.Now).SaltHash(hash), which salts the digest with the current timestamp before returning it — the value actually passed toGetSecureSettingis this salted string, not the unsalted Base64 digest (Compute a salted system hash). The executing assembly is SAIF.NetCore.OIDC, so the hash identifies the library, not the consuming host application. - 4.2. On the standard
SecureEnvironmentClient,GetSecureSetting/RetrieveSecureSetting<string>first calls the refresh check and cache reload, then looks up a per-section/key secure-setting cache; a cache miss or stale entry triggers a fetch delegate that first checks Has setting against the non-secure settings cache for the same key — if that returnsfalse, the delegate returnsdefault(string)(null) without any HTTP call. Iftrue, the delegate calls the Environment API's secure-setting endpoint, scoped by track and authorized withsystemHashValue. - 4.3. That HTTP call can itself fail before any value reaches
OidcSettings: a transport-level error is rethrown as-is, a 404 throwsEnvironmentClientException("Environment API could not find a value for the secure setting: NetCore_OIDC.BOLAppCredentials"), and any other non-success result throwsEnvironmentClientException("Failed to retrieve secure setting: NetCore_OIDC.BOLAppCredentials."); only a successful response is converted viaConvert.ChangeTypeand cached — see Get secure setting steps 5–6. Whether the cached/fetched value came from the cache or a fresh fetch,RetrieveSecureSettingthen checks: if the resultis nullor equalsdefault(string), it throwsEnvironmentClientException("No crypto data found for requested secure setting: NetCore_OIDC.BOLAppCredentials")before returning toOidcSettings— see Get secure setting. On the non-secureBaseEnvironmentClient,RetrieveSecureSettingunconditionally throwsEnvironmentClientException("Operation not allowed! This application is restricted from retrieving secure settings.")instead, for a different reason, without ever reaching the null/default check. - 4.4. Only a raw string value that the standard client actually returned (i.e., one that passed step 4.3's null/default check) reaches the
OidcAppCredentialsconstructor (declared inSAIF.NetCore.ServiceContracts). That constructor independently throwsArgumentNullExceptionifrawisnull, andArgumentExceptionif splitting on|does not yield exactly two fields (Construct OIDC application credentials). The standardSecureEnvironmentClientonly rejects anull/default value; it performs no pipe-field validation, so a non-null but malformed secret (including an empty string or a string with no|, or with 2+|characters) returned by the standard client reaches this parser and throwsArgumentExceptionthere, not through a customIEnvironmentClient. A custom implementation is needed only to reach the parser'sArgumentNullExceptionbranch, since the standard clients never returnnullpast step 4.3's check.Origin: Step 4.2–4.3 are implemented in
Get secure setting(SAIF.NetCore.EnvironmentClient), step 4.1 is implemented inCompute a salted system hash(SAIF.NetCore.SystemHash), and step 4.4 is implemented inConstruct OIDC application credentials(SAIF.NetCore.ServiceContracts), all executed on behalf of this operation. - Indexer
this[key]→_env.GetSettingValue(key, "NetCore_OIDC"), passing the caller-supplied key through unchanged (BR-OIDC-22) — see Get setting value.
Process flow¶
flowchart TD
Start([Property accessed on IOidcSettings]) --> Kind{Property kind?}
Kind -->|String| StringRead["GetSettingValue key, NetCore_OIDC"]
Kind -->|Int| IntRead["GetSettingValue-int- key, NetCore_OIDC"]
Kind -->|DefaultScopes| ScopeRead[GetSettingValue then Split by comma]
Kind -->|AppCredentials| ComputeHash[Compute salted system hash for SAIF.NetCore.OIDC]
Kind -->|Indexer| IndexerRead[GetSettingValue arbitrary key]
StringRead --> StringResult["Value or empty string"]
IntRead --> IntResult{Value present?}
IntResult -->|Yes| IntOk([Return int])
IntResult -->|No| IntThrow([Throw EnvironmentClientException])
ScopeRead --> ScopeResult([Return string array])
ComputeHash --> UnsaltedDigest["ComputeHash: SHA-256 digest of<br>SAIF.NetCore.OIDC identity string"]
UnsaltedDigest --> SaltHash["SaltShaker.SaltHash: salt the digest with current timestamp"]
SaltHash --> SecureRead[GetSecureSetting with salted hash]
SecureRead --> CacheCheck{Secure cache hit and fresh?}
CacheCheck -->|Yes| NullCheck
CacheCheck -->|No - miss or stale| HasSetting{Has setting non-secure?}
HasSetting -->|No| NullDefault["Fetch delegate returns default(T) - null"]
HasSetting -->|Yes| Fetch["GET secure setting endpoint"]
NullDefault --> NullCheck
Fetch -->|transport exception| FetchThrow(["Rethrow transport exception"])
Fetch -->|404| FetchNotFound(["Throw EnvironmentClientException: not found"])
Fetch -->|other failure| FetchFail(["Throw EnvironmentClientException: failed to retrieve"])
Fetch -->|success| FetchOk["Convert.ChangeType, cache value"]
FetchOk --> NullCheck
NullCheck{"Result null or default(T)?"}
NullCheck -->|Yes - standard SecureEnvironmentClient| SecureThrow(["Throw EnvironmentClientException: No crypto data found"])
NullCheck -->|No| BuildCreds["new OidcAppCredentials(raw)"]
BuildCreds --> CredsValid{"raw has exactly 2 pipe fields?"}
CredsValid -->|No, malformed| CredsThrowArg(["Throw ArgumentException"])
CredsValid -->|Yes| CredResult([Return OidcAppCredentials])
CustomNull(["Custom IEnvironmentClient only:<br>GetSecureSetting returns null<br>past this method's own contract"]) -.-> BuildCredsNull["new OidcAppCredentials(null)"]
BuildCredsNull -.-> CredsThrowNull(["Throw ArgumentNullException"])
IndexerRead --> IndexerResult(["Value or empty string"])
Failure and edge-case behavior¶
- Every string property silently defaults to
""when the underlying key is missing after its section has already loaded successfully; a failed section load/refresh (for example a non-success HTTP result from the Environment API) propagatesEnvironmentClientExceptionbefore that fallback is reached, which can mask misconfiguration until it manifests as a broken redirect or failed token exchange elsewhere (see Configuration, CR-03). - Every int property throws
EnvironmentClientExceptionwhen missing, which fails DI resolution or options construction for whatever consumer reads it. - The secure-setting hash for
AppCredentialsis computed from this library's own executing assembly name plus the current timestamp (viaSaltShaker.SaltHash), not the consuming application's identity, so every consuming app presents the same (differently salted per call) system-hash identity for this secret. - On the standard
SecureEnvironmentClient, a missing/restrictedBOLAppCredentialsvalue throwsEnvironmentClientExceptioninsideRetrieveSecureSettingbeforeOidcAppCredentialsis ever constructed, and the underlying HTTP fetch can itself fail with a transport exception, a 404EnvironmentClientException, or a generic failureEnvironmentClientException; theOidcAppCredentialsconstructor's ownArgumentNullException/ArgumentExceptionfailure modes are a separate, parser-level error surface — a non-null but malformed secret reaches that parser through the standard client becauseSecureEnvironmentClientvalidates only fornull/default, not pipe-field shape, so only theArgumentNullExceptionbranch requires a customIEnvironmentClientimplementation that returnsnull— see Configuration.
Related documentation¶
Sources¶
src/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Services/OidcSettings.cssrc/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/OidcAppCredentials.cs(consumer dependency; owns theclientId|clientSecretparsing and itsArgumentNullException/ArgumentExceptionfailure modes)src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/BaseEnvironmentClient.cs(consumer dependency;GetSettingValue/GetSettingValue<int>behavior)src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SecureEnvironmentClient.cs(consumer dependency;GetSecureSetting/RetrieveSecureSettingbehavior)src/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SystemHashService.cs(consumer dependency;ComputeHashbehavior)src/SAIF.NetCore.HashUtil/SAIF.NetCore.HashUtil/SaltShaker.cs(consumer dependency;SaltHashsalting behavior invoked byComputeHash)