---
title: Authorize a request against SAIF roles
summary: Decide whether the current caller holds any of the roles/resources declared on [SaifAuthorize], delegating the decision to ISecurityApiClient.
status: draft
owner: andmil
last_reviewed: 2026-09-15
library: SAIF.Web.API
reference_type: package
operation_id: OP-08
trigger: Web API authorization-filter pipeline on any decorated controller/action.
---

# Authorize a request against SAIF roles

`SaifAuthorizeAttribute.IsAuthorized(HttpActionContext)` decides whether the current caller holds at least one of the roles/resources declared on the attribute, delegating the actual decision to `ISecurityApiClient`.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-08 |
| **Trigger** | Web API authorization-filter pipeline on any decorated controller/action. |
| **Inputs** | `HttpActionContext`, constructed `Roles` string, request-scoped `ISecurityApiClient`/`IEnvironmentService` |
| **Outputs** | `bool` |
| **Primary components** | `SaifAuthorizeAttribute.IsAuthorized(HttpActionContext)` |
| **Entry point** | `src/SAIF.Web.API/SaifAuthorizeAttribute.cs` |

## Steps

0. *(construction)* Roles passed to the constructor are `Trim()`-ed and joined with `,` into the inherited `AuthorizeAttribute.Roles` property ([BR-WEBAPI-020](../business-rules.md#br-webapi-020-role-names-are-trimmed-and-normalized)). `forceProductionRole` is stored in `_ForceProductionRole` and **never read**.
1. `Enforce.ArgumentNotNull(actionContext, "actionContext")` ([BR-WEBAPI-014](../business-rules.md#br-webapi-014-authorization-fails-closed-on-missing-dependencies)).
   - Throws when null.
   - `SAIF.Exceptions.Enforce` is implemented in `SAIF.Framework.Core`; its internal steps could not be embedded — the package is not indexed for this run.**
2. Resolve `ISecurityApiClient` and `IEnvironmentService` from `actionContext.Request.GetDependencyScope()` ([BR-WEBAPI-014](../business-rules.md#br-webapi-014-authorization-fails-closed-on-missing-dependencies)).
3. If `ISecurityApiClient` is null → `throw new MissingDependencyException("ISecurityApiClient")` ([BR-WEBAPI-014](../business-rules.md#br-webapi-014-authorization-fails-closed-on-missing-dependencies)).
   - Fail-closed; surfaces as a 500 via the pipeline.*
4. If `IEnvironmentService` is null → `throw new MissingDependencyException("IEnvironmentService")` ([BR-WEBAPI-014](../business-rules.md#br-webapi-014-authorization-fails-closed-on-missing-dependencies)).
   - Fail-closed.
5. If `!Roles.Any()` (the joined role string is empty) → **return `true`** ([BR-WEBAPI-015](../business-rules.md#br-webapi-015-no-roles-specified-authorizes-unconditionally)).
   - Decorating with no roles authorizes every caller reaching the filter.
6. Read `user = environmentService.User.Identity.Name` ([BR-WEBAPI-016](../business-rules.md#br-webapi-016-unknown-user-is-not-authorized)).
   - `environmentService.User` is dereferenced without a null check.
7. If `user.IsNullOrWhiteSpace()` → **return `false`** ([BR-WEBAPI-016](../business-rules.md#br-webapi-016-unknown-user-is-not-authorized)).
   - Returns `false` → [Handle an unauthorized request](op-09-handle-an-unauthorized-request.md).
8. Strip all spaces from `Roles`, split on `,`, and return `true` if **any** role satisfies `securityApiClient.IsAuthorized(user, role)` ([BR-WEBAPI-017](../business-rules.md#br-webapi-017-role-evaluation-uses-or-semantics)).
   - The `Any` LINQ evaluation short-circuits on the first success; up to *N* remote calls otherwise.*
   - `ISecurityApiClient.IsAuthorized(user, role)` is implemented in `SAIF.Framework.Core`; its internal steps could not be embedded — the package is not indexed for this run.**
   - A `false` result also flows to [Handle an unauthorized request](op-09-handle-an-unauthorized-request.md).

## Process flow

```mermaid
flowchart TD
	subgraph MainFlow["Authorization decision<br>path"]
		Start(["Authorization filter<br>pipeline"]) --> NullCheck["Enforce.ArgumentNotNull(<br>actionContext)"]
		NullCheck --> Resolve["Resolve<br>ISecurityApiClient /<br>IEnvironmentService"]
		Resolve --> SecNull{ISecurityApiClient null?}
		SecNull -->|No| EnvNull{"IEnvironmentService<br>null?"}
		EnvNull -->|No| RolesEmpty{"Roles empty?"}
		RolesEmpty -->|No| ReadUser["user =<br>environmentService.User.<br>Identity.Name"]
		ReadUser --> UserEmpty{"user null/whitespace?"}
		UserEmpty -->|No| AnyRole["Any role:<br>securityApiClient.<br>IsAuthorized(user,<br>role)?"]
	end
	SecNull -->|Yes| ThrowSec["throw<br>MissingDependency<br>Exception"]
	EnvNull -->|Yes| ThrowEnv["throw<br>MissingDependency<br>Exception"]
	RolesEmpty -->|Yes| AllowAll["return true"]
	UserEmpty -->|Yes| Deny["return false"]
	AnyRole -->|True| Allow[return true]
	AnyRole -->|False| Deny2["return false"]
```

## Failure and edge-case behavior
- A missing `ISecurityApiClient` or `IEnvironmentService` throws `MissingDependencyException`, which surfaces as an unhandled server error (not a 401/403).*
- `environmentService.User` is dereferenced without a null guard; an environment service that returns a `null` `User` would raise `NullReferenceException`.

> **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.

## Related documentation
- [Operations and processes](../operations-processes.md)
- [Business rules](../business-rules.md)

## Sources
- `src/SAIF.Web.API/SaifAuthorizeAttribute.cs`
