Skip to content

SAIF.NetCore.ServiceExtensions security and authorization

This library is a security composition root, not an authorization engine. It defines no authorization mechanism of its own. Through its IServiceCollection extensions and abstract Startup base classes, it wires the authentication schemes, the REST SecurityApi authorization policy provider/handler, and the identity accessors — all implemented in SAIF.NetCore.AuthService and SAIF.NetCore.OIDC — into consuming applications. It also defines two cookie-presence "gate" middlewares of its own: one wired (AspNetSessionIdMiddleware) and one not (LegacyBolAuthMiddleware).

Authorization architecture

Role decisions run through the REST SecurityApi client (ISecurityClient, from SAIF.NetCore.AuthService), not through the WCF SecurityService — no WCF service reference, SecurityServiceClient, securityServiceUrl/WCFEndPointAddress key, or /WCF/Security URL exists under this library's source. This library decides which authentication stack and which authorization components a consuming application gets, based on the chosen Startup base class and the ASPNETCORE_HOST_LOCATION environment variable, and it adds two cookie-presence gates of its own.

flowchart TD
    A[Consuming application Startup] --> B{Which base class?}
    B -->|AppStartup, External| C["RegisterOidcServices +<br>RegisterCoreAPIAuthServices-shaped SecurityApi wiring"]
    B -->|AppStartup, Internal/NotDefined| D[RegisterCoreAPIAuthServices]
    B -->|CoreAPIStartup| D
    B -->|EdgeAPIStartup| E["RegisterEdgeAPIAuthServices - JWT only,<br>no SecurityApi policy provider"]
    C --> F[SecurityApiPolicy / SecurityApiHandler]
    D --> F
    F --> G["ISecurityClient.IsAuthorized role check -<br>AuthService REST call"]
    E -.->|SecurityApiAuthorize on Edge throws at runtime, SEC-07| G

AD resource catalog

No AD group or resource-name literal exists in this library's code. Every role name reaches the authorization handler from a consumer's new SecurityApiAuthorize(roles) attribute, where roles is a caller-supplied, comma-separated string argument, resolved through the SecurityAPI.<roles> dynamic policy (SecurityAPIPolicy, registered by this library). This library never supplies a role itself.

Resource name Context Enforcement point (as wired by this library) Confidence
(none hard-coded) Role names are supplied by consumers through new SecurityApiAuthorize(roles), where roles is a caller-supplied string. The comma-separated list is OR-evaluated by SecurityApiHandler. SecurityApiPolicy + SecurityApiHandler, registered by Register Core API auth services and by App startup configure services's External branch verified

Non-resource security identifiers hard-coded in the library

Identifier Kind Used by Confidence
ASP.NET_SessionId Request cookie name (legacy ASP.NET session cookie) AspNetSessionIdMiddleware presence check verified
SAIFAUTH Request cookie name (legacy credential cookie issued by SAIF.NetCore.LegacyAuth) LegacyBolAuthMiddleware presence check verified
SAIF.BOL.Auth Request cookie name (legacy BOL auth cookie; issuer not in this repository) LegacyBolAuthMiddleware presence check verified (name); unknown (issuer)**
endpoint / LegacySessionProvider EnvironmentClient setting (path of the legacy session provider) Redirect target in both gate middlewares — see Legacy cookie gate middleware verified
connectionservicecreds / dbconnectionclient EnvironmentClient secure setting (DBConnectionAPI client credentials, user\|password) Register DB connection client verified (value never reproduced)
ASPNETCORE_HOST_LOCATION Process/machine env var selecting the External (OIDC) or internal (dual IIS/JWT) auth stack AppStartup.HostLocation verified
ASPNETCORE_REDIRECT_SSL Machine env var that can disable UseHttpsRedirection AppStartup.EnforceSslRedirect verified

AD group names Domain Users, Developer_Group, and Users appear only in this library's integration test controllers (test-only, not library behavior).

Authorization enforcement points

This library contains no [SaifAuthorize] / [SecurityApiAuthorize] / [Authorize] decoration on any of its own public types. Its enforcement points fall into three kinds: (A) security service registrations made on behalf of consumers, (B) pipeline placement, and (C) the two cookie-gate middlewares.

A. Security service registrations

# Entry point Authentication scheme(s) wired Authorization components wired Identity services wired Source
A1 Register Core API auth services ConfigureJwtBearerOptions + dual policy scheme "dual": Authorization header starting "Bearer " → JwtBearer, else IIS/Negotiate (IISServerDefaults) AddSecurityApiAuthorizeAttribute() → IAuthorizationPolicyProvider = SecurityApiPolicy (Transient), IAuthorizationHandler = SecurityApiHandler (Singleton) AddIdentityAccessor() → ISecurityClient = SecurityClient (Singleton), WinIdentityService, TokenIdentityService, IIdentityServiceAccessor = MultiIdentityServiceAccessor src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/IServiceCollectionExtensions.cs L250–259; src/SAIF.NetCore.AuthService/SAIF.NetCore.AuthService/SAIF.NetCore.AuthService/Extensions/AuthenticationServiceExtensions.cs L46–53, L68–79, L83–88, L90–98
A2 Register Edge API auth services ConfigureJwtBearerOptions + JwtBearer only, no Windows fallback None — no SecurityApiPolicy/SecurityApiHandler registered (see SEC-07) AddTokenIdentityService() → ISecurityClient (Singleton), IIdentityService = TokenIdentityService, IIdentityServiceAccessor = IdentityServiceAccessor src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/IServiceCollectionExtensions.cs L99–108; src/SAIF.NetCore.AuthService/SAIF.NetCore.AuthService/SAIF.NetCore.AuthService/Extensions/AuthenticationServiceExtensions.cs L31–34
A3 Register OIDC services → Add web authentication Cookies (default authenticate/sign-in) + OpenIdConnect (Okta); challenge = Cookies by default or OpenIdConnect for a login app Role-claim transformation services (RoleClaimTransformer, IRoleClaimService, IClaimTransformCacheService); JwtClaimMiddleware, RefreshJwtMiddleware (conditional), LowerCaseUrlMiddleware, ILoginRedirectService, IJwtAuthorizationCookieClaimService, IDataProtectionCertService — src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/IServiceCollectionExtensions.cs L137–169, L177–212
A4 Require authenticated user — Global MVC AuthorizeFilter with RequireAuthenticatedUser() — denies anonymous access to every MVC endpoint unless [AllowAnonymous] — src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/IServiceCollectionExtensions.cs L116–129
A5 App startup configure services, External host (ASPNETCORE_HOST_LOCATION=EXTERNAL) A3 AddAuthorization(). SecurityApiPolicy+SecurityApiHandler registered twice — inline and again via AddSecurityApiAuthorizeAttribute() (see SEC-05) ISecurityClient registered Transient, then Singleton via AddIdentityAccessor() — last registration wins for single resolution src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/Startup/AppStartup.cs L98–137
A6 App startup configure services, Internal or NotDefined host A1 (dual IIS/JWT, not OIDC) A1 A1 src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/Startup/AppStartup.cs L133–134
A7 Core and Edge API startup — CoreAPIStartup A1, unless DisableAuthServices = true (turns off all auth registration) A1 A1 src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/Startup/CoreAPIStartup.cs L22, L61–64
A8 Core and Edge API startup — EdgeAPIStartup A2 A2 (none) A2 src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/Startup/EdgeAPIStartup.cs L36
A9 Register secure environment client — — (credential plumbing, not authorization). ISystemHashService is bound to the calling assembly, so secure-setting access is keyed on the caller's system hash. — src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/IServiceCollectionExtensions.cs L82–92
A10 Register environment client (non-secure) — Registers ISystemHashService as a null singleton — any consumer resolving it in a non-secure host gets null — src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/IServiceCollectionExtensions.cs L45–55

Wired handler semantics (summarized from AuthService; relevant here because this library activates the handler): SecurityApiHandler.HandleRequirementAsync returns without succeeding when User.Identity.Name is blank. It strips a DOMAIN\ prefix, splits the required roles on ,, and calls ISecurityClient.IsAuthorized(user, role) for each one, succeeding on the first true (OR semantics). The policy provider activates only for policy names starting with SecurityAPI.

B. Pipeline placement

Startup Order of security-relevant middleware Source Notes
AppStartup.Configure EnableBuffering → [UseSession → AspNetSessionIdMiddleware] (if EnableSessionMiddleware) → Dynamic error handler invocation → Debug request handler invocation → UseAuthentication → (External only: LowerCaseUrlMiddleware [OIDC challenge only] → JwtClaimMiddleware → RefreshJwtMiddleware [if enabled]) → UseHsts → UseHttpsRedirection (unless ASPNETCORE_REDIRECT_SSL=false) → UseStaticFiles → UseRouting → ConfigureAuthorization (virtual; default UseAuthorization) → ConfigureUI (endpoints) → ConfigureSaifLogger src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/Startup/AppStartup.cs L144–189, L195–198 (1) The session gate runs before the error handler and before authentication. (2) UseStaticFiles runs before UseAuthorization, so static files are served without authorization (standard ASP.NET Core behavior*). (3) ConfigureAuthorization is protected virtual — a derived class can drop UseAuthorization entirely.
CoreAPIStartup.Configure ConfigureMiddleware (buffering → error handler → debug handler) → UseAuthentication (skipped when DisableAuthServices) → UseHsts → UseHttpsRedirection → UseRouting → UseAuthorization (always) → MapControllers src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/Startup/CoreAPIStartup.cs L74–97; src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/Startup/BaseApiStartup.cs L33–42 With DisableAuthServices = true, UseAuthorization still runs, but no authentication scheme is registered — any [Authorize] endpoint then fails at challenge time (InvalidOperationException: no default challenge scheme*).
EdgeAPIStartup.Configure ConfigureMiddleware → UseAuthentication → UseHsts → UseHttpsRedirection → UseRouting → UseAuthorization → MapControllers src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/Startup/EdgeAPIStartup.cs L45–65 Standard ordering.

No startup calls RequireAuthenticatedUser() (A4); none of the base Startup classes applies a global authenticated-user requirement by default. Each endpoint is protected only if the consumer decorates it with [Authorize]/[SecurityApiAuthorize] or calls RequireAuthenticatedUser() itself — unprotected-by-default is the composed posture.

Both gate middlewares are documented together in Legacy cookie gate middleware and BR-SE-16

AspNetSessionIdMiddleware — wired, fail-open gate (SEC-01, High). Checks for presence only (Request.Cookies["ASP.NET_SessionId"] == null); never reads or validates the value (src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/Middleware/AspNetSessionIdMiddleware.cs L37–39). On a missing cookie, redirects to the LegacySessionProvider endpoint (L41–44), but then unconditionally calls await next(context) (L47) — the redirect is advisory only; the rest of the pipeline (error handler, authentication, routing, authorization, endpoint) still executes, and any endpoint result that sets its own status/body overwrites the 302. Any client-supplied cookie value, including an arbitrary or expired one, satisfies the gate. Additional observations: the app's own AddSession() issues .AspNetCore.Session, not ASP.NET_SessionId, so only an external legacy session provider on a shared host/domain can set the cookie this gate checks for; the redirect host is derived from Request.Host without host filtering (L43), so a forged Host header produces a Location on an attacker-chosen host (low impact alone, but relevant to shared-cache poisoning); and the gate runs before the error handler (src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/Startup/AppStartup.cs L152–158), so a failure in its settings lookup (L42) escapes the SAIF error handler. Mitigating control: ASP.NET Core authentication/authorization still applies independently to endpoints carrying [Authorize]/[SecurityApiAuthorize] — the defect bypasses only the legacy-session requirement, not decorated-endpoint authorization.

LegacyBolAuthMiddleware — not wired, dead code, same flaw (SEC-02, latent). Checks for presence of either SAIFAUTH or SAIF.BOL.Auth (src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/Middleware/LegacyBolAuthMiddleware.cs L36–39), with the identical fail-open defect (unconditional await next(context) at L47 after Redirect at L44). It is registered in DI when AppStartup.EnableLegacyBolAuthMiddleware = true (src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/Startup/AppStartup.cs L55, L113–116), but no UseMiddleware<LegacyBolAuthMiddleware>() call exists anywhere in the repository (repo-wide grep against src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/Startup/AppStartup.cs L144–189) — the flag registers a service that is never invoked, so setting it gives no protection (a misleading-configuration risk). The class is public, so an external consumer could wire it manually**.

Security observations

SEC-03 Exception detail disclosure to clients

Field Value
Finding Exception detail disclosure to clients. Every ControllerBaseExtensions.Throw*(Exception ex) overload (see Controller throw helpers) passes ex.ToString() (type, message, stack trace, inner exceptions) as the exception message; DynamicErrorHandler/ErrorHandler.LogAndReturnException writes it to the response body for handled exceptions. The response also carries a Server-Name header (machine name).
Severity Medium
Confidence verified

SEC-04 Credentials logged at Debug level

Field Value
Finding Credentials logged at Debug level. Debug request handler invocation runs on every request in every startup and logs WebRequestDetails, which captures all request headers (including Authorization and Cookie, which can carry SAIFAUTH) and the full request body with no redaction at the point this library emits the log event. Mitigating control: the LoggingApi sink (SAIF.NetCore.Logging's LoggingApiSink) is reported to apply middleware that identifies and masks credentials and other secure data elements before the log entry is persisted to disk, Splunk, or Dynatrace; this masking is not implemented in, or traceable to source in, this repository.** Exposure at the point of emission (in-process, pre-sink) depends on the effective production log level.
Severity Low (mitigated at the sink by LoggingApi masking, per the above); Medium at the point of emission if that mitigation is bypassed or the sink is misconfigured
Confidence verified (code emits unredacted data at the point of logging); unknown (sink-side masking could not be confirmed in source)**

SEC-05 Duplicate security registrations in the External branch

Field Value
Finding Duplicate security registrations in the External branch. SecurityApiHandler is added as IAuthorizationHandler twice in App startup configure services's External branch; ASP.NET Core runs every registered handler, so each [SecurityApiAuthorize] check calls the SecurityApi twice per role*. ISecurityClient is registered Transient then Singleton, so lifetime depends on resolution style.
Severity Low (performance/clarity)
Confidence verified (registrations); inferred (double call)*

SEC-06 Host-location fallback picks the internal auth stack

Field Value
Finding Host-location fallback picks the internal auth stack. If ASPNETCORE_HOST_LOCATION is missing or unrecognized, HostLocation = NotDefined falls into the internal (dual IIS/JWT) branch instead of OIDC. On an externally facing host, a missing env var silently switches auth stacks, and no OIDC middleware runs.
Severity Medium (misconfiguration)
Confidence verified

SEC-07 Edge APIs have no SecurityApi policy provider

Field Value
Finding Edge APIs have no SecurityApi policy provider. Register Edge API auth services does not call AddSecurityApiAuthorizeAttribute. A consumer applying [SecurityApiAuthorize("X")] on an Edge API resolves the SecurityAPI.X policy through the default policy provider, which is not registered for it — authorization throws at run time instead of enforcing the role (fails closed, but as an error rather than a 403)*.
Severity Low
Confidence verified (absence); inferred (runtime behavior)*

SEC-08 DisableAuthServices removes all authentication

Field Value
Finding DisableAuthServices removes all authentication for CoreAPIStartup derivatives with one protected flag; UseAuthorization still runs with no scheme registered. Intended as an opt-out, but a single switch for the whole auth stack.
Severity Low (by design)
Confidence verified

SEC-09 HTTPS redirection can be turned off by a machine env var

Field Value
Finding HTTPS redirection can be turned off by a machine env var. ASPNETCORE_REDIRECT_SSL=false disables UseHttpsRedirection for AppStartup apps (default true, fail-secure); HSTS stays on. CoreAPIStartup/EdgeAPIStartup always redirect.
Severity Low
Confidence verified

SEC-10 Claim parsing in JwtControllerBase

Field Value
Finding Claim parsing in JwtControllerBase (see JWT controller claim helpers): GetUsernameFromClaim takes the first claim whose type contains nameidentifier, splits on @, upper-cases. Any exception yields "Unknown"; a missing claim yields null. Callers using this value for authorization or auditing get a non-validated, substring-matched identity.
Severity Low
Confidence verified

SEC-11 Secure credential bootstrap

Field Value
Finding Secure credential bootstrap. Register DB connection client splits the connectionservicecreds/dbconnectionclient secure setting on \| without validating the format; a malformed value throws IndexOutOfRangeException at first resolution. No credential literal exists in source.
Severity Low (robustness)
Confidence verified

Data-level, segregation-of-duties, and machine authorization

None detected. No ownership validation (CheckValidContext, IsValidPolicyForCurrentUser, ValidateOwnership, or equivalent), no composite identity+resource check, no SOD class/method/privilege-combination query, and no machine-account ($-suffix) SAMAccountName handling exists in this library. This library composes framework-level authorization only.

Security interfaces and contracts

Interface/type Role Source library
ISecurityClient REST client for role/authorization checks, registered by this library on behalf of consumers SAIF.NetCore.AuthService
IAuthorizationPolicyProvider (SecurityApiPolicy) Dynamic authorization policy resolution for SecurityAPI.* policy names SAIF.NetCore.AuthService
IAuthorizationHandler (SecurityApiHandler) Evaluates [SecurityApiAuthorize] requirements via ISecurityClient SAIF.NetCore.AuthService
IIdentityService / IIdentityServiceAccessor Resolves the current identity depending on the active authentication scheme SAIF.NetCore.AuthService
IOidcSettings, RoleClaimTransformer, ILoginRedirectService OIDC settings, role-claim transformation, and login redirection composed into web applications SAIF.NetCore.OIDC
ISystemHashService Application-identity hash used to gate secure-setting retrieval (credential plumbing, not authorization) SAIF.NetCore.SystemHash

Notes

  • This page covers a REST SecurityApi composition root: there is no WCF SecurityService and no AD-group catalog owned by this library (see AD resource catalog above).
  • Severity labels (High/Medium/Low/latent) reflect this page's own risk assessment of the composed security posture, not a value read from any source artifact.

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.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/Middleware/AspNetSessionIdMiddleware.cs
  • src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/Middleware/LegacyBolAuthMiddleware.cs
  • src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/Middleware/DebugRequestHandler.cs
  • src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/Middleware/DynamicErrorHandler.cs
  • src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/Startup/AppStartup.cs, CoreAPIStartup.cs, EdgeAPIStartup.cs, BaseApiStartup.cs
  • src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/IServiceCollectionExtensions.cs
  • src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/IAppBuilderExtensions.cs
  • src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/ControllerBaseExtensions.cs
  • src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/JwtControllerBase.cs
  • src/SAIF.NetCore.AuthService/SAIF.NetCore.AuthService/SAIF.NetCore.AuthService/Extensions/AuthenticationServiceExtensions.cs
  • src/SAIF.NetCore.AuthService/SAIF.NetCore.AuthService/SAIF.NetCore.AuthService/Authorization/SecurityAPIPolicy.cs, SecurityApiHandler.cs
  • src/SAIF.NetCore.ErrorHandler/SAIF.NetCore.ErrorHandler/SAIF.NetCore.ErrorHandler/ErrorHandler.cs
  • src/SAIF.NetCore.Logging/SAIF.NetCore.Logging/SAIF.NetCore.Logging/ExceptionFormatter/WebRequestDetails.cs
  • src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions.Integration.Tests/Controllers/AuthController.cs, ValuesController.cs