Explainable Deny: Authorization Decision Evidence for SOC 2-Style Access Reviews

- Share:





2938 Members
A customer questionnaire lands. Or an incident page fires at 2 AM. Someone asks whether only the right people can reach customer data — and whether you can prove it. IdP logs show login and MFA. They do not show why the application allowed or denied a specific action on a specific tenant and resource.
That gap is where SOC 2-style access reviews get hard. The AICPA Trust Services Criteria are outcome- and control-based: they evaluate whether logical-access and monitoring controls are designed and operating effectively. They do not prescribe a canonical authorization log schema or a required field checklist. Reviewers ask for evidence that your controls work. Decision provenance is one of the strongest forms of that evidence.
In practice, access-control conversations become concrete:
These questions often align with CC6- and CC7-style logical-access and monitoring concerns. Decision logs help because they show the application actually enforced the intended model — not only that someone authenticated.
If authorization is scattered across application code, UI checks, database filters, support tooling, and one-off exceptions, review time becomes painful. You may describe the intended model and still struggle to prove effective access for a sampled user, tenant, or sensitive action.
That is the core problem behind Stop Rebuilding Permissions: most teams do not fail because they cannot create roles. They fail because authorization logic becomes fragmented and unexplainable.
And as we covered in Why Was This Allowed?, the important security question is not only "who logged in?" It is "why was this action allowed?"
The next level is explainable deny. Mature authorization evidence should show both sides: legitimate access was allowed for the right reason, and unauthorized access was denied for the right reason.
For a concise AuthN vs AuthZ framing, see also Authentication vs Authorization.
Okta, Microsoft Entra ID, Auth0, and similar identity providers are critical systems of record for identity. They can show login events, MFA challenges, SSO assignments, group lifecycle changes, user suspension, deprovisioning, and authentication failures.
You need that evidence.
But AuthN logs are not AuthZ evidence.
An IdP can show that Alice authenticated successfully, used MFA, and belonged to the support-engineering group. It usually cannot prove why Alice was allowed to view customer_123 inside your application but denied access to customer_456.
It typically cannot explain:
allow or denyThis is where teams get exposed. They have clean IdP evidence, but the application decision is buried in code:
if user.is_admin:
allow()
elif user.team_id == document.team_id:
allow()
elif user.email.endswith("@customer.com") and feature_enabled:
allow()
else:
deny()
That may work functionally. It does not scale as evidence.
When someone asks, "How do you know only authorized users can access customer data?" the answer cannot be, "We use SSO." SSO proves identity. It does not prove application-level least privilege.
The evidence needs to connect identity to policy to decision to resource.
Authorization systems should produce decision provenance: a clear chain from the requester through action, resource, tenant, policy, and enforcement point to the final allow or deny.
Useful provenance covers:
This is not verbose logging for its own sake. It is how security teams answer hard questions under pressure.
An allow decision tells you what access existed. A deny decision tells you what boundary held.
Examples:
A useful decision record might look like:
{
"timestamp": "2026-02-12T14:03:22Z",
"decision_id": "dec_01HR7ZK9VJ3Y6Q",
"result": "deny",
"subject": {
"type": "user",
"id": "usr_123",
"email": "alice@example.com",
"groups": ["support-engineering"]
},
"action": "customer.billing.export",
"resource": {
"type": "customer_account",
"id": "cust_456",
"tenant_id": "tenant_456"
},
"context": {
"request_ip": "203.0.113.10",
"environment": "production",
"session_mfa": true
},
"policy": {
"version": "2026-02-10.3",
"matched_rule": "support_export_requires_assigned_tenant_and_billing_scope",
"reason": "subject is not assigned to tenant_456 and lacks billing_export permission"
},
"enforcement_point": {
"service": "admin-api",
"endpoint": "POST /customers/{id}/billing/export"
}
}
This is illustrative, not literal Permit output. The point is the evidence shape: subject, action, resource, policy, result, reason, enforcement point.
For practical audit-log handling in Permit, see audit log types and filtering. For inspecting decision paths during policy development, see debug mode — use it to debug, not as your production evidence pipeline.
AI does not remove authorization evidence requirements. It makes the evidence chain more important.
In a traditional app, the request path is often:
human → application → resource
With AI agents, the path becomes:
human → agent → tool → resource
That introduces new questions: which human initiated or delegated the task? Which agent acted? Which tool was invoked? What resource did the tool touch? Was the action within the human's permissions and the agent's delegated scope?
An AI agent should not become a privilege amplifier. If a support engineer cannot export billing data for a tenant, an agent acting for that engineer should not be able to export it either.
For review and incident response, "the AI did it" is not an explanation. You need provenance:
Alice requested task X.
Agent Y interpreted the task.
Tool Z attempted action A on resource R.
Policy P evaluated the human, agent, tool, tenant, and context.
Decision was deny because the delegation did not include that action.
When agents call MCP servers, the same idea applies at the tool boundary. Permit MCP Gateway sits between MCP clients and MCP servers and logs authorization decisions with context such as agent identity, human identity, tool, MCP server, timestamp, and allow/deny result. That is explainable deny for agentic tool use — without inventing a new audit theater.
When preparing for a SOC 2-style review, customer security questionnaire, or internal access-control assessment, assemble a focused evidence pack. Not because one framework mandates a single format — because it makes the conversation concrete.
Explain how application authorization works: resource types, actions, roles, attributes, relationships, tenant boundaries, service identities, and any break-glass or agent flows.
Keep it readable. Example:
Customer support users can view accounts only for assigned tenants.
Billing export requires both tenant assignment and billing_export permission.
Engineering administrators cannot access customer production data by default.
Break-glass access requires approval, time-bound elevation, and logging.
Also note where policy lives and how changes are controlled — repository or config source, approval process, deployment history, rollback. That connects authorization to change management.
For sampled users, roles, tenants, or service identities, show why access exists, that owners reviewed it, and that leavers or movers lost it.
Useful artifacts include role or tenant assignment history, approval tickets, periodic review outcomes, HR or directory events, IdP deactivation, and application role or tenant removal. Cover effective application entitlements — not only SSO group membership. Do not assume IdP deactivation automatically clears every app-side assignment.
For a sampled user, tenant, or sensitive action, show decision logs that demonstrate enforcement: allow decisions for legitimate access, deny decisions for unauthorized attempts, plus resource/tenant identifiers, action, policy reference, enforcement point, and timestamp.
That is how you prove the control operated at runtime — not only that a role existed on paper.
Exceptions are normal. Hidden exceptions are the problem.
Track who requested and approved elevated access, what scope was granted, why, how long it lasted, what actions were taken, when it was removed, and whether activity was reviewed afterward. Break-glass without logging is standing privilege with better branding.
Decision logs matter when teams can retrieve and use them: storage location, access controls, retention, alerts for sensitive denies or unusual allows, and correlation with IdP and application logs. Reviewers care whether you can explain a sampled decision under time pressure.
Permit.io is built for teams that need application authorization to be externalized, enforceable, and explainable without rebuilding permissions in every service.
The architecture matters. Permit uses a hybrid model: a cloud control plane for policy management, and a Policy Decision Point (PDP) that can run close to your application. With a locally deployed PDP, there is no remote SaaS call on the hot path — decisions evaluate against local policy and authorization data. OPAL distributes policy and data updates so PDPs stay current without every service inventing its own sync layer.
That hybrid model matters for security and operations:
That is the difference between "we have permissions in code" and "we can explain our authorization system."
Permit helps teams support centralized policy management; RBAC, ABAC, and ReBAC-style models; tenant-aware authorization; application-level decision logging; policy debugging; clean AuthN/AuthZ separation; and least privilege for users, services, and agents.
The goal is not to claim any tool can "make you SOC 2 compliant." That is not how SOC 2 works. The goal is architecture and evidence that support the controls you say you operate.
Permit.io is SOC 2 Type II certified. It supports HIPAA-regulated workloads (with a BAA where applicable) and privacy practices aligned with GDPR and CCPA/CPRA obligations described on our Trust Center. Using Permit does not by itself guarantee any audit outcome.
Start with the Permit audit log docs for production decision evidence. Use debug mode only to inspect and debug authorization decisions during development — not as the production evidence path. Or try Permit in your application.

Co-Founder / CEO at Permit.io