Skip to content

Evaluate a SecurityApiRequirement

Evaluates a SecurityApiRequirement by calling the remote SecurityApi once per listed role, using OR semantics across roles.

Security enforcement points: EP-04 (SecurityApiHandler).

Field Value
Operation ID OP-03
Trigger ASP.NET Core authorization middleware evaluates the built policy's SecurityApiRequirement against the current ClaimsPrincipal.
Inputs AuthorizationHandlerContext (current user, requirement), SecurityApiRequirement.ProvidedRoles.
Outputs context.Succeed(requirement) when any role authorizes the user; otherwise the requirement remains unsatisfied.
Primary components SecurityApiHandler.HandleRequirementAsync
Entry point src/SAIF.NetCore.AuthService/SAIF.NetCore.AuthService/SAIF.NetCore.AuthService/Authorization/SecurityApiHandler.cs

Steps

  1. If context.User.Identity.Name is null or whitespace, return without calling context.Succeed — this is an empty-name guard; HandleRequirementAsync never inspects Identity.IsAuthenticated (BR-AUTH-006)
  2. Compute userName as the second \-delimited segment of Identity.Name (Split("\\")[1]) when the name contains a backslash, or the full name if there is no \\ — not necessarily "everything after the last backslash" (BR-AUTH-007)
  3. Split requirement.ProvidedRoles on ,. For each role, await ISecurityClient.IsAuthorized(userName, role); call context.Succeed(requirement) on any true result, without stopping the loop and without ever reversing an earlier Succeed call when a later role returns false (BR-AUTH-008)
  4. 3.1 Normalize the user ID: replace \ with / in userName.
  5. 3.2 Apply the resource-suffix rewrite to role: trim the role; if the trimmed role ends with resource (case-insensitive), replace the last occurrence of resource with the configured SecurityClient:resourcesuffix value; trim the result again.
  6. 3.3 Issue GET /v1/Users/IsAuthorized/{userId}/{suffixRole}.
  7. 3.4 On success, return the deserialized bool. On a non-success response with an inner exception, throw SecurityClientException(message, innerException). On a non-success response without an inner exception, throw SecurityClientException(message) — there is no default-deny false for a failed call.

Origin: Steps 3.1–3.4 are implemented in Check role authorization remotely (SAIF.NetCore.AuthService) and executed on behalf of this operation, describing the library's registered SecurityClient implementation — not every possible ISecurityClient a consumer could substitute.

Process flow

The following diagram visualizes this operation's control flow. context.Succeed(requirement) accumulates across loop iterations — once called for any role, a later role returning false does not undo it; the requirement is satisfied if Succeed was called at least once during the loop.

flowchart TD
    Start([Requirement evaluation]) --> NameCheck{Identity.Name null/whitespace?}
    NameCheck -->|Yes| Deny(["Requirement not satisfied<br/>no remote call made"])
    NameCheck -->|No| Strip["userName = second segment of Split('\\')<br/>or full name if no backslash"]
    Strip --> Loop[For each role in ProvidedRoles]
    Loop --> Call["await ISecurityClient.IsAuthorized user, role<br/>may throw SecurityClientException"]
    Call --> Result{Returned true?}
    Result -->|Yes| Succeed["context.Succeed requirement<br/>accumulated, not reversible"]
    Result -->|No| More{More roles remain?}
    Succeed --> More
    More -->|Yes| Loop
    More -->|No| Final{Succeed called<br/>at least once?}
    Final -->|Yes| Done([Requirement satisfied])
    Final -->|No| Deny2([Requirement not satisfied])

Failure and edge-case behavior

  • A non-success response from any role's remote call throws SecurityClientException from inside the handler (surfaced by the shared response handler described in Check role authorization remotely), surfacing as an unhandled exception rather than a 403*. This is distinct from an ordinary false result, which simply continues the loop.
  • The handler never calls context.Fail(); an unsatisfied requirement denies access purely by omission.
  • Once context.Succeed(requirement) has been called for any role, the requirement is satisfied even if a later role in the same loop returns false; the loop does not short-circuit on success but a later false never reverses the earlier success.

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.AuthService/SAIF.NetCore.AuthService/SAIF.NetCore.AuthService/Authorization/SecurityApiHandler.cs