Skip to content

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.

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.
  • 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.
  • Authorizes every request unconditionally when constructed with no roles, and denies requests where the resolved user identity is null/blank — see BR-WEBAPI-015 and BR-WEBAPI-016.
  • 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.
  • Normalizes constructor-supplied role names by trimming each entry before joining them into the inherited Roles property — see BR-WEBAPI-020.

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.

Private and protected members

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 through BR-WEBAPI-017.
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.
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.*
  • 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.
  • 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.
  • 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