---
title: Resolve OIDC settings
summary: Reads every OIDC setting from the Environment API section NetCore_OIDC.
status: draft
owner: SAIF Framework Platform Team
last_reviewed: 2026-10-02
library: SAIF.NetCore.OIDC
reference_type: package
operation_id: OP-11
trigger: Property access on IOidcSettings, on demand throughout this library
---

# 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](../business-rules.md#br-oidc-22-all-oidc-settings-are-read-from-the-netcore_oidc-section)).
   - `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.
   - 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](../../saif-netcore-environmentclient/operations/get-setting-value.md) and [Load section](../../saif-netcore-environmentclient/operations/load-section.md).
   > **Origin**: Steps 1, 2, and 5 are implemented in [`Get setting value`](../../saif-netcore-environmentclient/operations/get-setting-value.md) (`SAIF.NetCore.EnvironmentClient`) and executed on behalf of this operation.
2. Int properties (`RoleTransformCacheOffset`, `CookieExpireMinutes`, `ClockSkewSeconds`) → `_env.GetSettingValue<int>(key, "NetCore_OIDC")` for `Constants.RoleTransformCacheOffsetKey`, `Constants.CookieExpireMinutesKey`, `Constants.ClockSkewSecondsKey` ([BR-OIDC-22](../business-rules.md#br-oidc-22-all-oidc-settings-are-read-from-the-netcore_oidc-section)).
   - 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](../../saif-netcore-environmentclient/operations/get-setting-value.md).
3. `DefaultScopes` → `_env.GetSettingValue(Constants.DefaultOidcScopeKey, "NetCore_OIDC").Split(',')` ([BR-OIDC-22](../business-rules.md#br-oidc-22-all-oidc-settings-are-read-from-the-netcore_oidc-section)).
   - 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.
4. `AppCredentials` → `new OidcAppCredentials(_env.GetSecureSetting(Constants.AppCredentialsKey, Constants.EnvironmentSection, _hash.ComputeHash(SystemType.Other, Assembly.GetExecutingAssembly().GetName().Name)))` ([BR-OIDC-04](../business-rules.md#br-oidc-04-oidc-client-credentials-come-from-a-secure%2C-library-hashed-setting)).
   - 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](../../saif-netcore-systemhash/operations/compute-salted-system-hash.md)). 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](../../saif-netcore-environmentclient/operations/has-setting.md) 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`.
   - 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](../../saif-netcore-environmentclient/operations/get-secure-setting.md) 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](../../saif-netcore-environmentclient/operations/get-secure-setting.md). 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.
   - 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](../../saif-netcore-servicecontracts/operations/construct-oidc-application-credentials.md)). 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/operations/get-secure-setting.md) (`SAIF.NetCore.EnvironmentClient`), step 4.1 is implemented in [`Compute a salted system hash`](../../saif-netcore-systemhash/operations/compute-salted-system-hash.md) (`SAIF.NetCore.SystemHash`), and step 4.4 is implemented in [`Construct OIDC application credentials`](../../saif-netcore-servicecontracts/operations/construct-oidc-application-credentials.md) (`SAIF.NetCore.ServiceContracts`), all executed on behalf of this operation.
5. Indexer `this[key]` → `_env.GetSettingValue(key, "NetCore_OIDC")`, passing the caller-supplied key through unchanged ([BR-OIDC-22](../business-rules.md#br-oidc-22-all-oidc-settings-are-read-from-the-netcore_oidc-section)) — see [Get setting value](../../saif-netcore-environmentclient/operations/get-setting-value.md).

## Process flow

```mermaid
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](../configuration.md), 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](../configuration.md).

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

## 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`)
