---
title: SaifAuthorizeAttribute
summary: API reference for SaifAuthorizeAttribute.
status: draft
owner: andmil
last_reviewed: 2026-09-15
library: SAIF.Web.API
reference_type: package
---

# SaifAuthorizeAttribute

`SaifAuthorizeAttribute` is a sealed `System.Web.Http.AuthorizeAttribute` subclass that a consuming Web API application decorates controllers or actions with to gate access by SAIF security-resource role names; no such decoration exists inside this library.* Unlike the base `AuthorizeAttribute`, it never calls `base.IsAuthorized`, so the framework's own `Identity.IsAuthenticated` check is bypassed entirely; instead it resolves `ISecurityApiClient` and `IEnvironmentService` per request from the Web API dependency scope and delegates the authorization decision to `ISecurityApiClient.IsAuthorized(user, role)` for each configured role, with OR semantics across roles. It fails closed (throws) when either dependency is unregistered, and it is the concrete implementation of this library's attribute-based authorization enforcement point — see [security-authorization.md](../../security-authorization.md).

## Purpose
- Provides declarative, role-name-based authorization for Web API controllers/actions, delegating the actual decision to a remote `ISecurityApiClient` rather than evaluating roles locally — see [BR-WEBAPI-017](../../business-rules.md#br-webapi-017-role-evaluation-uses-or-semantics).
- Fails closed when its required collaborators (`ISecurityApiClient`, `IEnvironmentService`) are not registered with the host's dependency resolver, surfacing as an unhandled exception rather than silently allowing or denying — see [BR-WEBAPI-014](../../business-rules.md#br-webapi-014-authorization-fails-closed-on-missing-dependencies).
- Authorizes every request unconditionally when constructed with no roles, and denies requests where the resolved user identity is null/blank — see [BR-WEBAPI-015](../../business-rules.md#br-webapi-015-no-roles-specified-authorizes-unconditionally) and [BR-WEBAPI-016](../../business-rules.md#br-webapi-016-unknown-user-is-not-authorized).
- Logs unauthorized-request details (when the user is known) and returns a fixed `401 Unauthorized` response, rather than `403 Forbidden`, on denial — see [BR-WEBAPI-018](../../business-rules.md#br-webapi-018-unauthorized-requests-are-logged-and-answered-with-401).
- Normalizes constructor-supplied role names by trimming each entry before joining them into the inherited `Roles` property — see [BR-WEBAPI-020](../../business-rules.md#br-webapi-020-role-names-are-trimmed-and-normalized).

## Context
- Namespace: `SAIF.Web.API`
- Assembly/package: `SAIF.Web.API` (NuGet)
- Base type: `System.Web.Http.AuthorizeAttribute`
- Sealed class; decorated with `[SuppressMessage("Microsoft.Design", "CA1018:MarkAttributesWithAttributeUsage")]`, meaning no explicit `AttributeUsage` is declared and the default (all targets, single use, inherited) applies.

## Public members
| Member | Signature | Purpose |
| --- | --- | --- |
| `.ctor` | `public SaifAuthorizeAttribute(params string[] roles)` | Convenience constructor; delegates to the two-argument constructor with `forceProductionRole: false`. |
| `.ctor` | `public SaifAuthorizeAttribute(bool forceProductionRole, params string[] roles)` | Trims each supplied role and joins them with `,` into the inherited `AuthorizeAttribute.Roles` property; stores `forceProductionRole` in the private `_ForceProductionRole` field, which is never subsequently read anywhere in this library — see [BR-WEBAPI-020](../../business-rules.md#br-webapi-020-role-names-are-trimmed-and-normalized). |

## Private and protected members

<!-- SELF-CONTAINMENT: documented here because the analysis findings capture these members' full decision logic and business-rule linkage, and they are central to this type's authorization behavior. -->

| Member | Signature | Purpose |
| --- | --- | --- |
| `IsAuthorized` | `protected override bool IsAuthorized(HttpActionContext actionContext)` | Core authorization decision. Validates `actionContext` is non-null; resolves `ISecurityApiClient` and `IEnvironmentService` from the request dependency scope, throwing `MissingDependencyException` for either if unresolved; returns `true` immediately if no roles were configured; reads `environmentService.User.Identity.Name` (dereferenced without a null check) and returns `false` if the resolved user name is null/blank; otherwise splits the configured roles on `,` and returns `true` if `ISecurityApiClient.IsAuthorized(user, role)` succeeds for **any** role (short-circuiting on the first success) — see [BR-WEBAPI-014](../../business-rules.md#br-webapi-014-authorization-fails-closed-on-missing-dependencies) through [BR-WEBAPI-017](../../business-rules.md#br-webapi-017-role-evaluation-uses-or-semantics). |
| `HandleUnauthorizedRequest` | `protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)` | Invoked by the base `AuthorizeAttribute` when `IsAuthorized` returns `false`. Validates `actionContext`; resolves `ILogService` and `IEnvironmentService` from the request dependency scope, both `Enforce.ArgumentNotNull`-checked (unlike `IsAuthorized`, a missing `ILogService` here throws rather than being tolerated); reads the resolved user ID; if the user ID is known, logs a `SecurityException` describing the denial via `ILogService.LogException`; sets `actionContext.Response` to `401 Unauthorized` with a body that echoes the resolved user name — see [BR-WEBAPI-018](../../business-rules.md#br-webapi-018-unauthorized-requests-are-logged-and-answered-with-401). |
| `GetContext` | `private string GetContext(HttpActionContext actionContext, IEnvironmentService environmentService)` | Formats the log state string as `User: {userId}\nPage: {requestUri}`, used as the `state` argument to `ILogService.LogException` in `HandleUnauthorizedRequest`. |
| `_ForceProductionRole` | `private bool` field | Set from the `forceProductionRole` constructor parameter but never read anywhere in this library — dead state with no observable runtime effect. |

## Behavioral notes
- `IsAuthorized` never calls `base.IsAuthorized(actionContext)`, so the base `AuthorizeAttribute`'s built-in `Identity.IsAuthenticated` check and `Users` property filtering are bypassed entirely; an instance constructed with no roles authorizes every request that reaches the filter, including unauthenticated ones — see [BR-WEBAPI-015](../../business-rules.md#br-webapi-015-no-roles-specified-authorizes-unconditionally).*
- Missing-dependency failures (`MissingDependencyException`) are unhandled at this layer and propagate up the pipeline, typically surfacing to the caller as a `500`-class response rather than a `401`/`403`.*
- In `HandleUnauthorizedRequest`, logging is deliberately skipped when the resolved user ID is null/empty — a source comment notes the user may be null before credentials are demanded, i.e. the initial unauthenticated challenge round-trip is not logged as a security event.
- The response body set in `HandleUnauthorizedRequest` discloses the resolved account name (`"The user '{user}' is not authorized."`) to the caller.
- Authorization is evaluated against `IEnvironmentService.User.Identity.Name`, a separately resolved identity source, rather than `actionContext.RequestContext.Principal`; whether the host guarantees these represent the same principal could not be determined from this repository.**
- `_ForceProductionRole` is dead code: the two-argument constructor's `forceProductionRole` parameter has no effect on any runtime behavior in this library.

## Constraints and usage notes
- Both `ISecurityApiClient` and `IEnvironmentService` must be registered with the hosting application's `IDependencyResolver`; if either is absent, every decorated action throws on first authorization attempt rather than denying gracefully — see [security-authorization.md](../../security-authorization.md).
- No AD resource/role names are hard-coded in this library; all resource names are supplied by the consuming application via the constructor's `params string[] roles` — see [security-authorization.md](../../security-authorization.md).
- Role matching uses OR semantics: a caller authorized against **any one** of the configured roles passes.

> **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.Web.API/SaifAuthorizeAttribute.cs`
