---
title: SAIF.NetCore.AuthService architecture
summary: Architectural responsibilities, boundaries, and interaction patterns.
status: draft
owner: Framework.NCore maintainers
last_reviewed: 2026-10-02
library: SAIF.NetCore.AuthService
reference_type: package
---

# SAIF.NetCore.AuthService architecture

`SAIF.NetCore.AuthService` is an implementation-layer library in the "Auth & Identity" group that configures token- and Windows-based authentication, abstracts the current-user identity, calls a remote SecurityAPI service for role authorization and user/group lookups, and provides a declarative `[SecurityApiAuthorize]` authorization policy; it depends only on the foundation `SAIF.NetCore.ServiceContracts` package.

## Responsibilities
- Wire ASP.NET Core authentication schemes (`AddJwtAuthentication`, `AddIISAuthentication`, `AddDualAuthentication`) and populate `JwtBearerOptions` from environment settings via `ConfigureJwtBearerOptions`.
- Abstract the current-user identity behind `IIdentityService` (ServiceContracts) with two concrete implementations — `TokenIdentityService` (JWT; user identity from the `mailid` claim) and `WinIdentityService` (IIS/NTLM; user identity from `Identity.Name`) — selected per request by `IdentityServiceAccessor` or `MultiIdentityServiceAccessor`.
- Call the remote SecurityAPI service through `ISecurityClient`/`SecurityClient` for role authorization, user-profile retrieval, AD group membership, application authorization groups, and external-user data.
- Provide declarative role authorization: `[SecurityApiAuthorize("roles")]` → dynamic policy `"SecurityAPI.{roles}"` → `SecurityApiPolicy` → `SecurityApiRequirement` → `SecurityApiHandler`, which asks `ISecurityClient.IsAuthorized` for each comma-separated role.
- Acquire and cache outbound OAuth client-credentials access tokens via `OktaTokenClient`/`IAccessTokenClient` for calling downstream APIs.

## Component boundaries
- `Extensions.AuthenticationServiceExtensions`: the composition surface — eight `IServiceCollection`/`AuthenticationBuilder` extension methods, plus the public static mutable `AuthenticationSchemeSelector` field used by both the `"dual"` policy scheme and `MultiIdentityServiceAccessor`.
- `ConfigureJwtBearerOptions`: an `IConfigureNamedOptions<JwtBearerOptions>` that reads the `oauth` environment-settings section through the injected `IEnvironmentClient` (ServiceContracts).
- `Identity` namespace: `BaseIdentityService` (abstract, implements `IIdentityService`), `TokenIdentityService`, `WinIdentityService`, `IdentityServiceAccessor`, and `MultiIdentityServiceAccessor` (implements `IIdentityServiceAccessor`, ServiceContracts).
- `Clients` namespace: `SecurityClient` (implements `ISecurityClient`, calls the remote SecurityAPI through the injected `IHttpJsonClient`, ServiceContracts), `SecurityClientException` (extends `SAIFException`, ServiceContracts), and `OktaTokenClient`/`OktaToken` (implements `IAccessTokenClient`, calls an OAuth token endpoint through a raw `HttpClient`).
- `Authorization` namespace (note: this namespace is `SAIF.NetCore.Services.Authorization`, distinct from the library's `RootNamespace` `SAIF.NetCore.Services.Authentication`*): `SecurityApiAuthorize` (attribute), `SecurityApiPolicy` (implements `IAuthorizationPolicyProvider`), `SecurityApiRequirement` (implements `IAuthorizationRequirement`), `SecurityApiHandler` (extends `AuthorizationHandler<SecurityApiRequirement>`).
- This library implements `IIdentityService`/`IIdentityServiceAccessor` and consumes `IEnvironmentClient`/`IHttpJsonClient`/`IHttpRequestResult`/`IUserProfile`/`ExternalUser`/`SAIFException` (all ServiceContracts), but does not reference `SAIF.NetCore.EnvironmentClient` or `SAIF.NetCore.HttpClients` — the consuming host must register concrete implementations of those interfaces.

## Interaction model
- An inbound request passes through the ASP.NET Core authentication middleware; when `AddDualAuthentication` is used, the `"dual"` policy scheme's `ForwardDefaultSelector` (`AuthenticationSchemeSelector`) inspects the `Authorization` header and forwards to the JWT scheme if it starts with `"Bearer "`, otherwise to the IIS/Windows scheme.
- Authorization middleware evaluates `[SecurityApiAuthorize("roles")]` by resolving the dynamic `"SecurityAPI.{roles}"` policy through `SecurityApiPolicy`, which builds a policy requiring a `SecurityApiRequirement`; `SecurityApiHandler` then calls `ISecurityClient.IsAuthorized(user, role)` for each role (OR semantics — any one role succeeding is sufficient).
- Application code resolves the current identity through `IIdentityServiceAccessor.IdentityService`, which returns `TokenIdentityService` or `WinIdentityService` depending on the authentication scheme in effect; both ultimately call `SecurityClient.IsAuthorized` for remote authorization checks (with `TokenIdentityService` short-circuiting on a local `User.IsInRole` match first).
- `SecurityClient` issues all remote calls through the injected `IHttpJsonClient` (ServiceContracts) against a base URL resolved from the `SecurityClient:ServiceUrl` environment setting; `OktaTokenClient` issues its own token-endpoint calls through a separately constructed `HttpClient`, independent of `IHttpJsonClient`.
- Composition (calling `AddJwtBearer()`, registering `ConfigureJwtBearerOptions`, and selecting which `Add*` extension methods to call) is the consuming application's responsibility; this library's extension methods are building blocks, not an opinionated default pipeline. In the standard stack, `SAIF.NetCore.ServiceExtensions` performs this composition (`RegisterEdgeAPIAuthServices`, `RegisterCoreAPIAuthServices`).
- `SAIF.NetCore.NtlmApiAuth` is the one documented implementation-to-implementation exception in `Architecture.md`: it takes a direct NuGet dependency on this library to inject `ISecurityClient` into its own `NtlmApiAuthorizationService`.

## Architecture diagram
The following diagram shows how `SAIF.NetCore.AuthService` sits between the ASP.NET Core authentication/authorization pipeline, the ServiceContracts interfaces it depends on, and the remote SecurityAPI/OAuth endpoints it calls.

```mermaid
flowchart TD
    REQ["Inbound request"] -->|auth middleware| DUAL["dual policy scheme<br>(AuthenticationSchemeSelector)"]
    DUAL -->|"JWT scheme marker"| JWT["JwtBearer scheme<br>(ConfigureJwtBearerOptions)"]
    DUAL -->|otherwise| IIS["IIS / Windows scheme"]
    JWT --> IDSVC["IIdentityServiceAccessor<br>→ TokenIdentityService"]
    IIS --> IDSVC2["IIdentityServiceAccessor<br>→ WinIdentityService"]
    ATTR["[SecurityApiAuthorize]"] --> POLICY["SecurityApiPolicy<br>(IAuthorizationPolicyProvider)"]
    POLICY --> HANDLER["SecurityApiHandler<br>(AuthorizationHandler)"]
    HANDLER -->|IsAuthorized per role| SECCLIENT["SecurityClient<br>(ISecurityClient)"]
    IDSVC -->|IsAuthorized fallback| SECCLIENT
    IDSVC2 --> SECCLIENT
    SECCLIENT -->|IHttpJsonClient.GetAsync| API["SecurityAPI<br>(external REST service)"]
    ENVC["IEnvironmentClient<br>(ServiceContracts contract)"] --> JWT
    ENVC --> SECCLIENT
    OKTA["OktaTokenClient<br>(IAccessTokenClient)"] -->|raw HttpClient POST| OAUTH["OAuth token endpoint<br>(external)"]
    NTLM["SAIF.NetCore.NtlmApiAuth<br>(documented exception)"] -.->|NuGet ref| SECCLIENT
```

## Sequence diagram
The following diagram shows the primary request/response sequence for a `[SecurityApiAuthorize("role")]`-protected endpoint.

```mermaid
sequenceDiagram
    participant Client
    participant AuthZ as ASP.NET Core authorization
    participant Policy as SecurityApiPolicy
    participant Handler as SecurityApiHandler
    participant SecClient as SecurityClient
    participant API as SecurityAPI (external)
    Client->>AuthZ: Request SecurityApiAuthorize(role)
    AuthZ->>Policy: GetPolicyAsync("SecurityAPI.role")
    Policy-->>AuthZ: AuthorizationPolicy(SecurityApiRequirement)
    AuthZ->>Handler: HandleRequirementAsync(requirement)
    Handler->>SecClient: IsAuthorized(userId, "role")
    SecClient->>API: GET /v1/Users/IsAuthorized/{userId}/{suffixRole}
    API-->>SecClient: bool result
    SecClient-->>Handler: bool result
    Handler-->>AuthZ: context.Succeed(requirement) or no-op
    AuthZ-->>Client: 200 OK or 403 Forbidden
```

## Design constraints
- No project references between libraries in this repository; the only cross-library dependency is the `SAIF.NetCore.ServiceContracts` NuGet package, consistent with the repository-wide layering in [`Architecture.md`](../../../Architecture.md).
- `AuthenticationSchemeSelector` is a `public static` mutable field on `AuthenticationServiceExtensions`. `AddDualAuthentication()` captures its delegate value at configuration time into the `"dual"` policy scheme's `ForwardDefaultSelector`, while `MultiIdentityServiceAccessor` reads the static field fresh on every invocation; reassigning the field after startup changes `MultiIdentityServiceAccessor`'s next resolution immediately, but does not retroactively change the `ForwardDefaultSelector` delegate already captured by an already-configured `"dual"` scheme*.
- `OktaTokenClient` is registered as a singleton but holds mutable token state and settable credential properties without synchronization, constructs a new `HttpClient` per token refresh, and blocks synchronously (`.Wait()`/`.Result`); it depends on `IHttpContextAccessor.HttpContext` only to build `redirect_uri` on the refresh path — a cached, still-valid token is returned without touching `HttpContext`, so calling `GetToken()` outside a request context only throws `NullReferenceException` when a refresh is actually needed*. The refreshed token value is also cached and returned without any additional post-refresh validity check.
- `BaseIdentityService.GetUser()`/`GetUser<T>()` use sync-over-async (`.Result`); `TokenIdentityService.UserId` throws `NullReferenceException` if no claim type contains `mailid`; `BaseIdentityService.Claims`/`IsAuthenticated` dereference `Identity` without a null check.
- `AddTokenIdentityService()`/`AddWinIdentityService()` each register exactly one `IIdentityService` interface mapping (to `TokenIdentityService` or `WinIdentityService` respectively). `AddIdentityAccessor()` is a different, multi-scheme registration method: it always registers both `TokenIdentityService` and `WinIdentityService` as concrete self-bindings plus `MultiIdentityServiceAccessor`, rather than mapping a single `IIdentityService` interface. `MultiIdentityServiceAccessor` resolves one of those two concrete types from the service provider by scheme; if a consumer mixes registration methods such that only one of the two concrete types is registered, `GetService` returns `null` for the other scheme*.
- Composition (which authentication scheme(s) to enable, and registering `IEnvironmentClient`/`IHttpJsonClient` implementations) is left entirely to the consuming application or to `SAIF.NetCore.ServiceExtensions`; this library performs no automatic registration of its own dependencies.

## Related documentation
- [Index](index.md)
- [Overview](overview.md)
- [Dependencies](dependencies.md)
- [Configuration](configuration.md)
- [Operations and processes](operations-processes.md)
- [Business rules](business-rules.md)
- [Security and authorization](security-authorization.md)
- [Service registration](service-registration.md)
- [API reference](api/index.md)
- [SAIF.NetCore.ServiceContracts overview](../saif-netcore-servicecontracts/overview.md)

> **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.AuthService/SAIF.NetCore.AuthService/SAIF.NetCore.AuthService/SAIF.NetCore.AuthService.csproj`
- `src/SAIF.NetCore.AuthService/SAIF.NetCore.AuthService/SAIF.NetCore.AuthService/ConfigureJwtBearerOptions.cs`
- `src/SAIF.NetCore.AuthService/SAIF.NetCore.AuthService/SAIF.NetCore.AuthService/Authorization/SecurityApiAuthorize.cs`, `SecurityApiHandler.cs`, `SecurityAPIPolicy.cs`, `SecurityApiRequirement.cs`
- `src/SAIF.NetCore.AuthService/SAIF.NetCore.AuthService/SAIF.NetCore.AuthService/Clients/OktaTokenClient.cs`, `SecurityClient.cs`, `SecurityClientException.cs`
- `src/SAIF.NetCore.AuthService/SAIF.NetCore.AuthService/SAIF.NetCore.AuthService/Extensions/AuthenticationServiceExtensions.cs`
- `src/SAIF.NetCore.AuthService/SAIF.NetCore.AuthService/SAIF.NetCore.AuthService/Identity/BaseIdentityService.cs`, `IdentityServiceAccessor.cs`, `MultiIdentityServiceAccessor.cs`, `TokenIdentityService.cs`, `WinIdentityService.cs`
- `src/SAIF.NetCore.AuthService/SAIF.NetCore.AuthService/SAIF.NetCore.AuthService/Interfaces/IAccessTokenClient.cs`, `ISecurityClient.cs`
- `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/IIdentityService.cs`, `IEnvironmentClient.cs`, `IHttpJsonClient.cs`, `IHttpRequestResult.cs`, `SAIFException.cs`, `IUserProfile.cs`, `UserProfile.cs`, `ExternalUser.cs`
- `src/SAIF.NetCore.NtlmApiAuth/src/SAIF.NetCore.NtlmApiAuth/SAIF.NetCore.NtlmApiAuth.csproj`
- `Architecture.md`
