Skip to content

SAIF.NetCore.LegacyAuth business rules

This page captures the 14 business rules enforced by SAIF.NetCore.LegacyAuth's cookie-issuance and DPAPI encryption logic. Rule IDs use a plain, sequentially-numbered scheme for this library (see the rule headings below).

Field Value
Statement The legacy authentication cookie is always named SAIFAUTH.
Trigger SetCookie, ClearCookie
Enforcement LegacyAuthCookieService.COOKIE_NAME constant, used by both SetCookie and ClearCookie.
Outcome The name is fixed and non-configurable; it matches the name LegacyBolAuthMiddleware in SAIF.NetCore.ServiceExtensions checks for.
Source src/SAIF.NetCore.LegacyAuth/src/SAIF.NetCore.LegacyAuth/SAIF.NetCore.LegacyAuth/LegacyAuthCookieService.cs

BR-02 Tab-delimited credential payload

Field Value
Statement The cookie payload is user + TAB + password + TAB (a trailing separator is included).
Trigger SetCookie
Enforcement LegacyAuthCookieService.Encrypt, using the private PW_SEPARATOR = "\t" constant.
Outcome The legacy reader is expected to split on tab; a tab character embedded in the user name or password corrupts parsing*.
Source src/SAIF.NetCore.LegacyAuth/src/SAIF.NetCore.LegacyAuth/SAIF.NetCore.LegacyAuth/LegacyAuthCookieService.cs

BR-03 Client IP as encryption entropy

Field Value
Statement The client's remote IP address is used as DPAPI entropy, binding the cookie to the issuing client IP.
Trigger SetCookie
Enforcement LegacyAuthCookieService.Encrypt reads context.Connection.RemoteIpAddress.ToString() as the entropy value.
Outcome Decryption succeeds only when the identical IP string is presented*.
Source src/SAIF.NetCore.LegacyAuth/src/SAIF.NetCore.LegacyAuth/SAIF.NetCore.LegacyAuth/LegacyAuthCookieService.cs

BR-04 Machine-scope DPAPI encryption

Field Value
Statement Legacy cookie credentials are encrypted with the machine DPAPI scope.
Trigger SetCookie → DPAPI.Encrypt
Enforcement LegacyAuthCookieService.Encrypt passes KeyType.MachineKey; DPAPI.Encrypt(KeyType, byte[], byte[], string) maps it to the CRYPTPROTECT_LOCAL_MACHINE flag.
Outcome The ciphertext can be decrypted by any process on the same machine, and only on that machine.
Source 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

Field Value
Statement The cookie value is written without URL-encoding so that the legacy .NET Framework SecurityModule can decrypt it.
Trigger AddCookieToHeader
Enforcement LegacyAuthCookieService.AddCookieToHeader builds a SetCookieHeaderValue and appends its rendered string directly to the Set-Cookie header, bypassing Response.Cookies.Append's automatic encoding.
Outcome Raw Base64 (including +, /, = characters) is sent in the Set-Cookie header.
Source src/SAIF.NetCore.LegacyAuth/src/SAIF.NetCore.LegacyAuth/SAIF.NetCore.LegacyAuth/LegacyAuthCookieService.cs

Field Value
Statement Cookies written by the service are HttpOnly with Path=/; no Secure, SameSite, Domain, or expiry is set.
Trigger AddCookieToHeader
Enforcement LegacyAuthCookieService.AddCookieToHeader constructs a SetCookieHeaderValue with only Path and HttpOnly populated.
Outcome A host-only session cookie that JavaScript cannot read, but that is sent on every path and can travel over plain HTTP or in cross-site requests.
Source src/SAIF.NetCore.LegacyAuth/src/SAIF.NetCore.LegacyAuth/SAIF.NetCore.LegacyAuth/LegacyAuthCookieService.cs

BR-07 Instance Encrypt defaults to user key scope

Field Value
Statement The instance DPAPI.Encrypt(string) defaults to the user key scope; every other path chooses the scope explicitly.
Trigger new DPAPI().Encrypt(text)
Enforcement DPAPI.Encrypt(string) calls through with KeyType.UserKey.
Outcome User-scoped ciphertext, which differs from the machine scope the cookie service uses (BR-04).
Source src/SAIF.NetCore.LegacyAuth/src/SAIF.NetCore.LegacyAuth/SAIF.NetCore.LegacyAuth/DPAPI.cs

BR-08 Null inputs coerced to empty values

Field Value
Statement Null plaintext, entropy, and description are treated as empty strings.
Trigger Any Encrypt call
Enforcement DPAPI.Encrypt overloads coerce null arguments to "" before encoding.
Outcome No ArgumentNullException is thrown; an empty value is encrypted instead.
Source src/SAIF.NetCore.LegacyAuth/src/SAIF.NetCore.LegacyAuth/SAIF.NetCore.LegacyAuth/DPAPI.cs

BR-09 UTF-8 and Base64 encoding for string encryption

Field Value
Statement String encryption uses UTF-8 for plaintext and entropy, and Base64 for the returned ciphertext.
Trigger String Encrypt overloads
Enforcement DPAPI.Encrypt(KeyType, string, string, string) encodes with Encoding.UTF8 and Convert.ToBase64String.
Outcome The legacy reader must use the same encodings to decrypt successfully*.
Source src/SAIF.NetCore.LegacyAuth/src/SAIF.NetCore.LegacyAuth/SAIF.NetCore.LegacyAuth/DPAPI.cs

Field Value
Statement SetCookie requires both an active ambient HttpContext (resolved from IHttpContextAccessor) and a known remote IP on that context; ClearCookie requires only an active ambient HttpContext and never reads the remote IP. AddCookieToHeader is unaffected — it takes an explicit HttpContext parameter rather than resolving one from the accessor.
Trigger SetCookie, ClearCookie
Enforcement Neither SetCookie nor ClearCookie guards against a null HttpContext from _httpContextAccessor.HttpContext before use; SetCookie additionally dereferences context.Connection.RemoteIpAddress.ToString() without a null guard. ClearCookie only accesses context.Response.Cookies, so it never touches RemoteIpAddress.
Outcome A NullReferenceException is thrown when HttpContext is null for either method, and additionally when RemoteIpAddress is null for SetCookie only.
Source src/SAIF.NetCore.LegacyAuth/src/SAIF.NetCore.LegacyAuth/SAIF.NetCore.LegacyAuth/LegacyAuthCookieService.cs

BR-11 No DPAPI UI prompts permitted

Field Value
Statement Every encrypt/decrypt call made through the managed DPAPI wrapper methods must never prompt or show UI. This guarantee applies only to the managed wrapper: a consumer calling the public CryptProtectData/CryptUnprotectData externs directly supplies its own dwFlags and is not subject to it.
Trigger Every managed Encrypt/Decrypt call
Enforcement DPAPI.cs's managed Encrypt/Decrypt overloads always include the CRYPTPROTECT_UI_FORBIDDEN flag and initialize the prompt struct with no window handle or prompt text before calling the native primitive.
Outcome Calls made through the managed wrapper are suitable for non-interactive server processes; direct calls to the raw externs can enable a UI prompt if the caller chooses to omit the flag.
Source src/SAIF.NetCore.LegacyAuth/src/SAIF.NetCore.LegacyAuth/SAIF.NetCore.LegacyAuth/DPAPI.cs

BR-12 Generic exception wrapping on encryption failure

Field Value
Statement Every failure raised inside the managed byte-array Encrypt(KeyType, byte[], byte[], string) overload's try block surfaces as a generic System.Exception("DPAPI was unable to encrypt data.") wrapping the original cause (a Win32Exception for native API failures). A direct call to the public CryptProtectData extern is not wrapped; it returns the raw native bool result and leaves failure handling to the caller.
Trigger Any exception raised inside the byte-array Encrypt(KeyType, byte[], byte[], string) overload's try block — not InitPrompt (called before the try block starts), the string overload's own Encoding.UTF8.GetBytes/Convert.ToBase64String calls (which run outside the byte overload entirely), or the finally block's cleanup
Enforcement A single catch/rethrow wraps every failure path inside that overload's try block, including a CryptProtectData return of false.
Outcome For the exceptions scoped by the Trigger above (those raised inside the byte-array overload's try block), callers cannot catch a specific exception type; the Win32 error code is only available in the inner exception. Preparation, UTF-8/Base64 conversion, and cleanup failures outside that try block — and any failure from a direct call to the raw extern — propagate unwrapped.
Source src/SAIF.NetCore.LegacyAuth/src/SAIF.NetCore.LegacyAuth/SAIF.NetCore.LegacyAuth/DPAPI.cs

BR-13 Windows-only DPAPI DLL path

Field Value
Statement DPAPI is loaded only from the fixed path c:\windows\system32\crypt32.dll.
Trigger First CryptProtectData call
Enforcement DllImportAttribute on DPAPI.CryptProtectData and CryptUnprotectData hard-codes the absolute DLL path.
Outcome Windows-only behavior; it fails on other operating systems or non-default Windows installation directories. A call through the managed Encrypt/Decrypt wrapper has the resulting DllNotFoundException wrapped per BR-12; a direct call to the raw externs receives the unwrapped DllNotFoundException since BR-12 does not apply to that path.
Source src/SAIF.NetCore.LegacyAuth/src/SAIF.NetCore.LegacyAuth/SAIF.NetCore.LegacyAuth/DPAPI.cs

Field Value
Statement Clearing the cookie uses framework-default delete options (path /).
Trigger ClearCookie
Enforcement LegacyAuthCookieService.ClearCookie calls Response.Cookies.Delete("SAIFAUTH") with no explicit CookieOptions.
Outcome The default delete path matches the issued Path=/, so the browser removes the cookie.
Source src/SAIF.NetCore.LegacyAuth/src/SAIF.NetCore.LegacyAuth/SAIF.NetCore.LegacyAuth/LegacyAuthCookieService.cs

Notes

  • There are no business rules originating in dependency packages: IOidcSettings is injected but never invoked.

Note: Statements marked with * were inferred from code structure, naming conventions, or contextual evidence where direct confirmation was not available in source artifacts.

Sources

  • 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.LegacyAuth/src/SAIF.NetCore.LegacyAuth/SAIF.NetCore.LegacyAuth/ILegacyAuthCookieService.cs