---
title: Get secure setting
summary: OP-05 operation detail page.
status: draft
owner: Framework.NCore maintainers
last_reviewed: 2026-10-02
library: SAIF.NetCore.EnvironmentClient
reference_type: package
---

# Get secure setting

| Field | Value |
| --- | --- |
| Owner | `GetSecureSetting`/`GetSecureSettingAsync` (16 public overloads total) on `BaseEnvironmentClient`; `RetrieveSecureSetting[Async]<T>` (always denies on `BaseEnvironmentClient`, retrieves on `SecureEnvironmentClient`) |
| Trigger | Consumer call |
| Inputs | `configKey`, optional `section`, and either a caller-supplied `systemHashValue` or a `SystemType` resolved through the injected `ISystemHashService` |
| Outputs | The decrypted/plain secure setting value (converted to `T`), or an `EnvironmentClientException`/`NullReferenceException` |

## Steps
1. If a `SystemType` overload is used, the **public** `GetSecureSetting`/`GetSecureSettingAsync` method on `BaseEnvironmentClient` calls `_systemHashService.Get(systemType)` to resolve the hash **before** calling `RetrieveSecureSetting[Async]` ([BR-EC-21](../business-rules.md#br-ec-21-systemtype-overloads-derive-the-system-hash-from-the-injected-service)). The client registered through `RegisterEnvironmentClient` (the non-secure registration) supplies `ISystemHashService` as `null` by design; calling a `SystemType` overload against that registration throws `NullReferenceException` here, before the "operation not allowed" denial is ever reached. The `RegisterSecureEnvironmentClient` registration always supplies a concrete `ISystemHashService`, so this failure path is specific to misusing a `SystemType` overload against a base (non-secure) client registration.
2. On `BaseEnvironmentClient`, `RetrieveSecureSetting[Async]<T>` unconditionally throws `EnvironmentClientException("Operation not allowed! This application is restricted from retrieving secure settings.")` — no cache check, no HTTP call ([BR-EC-20](../business-rules.md#br-ec-20-the-base-client-must-never-return-secure-settings); see also [security and authorization, EP-5](../security-authorization.md#authorization-enforcement-points)).
3. On `SecureEnvironmentClient`, `RetrieveSecureSetting[Async]<T>` calls `CheckForUpdates()` (the same global refresh used by [Load section](load-section.md)), then calls `SecureSettingCache.Get`/`GetAsync` with key `"{section}.{configKey}"`, the client's current `LastRefreshed`, and a fetch delegate ([BR-EC-23](../business-rules.md#br-ec-23-secure-values-are-cached-per-section%2Fkey-and-re-fetched-only-after-a-refresh)).
   - 3.1. An empty global `Settings` cache (or a newer server `lastRefreshed` timestamp, probed at most every 60 seconds) triggers `CheckForUpdates()`'s full reload: `LastRefreshed` is set to the start-of-reload timestamp **before** the root section and every known section are reloaded, `Settings` is cleared first, and `Sections` is **not** cleared — so if an individual named section's reload throws partway through, that section stays recorded in `Sections` (marked loaded) with no corresponding `Settings` entries until the next successful global refresh for it (see [Load section](load-section.md) step 7 and [Refresh check and cache reload](refresh-check-and-cache-reload.md) step 5). Only after this non-secure reload (successful or not, since a thrown `EnvironmentClientException` here propagates out of the whole secure-setting call) does the secure lookup in step 3.2 proceed.
   - 3.2. Within `SecureSettingCache`, an **initial cache miss** (no entry yet for this key) invokes the fetch delegate and, if the fetched value is non-null, both caches it **and returns that fetched `T` directly** — with no `Convert.ChangeType` pass, since it is already the delegate's native return type; a `null` result on an initial miss is **not** cached at all, so the very next call for the same key re-invokes the fetch delegate rather than returning a cached `null`. A **stale existing entry** (`item.FetchTime < LastRefreshed`) instead re-invokes the fetch delegate and **unconditionally** overwrites that entry's `Value`/`FetchTime` — including with `null` if the delegate returns `null` — so a secure setting that stops resolving is actively replaced, not left at its old cached value; its result (fresh or just-overwritten) is read back out of `item.Value` and, only if non-null, converted via `Convert.ChangeType(item.Value, typeof(T))` — a `null` `item.Value` at this point returns `default(T)` instead. A fresh existing entry is likewise read back out of `item.Value` and converted via `Convert.ChangeType` without re-invoking the delegate. So only the two **existing-entry** paths (stale-refreshed and fresh) convert through `item.Value`/`Convert.ChangeType`; the initial-miss path never does.
   - > **Origin**: Steps 3.1–3.2 are implemented in [Refresh check and cache reload](refresh-check-and-cache-reload.md) and [Secure setting cache](secure-setting-cache.md), and executed on behalf of this operation.
4. The fetch delegate (invoked only when the secure-setting cache has no entry, or a stale one, for this key — per step 3.2) first calls `HasSetting(configKey, section)` against the **non-secure** settings cache (not the secure cache).
   - 4.1. `HasSetting` builds the dictionary key from the literal `section`/`configKey` (no `TrackName` substitution), then calls `LoadSection` for that literal section — which unconditionally runs the global refresh check (`CheckForUpdates()`) first, before testing whether the section itself is already loaded. If the section is not yet in `Sections`, `LoadSection` acquires `SectionsSemaphore`, re-checks `Sections` under the lock, and fetches the section from the Environment API: a successful fetch with 1+ settings marks it loaded, a successful fetch with zero settings is self-healing (removed from `Sections`, retried next call), and a non-success/malformed response throws `EnvironmentClientException` — which propagates out of `HasSetting`, out of this fetch delegate, and out of the whole `GetSecureSetting[Async]` call, rather than being treated as "key not found." Once loading completes without throwing, `HasSetting` reports whether the composed key is present in the non-secure `Settings` dictionary.
   - > **Origin**: Step 4.1 is implemented in [Has setting](has-setting.md) and executed on behalf of this operation.
   - If that returns `false`, the delegate returns `default(T)` without making any HTTP call ([BR-EC-24](../business-rules.md#br-ec-24-a-secure-setting-is-fetched-only-if-a-matching-non-secure-key-exists)); see the data-level finding DL-1 in [security and authorization](../security-authorization.md#data-level-authorization-findings).
5. If `HasSetting` returned `true`, the delegate calls `GET /v2/secureSetting/{ApplicationName}/{TrackName}/[{section}/]{configKey}/value?systemHashValue={hash}` — the URL is scoped by the environment **track**, not the resolved host ([BR-EC-22](../business-rules.md#br-ec-22-a-secure-setting-request-is-scoped-by-track-and-authorized-by-a-system-hash)).
6. `GetValueFromResult` maps the HTTP outcome: a transport-level `result.ErrorException` is rethrown as-is; a 404 throws `EnvironmentClientException("Environment API could not find a value for the secure setting: {section}.{configKey}")`; any other non-success result throws `EnvironmentClientException("Failed to retrieve secure setting: {section}.{configKey}.")`; on success, the delegate returns `Convert.ChangeType(result.GetResult<string>(), typeof(T))` ([BR-EC-25](../business-rules.md#br-ec-25-secure-fetch-failures-map-to-one-of-three-error-messages)). There is no supplied/injectable `TypeConverter`.
7. Once the cache call returns (whether a freshly fetched value or a value already cached from a prior call), `RetrieveSecureSetting` checks: if the resulting value `is null` or `.Equals(default(T))`, it throws `EnvironmentClientException("No crypto data found for requested secure setting: {section}.{configKey}")` ([BR-EC-26](../business-rules.md#br-ec-26-a-secure-setting-resolving-to-null-or-the-type-default-is-an-error)). This is the actual outcome when step 4's `HasSetting` check was `false` — there is no "No crypto data found" value *returned* to the caller, it is always an exception.

## Process flow

```mermaid
flowchart TD
    A["GetSecureSetting configKey, section, hash/SystemType"] --> B{"SystemType overload and<br>ISystemHashService null?"}
    B -- Yes --> C[NullReferenceException]
    B -- No --> D{BaseEnvironmentClient?}
    D -- Yes --> E["EnvironmentClientException: Operation not allowed"]
    D -- No / SecureEnvironmentClient --> F[Refresh check and cache reload]
    F --> G[Secure setting cache Get]
    G -->|cached and fresh| H["Convert.ChangeType(cached value, T)"]
    G -->|"no entry: initial miss"| FD[Has setting non-secure check]
    G -->|"cached but stale:<br>FetchTime less than LastRefreshed"| FD
    FD -->|false| NV["val = default(T) / null"]
    FD -->|true| GV["GET /v2/secureSetting/.../track/.../value<br>?systemHashValue=hash"]
    GV -->|transport exception| L[Rethrow as-is]
    GV -->|404| M["EnvironmentClientException: not found"]
    GV -->|other failure| N["EnvironmentClientException: failed to retrieve"]
    GV -->|success| CV["Convert.ChangeType(result, T)"]
    NV --> SD{"Initial miss or stale?"}
    CV --> SD
    SD -->|"initial miss, val not null"| INS["TryAdd new cache entry"]
    SD -->|"initial miss, val null"| SKIP["Not cached; retry next call"]
    SD -->|stale| OW["Overwrite entry Value/FetchTime<br>(even if null)"]
    INS --> P
    SKIP --> P
    OW --> P
    H --> P
    P{"Result null or default(T)?"}
    P -- Yes --> Q["EnvironmentClientException: No crypto data found"]
    P -- No --> R[Return value]
```

## Sources
- `src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/BaseEnvironmentClient.cs`
- `src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SecureEnvironmentClient.cs`
- `src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SecureSettingCache.cs`
- `src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/IServiceCollectionExtensions.cs`
