Skip to content

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

  1. String properties → _env.GetSettingValue(key, "NetCore_OIDC"), where key is the property's own Constants.*Key constant — for example CallbackPath reads Constants.CallbackPathKey, CookieName reads Constants.CookieNameKey, and OktaBaseUrl reads Constants.OktaBaseUrlKey (every string property follows the same one-key-per-property pattern) (BR-OIDC-22).
  2. GetSettingValue resolves 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.
  3. 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/LoadSection can still propagate EnvironmentClientException (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.

  4. Int properties (RoleTransformCacheOffset, CookieExpireMinutes, ClockSkewSeconds) → _env.GetSettingValue<int>(key, "NetCore_OIDC") for Constants.RoleTransformCacheOffsetKey, Constants.CookieExpireMinutesKey, Constants.ClockSkewSecondsKey (BR-OIDC-22).
  5. On failure: the generic overload calls ThrowExceptionIfNullOnGeneric first; a missing key throws EnvironmentClientException("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.
  6. DefaultScopes → _env.GetSettingValue(Constants.DefaultOidcScopeKey, "NetCore_OIDC").Split(',') (BR-OIDC-22).
  7. 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.
  8. AppCredentials → new OidcAppCredentials(_env.GetSecureSetting(Constants.AppCredentialsKey, Constants.EnvironmentSection, _hash.ComputeHash(SystemType.Other, Assembly.GetExecutingAssembly().GetName().Name))) (BR-OIDC-04).
  9. 4.1. The hash is computed by ISystemHashService.ComputeHash(SystemType.Other, "SAIF.NetCore.OIDC"): GetHashValue builds the identity string "SAIF.NetCore.OIDC-{(int)SystemType.Other}", UTF-16LE-encodes it, SHA-256-hashes it, and Base64-encodes the digest; ComputeHash then passes that digest to new SaltShaker(DateTime.Now).SaltHash(hash), which salts the digest with the current timestamp before returning it — the value actually passed to GetSecureSetting is 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.
  10. 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 returns false, the delegate returns default(string) (null) without any HTTP call. If true, the delegate calls the Environment API's secure-setting endpoint, scoped by track and authorized with systemHashValue.
  11. 4.3. That HTTP call can itself fail before any value reaches OidcSettings: a transport-level error is rethrown as-is, a 404 throws EnvironmentClientException("Environment API could not find a value for the secure setting: NetCore_OIDC.BOLAppCredentials"), and any other non-success result throws EnvironmentClientException("Failed to retrieve secure setting: NetCore_OIDC.BOLAppCredentials."); only a successful response is converted via Convert.ChangeType and cached — see Get secure setting steps 5–6. Whether the cached/fetched value came from the cache or a fresh fetch, RetrieveSecureSetting then checks: if the result is null or equals default(string), it throws EnvironmentClientException("No crypto data found for requested secure setting: NetCore_OIDC.BOLAppCredentials") before returning to OidcSettings — see Get secure setting. On the non-secure BaseEnvironmentClient, RetrieveSecureSetting unconditionally throws EnvironmentClientException("Operation not allowed! This application is restricted from retrieving secure settings.") instead, for a different reason, without ever reaching the null/default check.
  12. 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 OidcAppCredentials constructor (declared in SAIF.NetCore.ServiceContracts). That constructor independently throws ArgumentNullException if raw is null, and ArgumentException if splitting on | does not yield exactly two fields (Construct OIDC application credentials). The standard SecureEnvironmentClient only rejects a null/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 throws ArgumentException there, not through a custom IEnvironmentClient. A custom implementation is needed only to reach the parser's ArgumentNullException branch, since the standard clients never return null past 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 in Compute a salted system hash (SAIF.NetCore.SystemHash), and step 4.4 is implemented in Construct OIDC application credentials (SAIF.NetCore.ServiceContracts), all executed on behalf of this operation.

  13. 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) propagates EnvironmentClientException before 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 EnvironmentClientException when missing, which fails DI resolution or options construction for whatever consumer reads it.
  • The secure-setting hash for AppCredentials is computed from this library's own executing assembly name plus the current timestamp (via SaltShaker.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/restricted BOLAppCredentials value throws EnvironmentClientException inside RetrieveSecureSetting before OidcAppCredentials is ever constructed, and the underlying HTTP fetch can itself fail with a transport exception, a 404 EnvironmentClientException, or a generic failure EnvironmentClientException; the OidcAppCredentials constructor's own ArgumentNullException/ArgumentException failure modes are a separate, parser-level error surface — a non-null but malformed secret reaches that parser through the standard client because SecureEnvironmentClient validates only for null/default, not pipe-field shape, so only the ArgumentNullException branch requires a custom IEnvironmentClient implementation that returns null — see Configuration.

Sources

  • src/SAIF.NetCore.OIDC/src/SAIF.NetCore.OIDC/Services/OidcSettings.cs
  • src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/OidcAppCredentials.cs (consumer dependency; owns the clientId|clientSecret parsing and its ArgumentNullException/ArgumentException failure 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/RetrieveSecureSetting behavior)
  • src/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SAIF.NetCore.SystemHash/SystemHashService.cs (consumer dependency; ComputeHash behavior)
  • src/SAIF.NetCore.HashUtil/SAIF.NetCore.HashUtil/SaltShaker.cs (consumer dependency; SaltHash salting behavior invoked by ComputeHash)