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

# SAIF.NetCore.LegacyAuth architecture

SAIF.NetCore.LegacyAuth is a narrow bridging library that lets an ASP.NET Core application issue the legacy `SAIFAUTH` credential cookie — the user's name and password, DPAPI-encrypted with the Windows machine key and client-IP entropy — so that a legacy .NET Framework component referred to in source as "SecurityModule" can authenticate the user on the same host; it is not itself an ASP.NET Core authentication handler, and it never reads or validates a request cookie.

## Responsibilities
- Build the tab-delimited `user`/`password` payload and encrypt it with the Windows Data Protection API (DPAPI), scoped to the local machine key and bound to the client's remote IP address as entropy.
- Write the encrypted value as a raw, non-URL-encoded `Set-Cookie: SAIFAUTH=<DPAPI-encrypted-Base64-value>` response header so the legacy reader can decrypt it without first URL-decoding.
- Provide a `ClearCookie` operation that deletes the `SAIFAUTH` cookie using framework-default delete options.
- Expose a small, independently reusable DPAPI encrypt wrapper (`DPAPI`) over the native `crypt32.dll` P/Invoke surface, including Win32 interop structs.

## Component boundaries
- `ILegacyAuthCookieService` / `LegacyAuthCookieService`: the cookie service — builds the credential payload, encrypts it, and writes or deletes the `Set-Cookie` header. Depends on `IHttpContextAccessor` (framework) and `IOidcSettings` (SAIF.NetCore.ServiceContracts, injected but never read — verified directly in source).
- `DPAPI`: a self-contained P/Invoke wrapper over `CryptProtectData`/`CryptUnprotectData` with UTF-8 + Base64 string helpers. Its managed `Decrypt` string/byte overloads are `internal` and unreachable from any consumer in this repository, but both raw native externs — `CryptProtectData` and `CryptUnprotectData` — are declared `public static extern`, so the raw decryption capability remains publicly reachable even though the convenience wrapper is not.
- `KeyType`, `DATA_BLOB`, `CRYPTPROTECT_PROMPTSTRUCT`: supporting enum and Win32 interop structs used by `DPAPI`.
- Contract boundary: `IOidcSettings` is declared in SAIF.NetCore.ServiceContracts and implemented by SAIF.NetCore.OIDC; this library only consumes the interface (and does not invoke any member on it).

## Interaction model
- A consuming web application (inferred to be a login application such as "OnlineLogin"**, since no consumer exists in this repository) registers `ILegacyAuthCookieService`, `IHttpContextAccessor`, and `IOidcSettings` itself — this library ships no `IServiceCollection` registration extension.
- After the application authenticates a user through its normal login flow (inferred to be OIDC-based*, per an excluded source comment referencing "OnlineLogin"), it calls `SetCookie(user, password)` while the plaintext password is still available*, which encrypts the credential pair with DPAPI and appends the `SAIFAUTH` header to the current response.
- The legacy "SecurityModule" reader, which lives outside this repository, must run on the same Windows machine as the issuing application (DPAPI machine-key scope) and must supply the same client IP as entropy to decrypt the cookie**.
- `SAIF.NetCore.ServiceExtensions`' `LegacyBolAuthMiddleware` is a related but independent mechanism: it checks only for the *presence* of a `SAIFAUTH`/`SAIF.BOL.Auth` request cookie and redirects if absent, without referencing this package or decrypting the value — the two libraries share only the cookie-name convention.
- This library makes no API (HTTP/SOAP) calls; its only outbound calls are to the native Windows DPAPI and to ASP.NET Core's own response-header and cookie APIs.

## Architecture diagram
The following diagram shows the cookie-issuance flow from a consuming application's login handler through DPAPI encryption to the response header.

```mermaid
flowchart TD
    A["Consuming app:<br>user authenticates"] --> B["LegacyAuthCookieService.SetCookie(user, password)"]
    B --> C["Build payload:<br>user + TAB + password + TAB"]
    C --> D["Read client remote IP<br>as DPAPI entropy"]
    D --> E["DPAPI.Encrypt:<br>MachineKey scope + IP entropy"]
    E --> F["AddCookieToHeader:<br>raw Base64, no URL-encoding"]
    F --> G["Set-Cookie: SAIFAUTH=...<br>Path=/, HttpOnly"]
    G -.-> H["Legacy SecurityModule<br>(same machine, out of repo)"]
```

## Sequence diagram
The following diagram shows the primary cookie-issuance sequence, including the unused `IOidcSettings` dependency resolved at construction.

```mermaid
sequenceDiagram
    participant App as "Consuming application"
    participant Service as LegacyAuthCookieService
    participant DPAPI
    participant Response as "HttpContext.Response"
    App->>Service: new LegacyAuthCookieService(httpContextAccessor, oidcSettings)
    App->>Service: SetCookie(user, password)
    Service->>Service: Build tab-delimited payload
    Service->>DPAPI: Encrypt(MachineKey, payload, remoteIp)
    DPAPI-->>Service: Base64 ciphertext
    Service->>Response: Append raw Set-Cookie SAIFAUTH header
    Response-->>App: void
```

## Design constraints
- The DPAPI P/Invoke path is hard-coded to `c:\windows\system32\crypt32.dll` with no `[SupportedOSPlatform("windows")]` annotation, even though the package targets cross-platform `net8.0;net10.0` — the first encrypt call's native invocation throws `DllNotFoundException` on non-Windows hosts, but that exception is caught and rewrapped as a generic `System.Exception("DPAPI was unable to encrypt data.")` with the `DllNotFoundException` as its inner cause, rather than surfacing directly to the caller*.
- The issued cookie sets no `Secure`, `SameSite`, `Domain`, or `Expires` attributes, so it is a host-only session cookie that can travel over plain HTTP and in cross-site requests.
- `IOidcSettings` is constructor-injected but never read; callers must still register an implementation or DI construction fails.
- There is no consumer of this package anywhere in this repository; its only known relationship is the shared `SAIFAUTH` cookie-name convention with `LegacyBolAuthMiddleware` in `SAIF.NetCore.ServiceExtensions`, which checks presence only and does not decrypt the value.

## Related documentation
- [Index](index.md)
- [Overview](overview.md)
- [Dependencies](dependencies.md)
- [Operations and processes](operations-processes.md)
- [Business rules](business-rules.md)
- [Security and authorization](security-authorization.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.LegacyAuth/src/SAIF.NetCore.LegacyAuth/SAIF.NetCore.LegacyAuth/ILegacyAuthCookieService.cs`
- `src/SAIF.NetCore.LegacyAuth/src/SAIF.NetCore.LegacyAuth/SAIF.NetCore.LegacyAuth/LegacyAuthCookieService.cs`
- `src/SAIF.NetCore.LegacyAuth/src/SAIF.NetCore.LegacyAuth/SAIF.NetCore.LegacyAuth/DPAPI.cs`
- `src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/Middleware/LegacyBolAuthMiddleware.cs`
