<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: kt</title>
    <description>The latest articles on DEV Community by kt (@kanywst).</description>
    <link>https://dev.to/kanywst</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3700180%2F04651b63-c6a1-4069-b356-a0f85c17e0bb.png</url>
      <title>DEV Community: kt</title>
      <link>https://dev.to/kanywst</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kanywst"/>
    <language>en</language>
    <item>
      <title>xDS Deep Dive: Dissecting the "Nervous System" of the Service Mesh</title>
      <dc:creator>kt</dc:creator>
      <pubDate>Mon, 20 Apr 2026 15:40:41 +0000</pubDate>
      <link>https://dev.to/kanywst/xds-deep-dive-dissecting-the-nervous-system-of-the-service-mesh-3m5i</link>
      <guid>https://dev.to/kanywst/xds-deep-dive-dissecting-the-nervous-system-of-the-service-mesh-3m5i</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;I was debugging Istio routing the other day, and honestly, I had a moment where I felt a bit "creeped out."&lt;/p&gt;

&lt;p&gt;You tweak a &lt;code&gt;VirtualService&lt;/code&gt; YAML file, hit &lt;code&gt;kubectl apply&lt;/code&gt;, and within seconds, the routing rules across hundreds of Envoy proxies scattered throughout the cluster switch over perfectly.&lt;/p&gt;

&lt;p&gt;There's no process restart. You aren't running &lt;code&gt;nginx -s reload&lt;/code&gt;. Rolling out configuration changes to thousands of hosts happens in seconds, and &lt;strong&gt;even if you push a completely broken YAML, it magically rolls back to a safe state on its own.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It feels like magic, but naturally, there's an incredibly gritty mechanism running behind the scenes. That mechanism is &lt;strong&gt;xDS (xDiscovery Service)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You might think of it as just "Envoy's config protocol," but xDS has long escaped Envoy's borders. Today, it's standardized as the &lt;strong&gt;"Universal Data Plane API" (the lingua franca of L4/L7 networking)&lt;/strong&gt; by the CNCF xDS API Working Group, heavily used by gRPC (Proxyless) and Cilium's ztunnel. As of 2026, it is the absolute most important protocol to understand if you want to talk about service mesh.&lt;/p&gt;

&lt;p&gt;We are going to read through this from top to bottom—covering why static configuration is a nightmare, the dependency chain of LDS/RDS/CDS/EDS, and the ACK/NACK rollback mechanism that saves us from outages.&lt;/p&gt;




&lt;h2&gt;
  
  
  0. Prerequisites: Why use an "API" to push configurations?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Envoy and Protocol Buffers
&lt;/h3&gt;

&lt;p&gt;The decisive difference between Envoy and traditional reverse proxies like Nginx or HAProxy is that Envoy was built from the ground up on the premise that &lt;strong&gt;configuration will be injected externally, in real-time, via gRPC.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Envoy doesn't do service discovery on its own. The control plane (like Istio's &lt;code&gt;istiod&lt;/code&gt;) watches Kubernetes &lt;code&gt;Pods&lt;/code&gt; and &lt;code&gt;Services&lt;/code&gt;, translates them into strongly-typed &lt;strong&gt;Protocol Buffers (proto3)&lt;/strong&gt; messages that Envoy understands, and streams them over HTTP/2 gRPC. By using gRPC streaming instead of JSON polling, it achieves incredibly low latency and type safety.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Despair of Static Configuration and the Awakening of xDS
&lt;/h2&gt;

&lt;p&gt;If you were to configure Envoy statically, you’d end up writing an &lt;code&gt;envoy.yaml&lt;/code&gt; file like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;static_resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;listeners&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my_listener&lt;/span&gt;
    &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;socket_address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;0.0.0.0&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;port_value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;8080&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;# ...filter configs...&lt;/span&gt;
  &lt;span class="na"&gt;clusters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my_backend&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;STRICT_DNS&lt;/span&gt;
    &lt;span class="na"&gt;load_assignment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;cluster_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my_backend&lt;/span&gt;
      &lt;span class="na"&gt;endpoints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;lb_endpoints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;socket_address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;10.0.1.15&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;port_value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;8080&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the idyllic days when you only had 10 Pods, this was fine. But in today's Kubernetes environments, Pods scale every second, they fluctuate due to HPA, and IPs change constantly as nodes are retired. &lt;strong&gt;Every time a backend IP changes, are you going to rewrite the config files for all proxies and restart their processes?&lt;/strong&gt; That's pure insanity. Connections would drop, latency would spike, and the system would collapse.&lt;/p&gt;

&lt;p&gt;That is exactly why dynamic configuration (xDS) is mandatory.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuif9wek8lyoqtkmp4gbp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuif9wek8lyoqtkmp4gbp.png" alt="dynamic configuration" width="800" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With xDS, Envoy can seamlessly start routing traffic to new Pod IPs with absolutely zero restarts.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The Core 5 Discovery Services
&lt;/h2&gt;

&lt;p&gt;The "x" in xDS is a wildcard. The protocol is heavily segmented into five major services (APIs) based on the scope of the configuration.&lt;/p&gt;

&lt;p&gt;These aren't just parallel configuration items—they have &lt;strong&gt;strict dependencies (pointers)&lt;/strong&gt; on one another. Grasping this dependency chain is step one to mastering xDS.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LDS (Listener)&lt;/strong&gt;: Which port should we listen on?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RDS (Route)&lt;/strong&gt;: Which path routes to where?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CDS (Cluster)&lt;/strong&gt;: What are the connection settings for the destination service?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EDS (Endpoint)&lt;/strong&gt;: What are the actual IP addresses of that service?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SDS (Secret)&lt;/strong&gt;: What certificate data (e.g., for TLS termination) do we need?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's look at how they point to each other at the YAML/code level.&lt;/p&gt;

&lt;h3&gt;
  
  
  ① LDS → Pointing to RDS
&lt;/h3&gt;

&lt;p&gt;LDS creates the entry point, defining "Listen for HTTP on port 8080." However, instead of hardcoding IP destinations for the traffic, it embeds a &lt;strong&gt;reference (name) to the RDS&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Data streamed from LDS (Listener Discovery Service)&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my_listener&lt;/span&gt;
&lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;socket_address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;0.0.0.0&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;port_value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;8080&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;span class="na"&gt;filter_chains&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;filters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;envoy.filters.network.http_connection_manager&lt;/span&gt;
    &lt;span class="na"&gt;typed_config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;rds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;route_config_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my_routes&lt;/span&gt;  &lt;span class="c1"&gt;# ← [KEY] Refers to RDS resource "my_routes"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ② RDS → Pointing to CDS
&lt;/h3&gt;

&lt;p&gt;RDS is the "signboard." Called up by LDS as &lt;code&gt;my_routes&lt;/code&gt;, this config evaluates HTTP paths or headers and returns &lt;strong&gt;a logical cluster name (a reference to CDS)&lt;/strong&gt;. Istio's &lt;code&gt;VirtualService&lt;/code&gt; gets translated into this layer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Data streamed from RDS (Route Discovery Service)&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my_routes&lt;/span&gt;
&lt;span class="na"&gt;virtual_hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api_host&lt;/span&gt;
  &lt;span class="na"&gt;domains&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api.example.com"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;prefix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/v1/"&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;cluster&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;api-v1-cluster&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;  &lt;span class="c1"&gt;# ← [KEY] Refers to CDS resource "api-v1-cluster"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ③ CDS → Pointing to EDS
&lt;/h3&gt;

&lt;p&gt;CDS defines the &lt;em&gt;nature&lt;/em&gt; of the designated cluster. It determines the load balancing algorithm (Round Robin, etc.) and circuit breaker thresholds. This is the domain of Istio's &lt;code&gt;DestinationRule&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It still doesn't write down specific IP addresses here; it &lt;strong&gt;delegates the final resolution to EDS&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Data streamed from CDS (Cluster Discovery Service)&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api-v1-cluster&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;EDS&lt;/span&gt;                           &lt;span class="c1"&gt;# ← [KEY] Declares we will fetch endpoints dynamically via EDS&lt;/span&gt;
&lt;span class="na"&gt;eds_cluster_config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;eds_config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;ads&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;           &lt;span class="c1"&gt;# (Requests EDS via ADS)&lt;/span&gt;
&lt;span class="na"&gt;lb_policy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ROUND_ROBIN&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ④ Touching Down at EDS
&lt;/h3&gt;

&lt;p&gt;Finally, EDS returns the &lt;strong&gt;actual IP addresses of the Pods&lt;/strong&gt; tied to that cluster. Because clusters scale up and down, EDS is the most aggressively updated component in xDS.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Data streamed from EDS (Endpoint Discovery Service)&lt;/span&gt;
&lt;span class="na"&gt;cluster_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api-v1-cluster&lt;/span&gt;
&lt;span class="na"&gt;endpoints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;lb_endpoints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;socket_address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;10.0.1.15&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;port_value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;8080&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;  &lt;span class="c1"&gt;# ← Actual IP&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;socket_address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;10.0.1.22&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;port_value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;8080&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;  &lt;span class="c1"&gt;# ← Actual IP&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ⑤ Encrypting via SDS (mTLS) and Zero-Downtime Rotation
&lt;/h3&gt;

&lt;p&gt;Once the destination IP is pinned down, we don't just blindly fire the packet. In modern service meshes, mTLS (mutual TLS) encryption between Pods is mandatory. This is where &lt;strong&gt;SDS (Secret Discovery Service)&lt;/strong&gt; steps onto the stage.&lt;/p&gt;

&lt;p&gt;Inside the CDS definition, it dictates "use this specific TLS context when talking to this cluster." Envoy then uses SDS to dynamically fetch the certificate (SVID) and private key straight into memory.&lt;br&gt;
What's spectacular here is &lt;strong&gt;certificate rotation&lt;/strong&gt;. Before a certificate expires, you historically had to restart the web server process. With SDS, the microsecond a new certificate is issued, it is flashed into memory via xDS, enabling &lt;strong&gt;100% zero-downtime, automated certificate rotation&lt;/strong&gt;. Because this is highly sensitive secret data, this specific stream is strictly gated and protected.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Conceptual data streamed from SDS (Secret Discovery Service)&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;tls_certificate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;certificate_chain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;inline_string&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-----BEGIN&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;CERTIFICATE-----&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;..."&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;private_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;inline_string&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-----BEGIN&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;PRIVATE&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;KEY-----&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;..."&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In short, a packet perfectly traces the configuration pointers in the exact order of &lt;strong&gt;LDS → RDS → CDS → EDS (+ SDS)&lt;/strong&gt;, encrypts itself, secures routing, and takes flight.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp1cynjg6sd8fywk9kae7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp1cynjg6sd8fywk9kae7.png" alt="xds flow" width="769" height="1052"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You must never forget this "dependency order." It is the exact reason why ADS (Aggregated Discovery Service), which we'll discuss later, exists.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Surviving Broken Configs: The ACK/NACK Flow
&lt;/h2&gt;

&lt;p&gt;In an architecture where dynamic configs are blasted out to thousands of proxies, pushing a "bad config" causes apocalyptic damage. What happens if the control plane sends a malformed JSON or a conflicting route setting?&lt;/p&gt;

&lt;p&gt;Does Envoy bug out and crash? ...Absolutely not.&lt;br&gt;
xDS is armed with a fiercely robust rollback mechanism called &lt;strong&gt;"NACK" (Negative Acknowledgement)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;xDS communication is a bidirectional gRPC stream. When a &lt;code&gt;DiscoveryResponse&lt;/code&gt; arrives from the control plane, Envoy attempts to apply it. It then packages the result into a &lt;code&gt;DiscoveryRequest&lt;/code&gt; and shoots it back to the control plane.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flx394bqj4g1lzvbtcy8q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flx394bqj4g1lzvbtcy8q.png" alt="nack" width="800" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The absolute beauty of this design is that &lt;strong&gt;even if a NACK occurs, Envoy's stream does NOT sever; it just keeps humming along using the last-known-good config (v1)&lt;/strong&gt;.&lt;br&gt;
Even if an operator brutally applies a flawed &lt;code&gt;VirtualService&lt;/code&gt; to Kubernetes, Envoy essentially says "screw this," returns a NACK, and existing traffic doesn't drop a single millisecond. It simply waits idly for a corrected config to be pushed.&lt;/p&gt;

&lt;p&gt;Two control fields are the key players here: &lt;code&gt;nonce&lt;/code&gt; and &lt;code&gt;version_info&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;nonce&lt;/code&gt;&lt;/strong&gt;: A simple ID that says, "Hey, which specific update payload are you giving me the validation result for?"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;version_info&lt;/code&gt;&lt;/strong&gt;: The factual state that says, "As a result, what version of the config am I currently running?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If Envoy simply parrots back the latest &lt;code&gt;version_info&lt;/code&gt; the server sent, it's categorized as an "ACK (Success)." If it returns an older version number, the server realizes it's a "NACK (Failure)." It’s brilliantly simple, yet ruthlessly effective.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Why We Need ADS (Aggregated Discovery Service)
&lt;/h2&gt;

&lt;p&gt;Earlier, I mentioned that LDS → RDS → CDS → EDS share a strict dependency chain.&lt;/p&gt;

&lt;p&gt;What would happen if you subscribed to these four APIs via &lt;strong&gt;completely separate gRPC streams&lt;/strong&gt; asynchronously from the control plane? Naturally, due to network latency or processing timing, their arrival order would scramble.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Imagine the worst-case scenario.&lt;/strong&gt;&lt;br&gt;
A new RDS (route setting) arrives first. It says, "route traffic to &lt;code&gt;cluster-B&lt;/code&gt;". Envoy eagerly updates its settings and tries to shove traffic towards &lt;code&gt;cluster-B&lt;/code&gt;. However, the streams for CDS and EDS (the actual definition and IPs of &lt;code&gt;cluster-B&lt;/code&gt;) are lagging slightly behind and haven't hit Envoy yet.&lt;/p&gt;

&lt;p&gt;As a result, Envoy concludes "the destination cluster doesn't exist" and &lt;strong&gt;starts aggressively throwing 503 Service Unavailable errors&lt;/strong&gt;. A service mesh where 503s run rampant every time a configuration changes is completely unusable.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix: Bundling the Streams (ADS)
&lt;/h3&gt;

&lt;p&gt;To prevent this "temporary inconsistency in an eventually consistent system," &lt;strong&gt;ADS (Aggregated Discovery Service)&lt;/strong&gt; was forged.&lt;/p&gt;

&lt;p&gt;ADS multiplexes (aggregates) the requests and responses for ALL xDS resource types (LDS/RDS/CDS/EDS) into &lt;strong&gt;a single solitary gRPC stream&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo5hrluvxj3r7gwn4zg6m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo5hrluvxj3r7gwn4zg6m.png" alt="ads" width="800" height="518"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By bundling everything into one stream, the control plane gains the ability to enforce perfect Sequencing: &lt;strong&gt;"I will force Envoy to apply CDS/EDS first, and I won't send RDS until I get the ACKs back."&lt;/strong&gt;&lt;br&gt;
Istio's control plane (&lt;code&gt;istiod&lt;/code&gt;) uses this ADS approach by default to stream configurations safely.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. SotW vs Delta: Taming the Infinite Endpoint Explosion
&lt;/h2&gt;

&lt;p&gt;Looking back at the history of xDS brings us to another massive evolutionary fork: &lt;em&gt;how&lt;/em&gt; the configs are sent.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Limits of State of the World (SotW)
&lt;/h3&gt;

&lt;p&gt;Early xDS utilized a model called &lt;strong&gt;SotW (State of the World)&lt;/strong&gt;. When Envoy asks "Tell me the current endpoints," the server responds by sending back &lt;strong&gt;"the entire, exhaustive list of endpoints"&lt;/strong&gt; every single time.&lt;/p&gt;

&lt;p&gt;Let's assume you have a cluster of 1,000 Pods, and a single Pod scales out, making it 1,001.&lt;br&gt;
In the SotW model, &lt;code&gt;istiod&lt;/code&gt; beams out the &lt;strong&gt;full list of 1,001 IP addresses&lt;/strong&gt; to every single Envoy proxy. The 1,000 unmodified records are re-transmitted entirely. This is a colossal waste of network bandwidth and CPU horsepower. Once your cluster scales large enough, the control plane literally chokes and dies.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Dawn of Incremental (Delta) xDS
&lt;/h3&gt;

&lt;p&gt;This crisis birthed &lt;strong&gt;Delta xDS&lt;/strong&gt;.&lt;br&gt;
When returning subscription requests, the server now sends &lt;strong&gt;"only the diff from the last state (added IPs, removed IPs)."&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SotW: &lt;code&gt;[IP_A, IP_B, IP_C]&lt;/code&gt; (If B is deleted, it resends &lt;code&gt;[IP_A, IP_C]&lt;/code&gt;. Items missing from the list are implicitly assumed deleted.)&lt;/li&gt;
&lt;li&gt;Delta: &lt;code&gt;removed_resources: ["IP_B"]&lt;/code&gt; (Explicitly sends ONLY the deletion directive.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because the server must maintain an in-memory cache tracking the individual state of every single client, the backend implementation becomes drastically more complex. However, the performance gains are monumental. Modern service meshes circa 2026 (like recent versions of Istio) securely default to Delta xDS.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Beyond Routing: Advanced xDS Use Cases
&lt;/h2&gt;

&lt;p&gt;When discussing xDS, it's impossible to ignore the fact that &lt;strong&gt;xDS is no longer an "Envoy-exclusive routing protocol."&lt;/strong&gt; As of 2026, the xDS ecosystem has wildly expanded beyond basic routing and breached into non-Envoy clients.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.1 Dynamic Injection of Extensions (ECDS)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;ECDS (Extension Config Discovery Service)&lt;/strong&gt; is a mechanism to dynamically push "extension filters" (like WebAssembly) directly into Envoy.&lt;br&gt;
For example, you write a proprietary Wasm module that applies custom obfuscation to a specific HTTP header, and you stream it via ECDS. This lets you &lt;strong&gt;hot-reload and inject brand-new Wasm modules into every proxy safely, while they are running&lt;/strong&gt;, without touching LDS or RDS at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.2 Streaming Runtime Variables (RTDS)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;RTDS (Runtime Discovery Service)&lt;/strong&gt; skips routing altogether and instead streams "runtime variables" (think of it as a virtual file system).&lt;br&gt;
Need to flip a new feature ON/OFF (feature toggles) or temporarily throttle a specific user's rate limits? You use RTDS. It instantly propagates single variable tweaks to thousands of proxies without forcing an application rebuild or redeploy.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.3 Building Proprietary Control Planes (&lt;code&gt;go-control-plane&lt;/code&gt;) &amp;amp; Case Studies
&lt;/h3&gt;

&lt;p&gt;Because the xDS specs are fully open (defined in Protobuf), it's highly common for massive-scale environments to ditch off-the-shelf products like Istio and &lt;strong&gt;build their very own bespoke xDS control planes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Libraries provided by the Envoy project, like &lt;code&gt;go-control-plane&lt;/code&gt;, shoulder the agonizing implementation burdens of operating an xDS gRPC server (handling streams, snapshot caching, etc.). By wiring this up, companies can construct proprietary control planes that use "internal corporate databases" as the Source of Truth, governed by heavily customized business logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tech Giant Case Studies:&lt;/strong&gt;&lt;br&gt;
For companies wrestling with horribly complex "brownfield" infrastructure, these custom control planes are a lifeline.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stripe&lt;/strong&gt;: They operate an internal service mesh using HashiCorp Consul as the Source of Truth for service discovery. They built a custom control plane that snags data from Consul, compiles it into xDS parameters, and streams it to Envoy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Netflix&lt;/strong&gt;: To manage their astronomical fleet of microservices, Netflix built a custom foundation fused with Eureka (their service registry). By aggressively utilizing &lt;code&gt;On-Demand Cluster Discovery (ODCDS)&lt;/code&gt;, they dynamically inject &lt;em&gt;only&lt;/em&gt; the settings Envoy actually needs, shattering the scaling boundaries of giant clusters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Airbnb / Uber&lt;/strong&gt;: They bake custom logic into their bespoke control planes to rein in legacy, non-containerized workloads that refuse to submit to Kubernetes, and to shove highly specialized, company-specific L7 routing logic straight into the proxy tier.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The meta isn't just "deploy Istio and call it a day." It’s "translate your company's proprietary domain logic into xDS, the universal language, and stream it." That is the absolute frontline of the service mesh today.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.4 The "Proxyless gRPC" Paradigm
&lt;/h3&gt;

&lt;p&gt;The ultimate evolution of this is &lt;strong&gt;Proxyless gRPC&lt;/strong&gt;.&lt;br&gt;
Instead of deploying an Envoy (sidecar) next to your application, &lt;strong&gt;the gRPC library itself seamlessly acts as an xDS client&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc9zsznf1x2dne6u3h4dm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc9zsznf1x2dne6u3h4dm.png" alt="Proxyless gRPC" width="621" height="589"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By generating a gRPC channel using the unique &lt;code&gt;xds:///my-service&lt;/code&gt; URI scheme, the gRPC library quietly connects to the control plane (like &lt;code&gt;istiod&lt;/code&gt;) under the hood, pulls down EDS configs, and blasts direct HTTP/2 requests right to the optimal Pod from &lt;em&gt;within your own application process&lt;/em&gt;.&lt;br&gt;
Because you bypass the sidecar entirely, you prune network hops, slashing latency and devouring far less CPU. &lt;em&gt;This&lt;/em&gt; is the true essence of xDS earning the title "Universal Data Plane API".&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Behind the wizardry of an infrastructure that magically swaps over seconds after you smash &lt;code&gt;kubectl apply&lt;/code&gt;, lies this heavily gritty, battle-tested mechanism.&lt;/p&gt;

&lt;p&gt;The rigid hierarchy of LDS/RDS/CDS/EDS enforcing dependencies.&lt;br&gt;
The indestructible ACK/NACK flow shielding the system from crippled configurations.&lt;br&gt;
The sequencing and stream multiplexing of ADS preventing nasty 503 hiccups.&lt;br&gt;
And the adoption of Delta xDS breaking the chains of scalability limits.&lt;/p&gt;

&lt;p&gt;It is precisely because these gears mesh in such miraculous balance that we get to casually enjoy things like "zero downtime traffic shifting" and "canary releases."&lt;br&gt;
xDS is no longer just Envoy's internal protocol. It is the absolute, most vital "nervous system" anchoring modern, cloud-native network architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.envoyproxy.io/docs/envoy/latest/api-docs/xds_protocol" rel="noopener noreferrer"&gt;Envoy xDS REST and gRPC Protocol (Official Documentation)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.envoyproxy.io/docs/envoy/latest/api/api" rel="noopener noreferrer"&gt;xDS API Overview - Envoy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cncf/xds" rel="noopener noreferrer"&gt;CNCF xDS API Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://grpc.io/docs/guides/xds/" rel="noopener noreferrer"&gt;gRPC Proxyless Service Mesh&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>envoy</category>
      <category>servicemesh</category>
      <category>kubernetes</category>
      <category>istio</category>
    </item>
    <item>
      <title>Why Can We Use "Shorter" Keys?: Key Length vs Security Bits, the Real Story</title>
      <dc:creator>kt</dc:creator>
      <pubDate>Sun, 19 Apr 2026 15:50:50 +0000</pubDate>
      <link>https://dev.to/kanywst/why-can-we-use-shorter-keys-key-length-vs-security-bits-the-real-story-1gl3</link>
      <guid>https://dev.to/kanywst/why-can-we-use-shorter-keys-key-length-vs-security-bits-the-real-story-1gl3</guid>
      <description>&lt;p&gt;"ECDSA P-256 has shorter keys than RSA-2048. So it must be weaker."&lt;/p&gt;

&lt;p&gt;...I used to think that too.&lt;/p&gt;

&lt;p&gt;2048 bits vs 256 bits. Just looking at the numbers, RSA seems 8x "stronger." But NIST (the National Institute of Standards and Technology) treats these two as &lt;strong&gt;equal in security strength&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;On top of that, RSA-2048's actual security does not even reach "128-bit security." It sits at &lt;strong&gt;112 bits&lt;/strong&gt;, and it is getting deprecated in 2030. Meanwhile, P-256 properly achieves 128-bit security.&lt;/p&gt;

&lt;p&gt;So &lt;strong&gt;the shorter key is actually stronger&lt;/strong&gt;. The concept behind this counterintuitive fact is called "security bits."&lt;/p&gt;

&lt;p&gt;This article covers why "key length" and "actual security" do not match up, from the mathematical foundations all the way to the 2026 transition timeline.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Background: Symmetric vs Public Key Cryptography
&lt;/h2&gt;

&lt;p&gt;There are two fundamental types of cryptography. Without understanding this distinction, key length discussions will never make sense.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Symmetric Key Cryptography&lt;/strong&gt;: Uses &lt;strong&gt;the same key&lt;/strong&gt; for both encryption and decryption. The canonical example is &lt;strong&gt;AES&lt;/strong&gt; (Advanced Encryption Standard). Both sender and receiver need to share the same key. It is fast, and it is what actually encrypts your data. WiFi (WPA2/3) and disk encryption (BitLocker, FileVault) all use this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Public Key Cryptography&lt;/strong&gt;: Uses &lt;strong&gt;a pair of different keys&lt;/strong&gt; for encryption and decryption. The main examples are &lt;strong&gt;RSA&lt;/strong&gt; and &lt;strong&gt;ECC&lt;/strong&gt; (Elliptic Curve Cryptography). You can hand out the public key to anyone, but only the corresponding private key can decrypt. Key distribution is easy, so it is used for TLS (HTTPS) key exchange and digital signatures. The downside is that it is computationally expensive.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuduwun7mcbp4jeicp20j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuduwun7mcbp4jeicp20j.png" alt="key" width="800" height="257"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In practice, HTTPS combines both. Public key crypto handles "securely exchanging a key," then symmetric crypto handles "encrypting data fast."&lt;/p&gt;

&lt;p&gt;Now here is the thing. &lt;strong&gt;These two types of cryptography require completely different key lengths.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  2. "Key Length" and "Security Bits" Are Different Things
&lt;/h2&gt;

&lt;p&gt;Let me clarify the terminology.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Key Length&lt;/strong&gt;: The number of bits in the key data that the algorithm uses. RSA-2048 uses 2048 bits, AES-128 uses 128 bits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security Bits (Security Strength)&lt;/strong&gt;: The computational effort required to break the cipher, expressed as a power of 2.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;"128-bit security" means that even the best known attack requires roughly $2^{128}$ operations. $2^{128}$ is about 3.4 x $10^{38}$. Even if you threw every supercomputer on Earth at it, you could repeat the entire age of the universe (about 13.8 billion years) trillions of times over and still not finish. It is basically the threshold for "cannot be broken in practice."&lt;/p&gt;

&lt;p&gt;Here is the biggest source of confusion: &lt;strong&gt;key length ≠ security bits&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;AES-128 has 128-bit security. Key length and security bits happen to match.&lt;br&gt;
But RSA-2048 only has 112-bit security. Despite having a 2048-bit key, its effective strength is only 112 bits.&lt;/p&gt;

&lt;p&gt;Why does this happen?&lt;/p&gt;


&lt;h2&gt;
  
  
  3. Each Cipher Has Different Attack "Shortcuts"
&lt;/h2&gt;

&lt;p&gt;Breaking a cipher is not just about trying every possible key (brute force). Some algorithms have much more efficient attack methods. The existence and efficiency of these "shortcuts" determine the key length each algorithm needs.&lt;/p&gt;
&lt;h3&gt;
  
  
  AES: No shortcuts, so key length = security bits
&lt;/h3&gt;

&lt;p&gt;The best known attack against AES is essentially brute force. For AES-128, you need to try all $2^{128}$ possible keys. That is why key length directly equals security bits.&lt;/p&gt;
&lt;h3&gt;
  
  
  RSA: Integer factorization is a powerful shortcut
&lt;/h3&gt;

&lt;p&gt;RSA's security relies on the assumption that "factoring a huge number $N$ is hard."&lt;/p&gt;

&lt;p&gt;$N$ is the product of two large primes $p$ and $q$ ($N = p \times q$). If you can find $p$ and $q$, you can compute the private key, but if $N$ is large enough, factoring should take an impractical amount of time... or so the idea goes.&lt;/p&gt;

&lt;p&gt;The problem is that there exists an efficient algorithm for integer factorization called the &lt;strong&gt;General Number Field Sieve (GNFS)&lt;/strong&gt;. Thanks to GNFS, the effort to break RSA-2048 drops from $2^{2048}$ (brute force) down to roughly $2^{112}$.&lt;/p&gt;

&lt;p&gt;Think of it this way. If you tried every combination on a 2048-digit safe one by one, it would take an astronomical amount of time. But GNFS is like "analyzing the safe's internal structure to dramatically narrow down the combinations." The key is 2048 bits, but the effective defense is only 112 bits.&lt;/p&gt;
&lt;h3&gt;
  
  
  ECC: Has shortcuts, but they are far less efficient than RSA's
&lt;/h3&gt;

&lt;p&gt;ECC (Elliptic Curve Cryptography) relies on the "Elliptic Curve Discrete Logarithm Problem (ECDLP)."&lt;/p&gt;

&lt;p&gt;I will skip the detailed math, but the key point is this: the best attack against ECC (Pollard's rho) requires computation proportional to &lt;strong&gt;half the key length&lt;/strong&gt;. For P-256 (256-bit key), that means $2^{128}$ operations, which gives you 128-bit security.&lt;/p&gt;

&lt;p&gt;Digging a bit deeper, this difference comes from a &lt;strong&gt;gap in computational complexity&lt;/strong&gt;. GNFS runs in &lt;strong&gt;sub-exponential time&lt;/strong&gt;, meaning that even as you make keys longer, the attack cost grows sluggishly. Pollard's rho against ECC, on the other hand, runs in &lt;strong&gt;exponential time&lt;/strong&gt;, specifically $O(\sqrt{N})$. Each extra bit in the key doubles the attack cost.&lt;/p&gt;

&lt;p&gt;In concrete numbers: RSA-2048 goes from 2048 bits to 112-bit security (roughly 1/18), RSA-3072 goes from 3072 bits to 128 bits (roughly 1/24), and RSA-15360 goes from 15360 bits to 256 bits (roughly 1/60). The longer the key, the worse the "decay ratio" gets. This is a direct consequence of GNFS being sub-exponential: the growth in attack cost cannot keep up with the cost of making keys longer. ECC stays at 1/2 regardless. This mathematical gap is what makes "ECC is secure with short keys" possible.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8z9stn3ki2x27p0t4cup.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8z9stn3ki2x27p0t4cup.png" alt="ecc vs rsa" width="715" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;RSA has the longer key but the lower security bits. That is how big the difference in attack efficiency is.&lt;/p&gt;


&lt;h2&gt;
  
  
  4. Equivalent Security Key Lengths (NIST SP 800-57)
&lt;/h2&gt;

&lt;p&gt;So how do these differences in attack efficiency translate to actual key lengths? NIST SP 800-57 Part 1 defines a security strength equivalence table, and it is the industry standard as of 2026.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Security&lt;br&gt;Bits&lt;/th&gt;
&lt;th&gt;Symmetric&lt;br&gt;(AES)&lt;/th&gt;
&lt;th&gt;RSA&lt;br&gt;(Key)&lt;/th&gt;
&lt;th&gt;ECC&lt;br&gt;(Key)&lt;/th&gt;
&lt;th&gt;Hash&lt;br&gt;(SHA)&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;80&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;1024&lt;/td&gt;
&lt;td&gt;160&lt;/td&gt;
&lt;td&gt;SHA-1&lt;/td&gt;
&lt;td&gt;❌ Prohibited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;112&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;2048&lt;/td&gt;
&lt;td&gt;224&lt;/td&gt;
&lt;td&gt;SHA-224&lt;/td&gt;
&lt;td&gt;⚠️ Deprecated by 2030&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;128&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AES-128&lt;/td&gt;
&lt;td&gt;3072&lt;/td&gt;
&lt;td&gt;256&lt;/td&gt;
&lt;td&gt;SHA-256&lt;/td&gt;
&lt;td&gt;✅ Current minimum&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;192&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AES-192&lt;/td&gt;
&lt;td&gt;7680&lt;/td&gt;
&lt;td&gt;384&lt;/td&gt;
&lt;td&gt;SHA-384&lt;/td&gt;
&lt;td&gt;✅ Recommended&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;256&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AES-256&lt;/td&gt;
&lt;td&gt;15360&lt;/td&gt;
&lt;td&gt;512+&lt;/td&gt;
&lt;td&gt;SHA-512&lt;/td&gt;
&lt;td&gt;✅ Long-term protection&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read this table horizontally. &lt;strong&gt;Every entry in the same row provides the same security.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Look at the 128-bit security row: AES needs 128 bits, ECC needs 256 bits, and RSA needs &lt;strong&gt;3072 bits&lt;/strong&gt;. RSA requires 12x the key length of ECC and 24x that of AES for the same security level. At 192-bit security, RSA-7680 is needed, and that causes serious performance issues in TLS handshakes.&lt;/p&gt;

&lt;p&gt;The most important row right now is 112 bits. That is where RSA-2048 lives. It does not meet the current minimum recommendation of 128-bit security.&lt;/p&gt;


&lt;h2&gt;
  
  
  5. RSA-2048 Gets Deprecated in 2030
&lt;/h2&gt;

&lt;p&gt;By now you know that RSA-2048 only provides 112-bit security. But it is not getting disabled overnight. NIST has defined a phased transition schedule.&lt;/p&gt;
&lt;h3&gt;
  
  
  NIST Transition Timeline
&lt;/h3&gt;

&lt;p&gt;NIST IR 8547 (published November 2024) lays out the concrete timeline.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhswg1dmswlpfibyh5i9d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhswg1dmswlpfibyh5i9d.png" alt="timeline" width="654" height="293"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;When&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;Scope&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2030&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Deprecated&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;RSA-2048 and other ≤112-bit algorithms&lt;/td&gt;
&lt;td&gt;No new deployments. Existing systems need a migration plan.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2035&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Disallowed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;RSA-3072/4096, P-256, P-384, and&lt;br&gt;&lt;strong&gt;all&lt;/strong&gt; quantum-vulnerable public key crypto&lt;/td&gt;
&lt;td&gt;Completely prohibited in FIPS-compliant systems.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Take a close look at the 2035 row. RSA-3072, P-256, P-384: algorithms that are currently "recommended" will all be prohibited. This is not about security bits. It is about quantum computers breaking the algorithms at a fundamental level. The next section explains why making keys longer will not help.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Gap Between 112 and 128 Bits Is Not "Just 16"
&lt;/h3&gt;

&lt;p&gt;You might think "112 vs 128, that is only 16 apart." But in security bits, the difference is exponential.&lt;/p&gt;

&lt;p&gt;$2^{128} \div 2^{112} = 2^{16} = 65,536$&lt;/p&gt;

&lt;p&gt;An attacker capable of breaking 112-bit security would need &lt;strong&gt;65,536 times&lt;/strong&gt; more computation to break 128-bit security. Put another way, 112-bit security is only $\frac{1}{65536}$ as hard to break as 128-bit.&lt;/p&gt;

&lt;p&gt;RSA-2048 is not going to be cracked tomorrow, not in 2026. But computing power improves every year. If you are encrypting data today with RSA-2048 that would be damaging if decrypted in 10 or 20 years (medical records, intellectual property, diplomatic communications), it is time to start thinking about migration.&lt;/p&gt;


&lt;h2&gt;
  
  
  6. Quantum Computers: Why Longer Keys Will Not Save You
&lt;/h2&gt;

&lt;p&gt;In section 5, I wrote that RSA-3072 and P-384 will be prohibited by 2035. If longer keys mean more security bits, why ban them?&lt;/p&gt;

&lt;p&gt;The answer is quantum computers. Quantum computers affect cryptography in two ways, and they are fundamentally different from each other.&lt;/p&gt;
&lt;h3&gt;
  
  
  Shor's Algorithm: Breaks Public Key Crypto at the Root
&lt;/h3&gt;

&lt;p&gt;Shor's algorithm solves integer factorization and the discrete logarithm problem in &lt;strong&gt;polynomial time&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For RSA, this means the assumption that "factoring $N$ is astronomically hard" simply collapses. The discrete logarithm problem behind ECC falls the same way. No matter how long you make the key, the fundamental assumption the algorithm relies on is gone. Upgrade to RSA-4096, RSA-15360, it makes no difference against Shor.&lt;/p&gt;

&lt;p&gt;This is why "all quantum-vulnerable public key crypto is prohibited by 2035." It is not a key length issue. You &lt;strong&gt;have to change the algorithm itself&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Grover's Algorithm: Halves Symmetric Crypto Security
&lt;/h3&gt;

&lt;p&gt;Grover's algorithm speeds up brute-force search from $2^n$ to $\sqrt{2^n} = 2^{n/2}$.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symmetric Cipher&lt;/th&gt;
&lt;th&gt;Classical Security&lt;/th&gt;
&lt;th&gt;Quantum Security&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AES-128&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;128 bit&lt;/td&gt;
&lt;td&gt;→ &lt;strong&gt;64 bit&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;❌ Not enough&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AES-192&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;192 bit&lt;/td&gt;
&lt;td&gt;→ &lt;strong&gt;96 bit&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;⚠️ Marginal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AES-256&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;256 bit&lt;/td&gt;
&lt;td&gt;→ &lt;strong&gt;128 bit&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;✅ Sufficient&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One caveat, though. These numbers assume Grover's algorithm running under ideal conditions. Actually attacking AES-128 with Grover would require tens of millions of logical qubits, which is orders of magnitude beyond current quantum computers (which have a few thousand physical qubits at best). Still, for long-term security, moving to AES-256 is the safe bet.&lt;/p&gt;

&lt;p&gt;The crucial difference from Shor is that Grover &lt;strong&gt;can be countered by using longer keys&lt;/strong&gt;. AES-256 maintains 128-bit security even against quantum computers. No need to change the algorithm. Just use longer keys.&lt;/p&gt;

&lt;p&gt;This is why NIST is saying "use AES-256, use SHA-384 or above."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzscxm9k6qldtvm5vmtsr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzscxm9k6qldtvm5vmtsr.png" alt="Quantum" width="611" height="506"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  7. Harvest Now, Decrypt Later: Today's Data, Broken Tomorrow
&lt;/h2&gt;

&lt;p&gt;"Practical quantum computers are still years away, right?"&lt;/p&gt;

&lt;p&gt;That is the most dangerous assumption. There is an attack model called Harvest Now, Decrypt Later (HNDL).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5hu3im7m9a6jhrqnx80y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5hu3im7m9a6jhrqnx80y.png" alt="Attack" width="736" height="606"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Attackers intercept encrypted data &lt;strong&gt;now&lt;/strong&gt; and store it, then decrypt it once quantum computers become viable. The NSA and CISA have warned that nation-state actors are already in this "collection phase."&lt;/p&gt;

&lt;p&gt;The critical question is: how long does your data need to stay confidential? If a medical record encrypted today gets decrypted 20 years from now, that is a real data breach. Before quantum computers arrive, long-lived sensitive data needs to be re-protected with quantum-resistant methods.&lt;/p&gt;


&lt;h2&gt;
  
  
  8. Post-Quantum Cryptography (PQC) Key Sizes
&lt;/h2&gt;

&lt;p&gt;So what cryptography can actually withstand quantum computers? In August 2024, NIST officially published three post-quantum cryptography standards.&lt;/p&gt;
&lt;h3&gt;
  
  
  ML-KEM (FIPS 203): Key Encapsulation
&lt;/h3&gt;

&lt;p&gt;Formerly known as CRYSTALS-Kyber. Replaces RSA key exchange and ECDH.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Security&lt;/th&gt;
&lt;th&gt;Public Key&lt;/th&gt;
&lt;th&gt;Ciphertext&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ML-KEM-512&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AES-128 equiv.&lt;/td&gt;
&lt;td&gt;800 bytes&lt;/td&gt;
&lt;td&gt;768 bytes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ML-KEM-768&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AES-192 equiv.&lt;/td&gt;
&lt;td&gt;1,184 bytes&lt;/td&gt;
&lt;td&gt;1,088 bytes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ML-KEM-1024&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AES-256 equiv.&lt;/td&gt;
&lt;td&gt;1,568 bytes&lt;/td&gt;
&lt;td&gt;1,568 bytes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  ML-DSA (FIPS 204): Digital Signatures
&lt;/h3&gt;

&lt;p&gt;Formerly known as CRYSTALS-Dilithium. Replaces RSA signatures and ECDSA.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Security&lt;/th&gt;
&lt;th&gt;Public Key&lt;/th&gt;
&lt;th&gt;Signature&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ML-DSA-44&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AES-128 equiv.&lt;/td&gt;
&lt;td&gt;1,312 bytes&lt;/td&gt;
&lt;td&gt;2,420 bytes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ML-DSA-65&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AES-192 equiv.&lt;/td&gt;
&lt;td&gt;1,952 bytes&lt;/td&gt;
&lt;td&gt;3,309 bytes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ML-DSA-87&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AES-256 equiv.&lt;/td&gt;
&lt;td&gt;2,592 bytes&lt;/td&gt;
&lt;td&gt;4,627 bytes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  SLH-DSA (FIPS 205): Hash-Based Signatures
&lt;/h3&gt;

&lt;p&gt;Formerly known as SPHINCS+. Positioned as a backup for ML-DSA. It relies on a different mathematical foundation (hash functions) than the lattice-based ML-DSA, so it serves as insurance in case a vulnerability is found in ML-DSA.&lt;/p&gt;
&lt;h3&gt;
  
  
  Comparing Legacy Crypto and PQC Key Sizes
&lt;/h3&gt;

&lt;p&gt;This is where PQC hurts. Quantum resistance comes at the cost of larger keys and signatures.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;ECDSA P-256&lt;/th&gt;
&lt;th&gt;RSA-3072&lt;/th&gt;
&lt;th&gt;ML-DSA-65&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Public Key&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;64 bytes&lt;/td&gt;
&lt;td&gt;384 bytes&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1,952 bytes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Signature&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;64 bytes&lt;/td&gt;
&lt;td&gt;384 bytes&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3,309 bytes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Security&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;128 bit&lt;/td&gt;
&lt;td&gt;128 bit&lt;/td&gt;
&lt;td&gt;192 bit (quantum-resistant)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;ML-DSA-65's public key is &lt;strong&gt;roughly 30x&lt;/strong&gt; that of ECDSA P-256. This directly impacts TLS handshake sizes and certificate chains. That is why hybrid approaches (combining legacy crypto + PQC) are currently recommended. The transition will be gradual, not a full switchover all at once.&lt;/p&gt;


&lt;h2&gt;
  
  
  9. What to Do in 2026
&lt;/h2&gt;

&lt;p&gt;Theory is great. But what should you actually do?&lt;/p&gt;
&lt;h3&gt;
  
  
  Start with a Crypto Inventory
&lt;/h3&gt;

&lt;p&gt;Figuring out what your systems are currently using is step one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check your server's TLS certificate&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; | openssl s_client &lt;span class="nt"&gt;-connect&lt;/span&gt; example.com:443 2&amp;gt;/dev/null &lt;span class="se"&gt;\&lt;/span&gt;
  | openssl x509 &lt;span class="nt"&gt;-noout&lt;/span&gt; &lt;span class="nt"&gt;-text&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"Public Key Algorithm|Public-Key"&lt;/span&gt;

&lt;span class="c"&gt;# Check your SSH keys&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;key &lt;span class="k"&gt;in&lt;/span&gt; ~/.ssh/id_&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;ssh-keygen &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;/dev/null
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What to check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Are your TLS certificates using ECDSA (P-256 or above)? If they are still RSA-2048, start planning the migration.&lt;/li&gt;
&lt;li&gt;Are your SSH keys Ed25519? If they are still RSA-2048, regenerate them with &lt;code&gt;ssh-keygen -t ed25519&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Are your JWT signatures using ES256 / EdDSA? If RS256, consider switching.&lt;/li&gt;
&lt;li&gt;Are you using AES-256 for symmetric encryption? Migrate long-lived data from AES-128.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Build Crypto Agility into Your Architecture
&lt;/h3&gt;

&lt;p&gt;No cryptographic algorithm lasts forever. RSA and ECC both have expiration dates.&lt;/p&gt;

&lt;p&gt;The important thing is to not hardcode algorithms. Make them configurable via config files or environment variables so that when NIST publishes new recommendations, you can switch with a config change. If you have to rewrite code and redeploy to every environment, you are looking at months of work.&lt;/p&gt;

&lt;p&gt;Combined with shorter certificate lifetimes (SC-081v3 brings the max down to 47 days by 2029), you can automatically switch algorithms at the next renewal cycle. If you are already auto-renewing certificates with ACME, the PQC transition is a natural extension of that.&lt;/p&gt;




&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Key length and security bits are different things.&lt;/strong&gt; AES-128 has 128-bit security, but RSA-2048 only has 112. The efficiency of the best known attack against each algorithm determines how many key bits it actually needs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why ECC gets away with short keys.&lt;/strong&gt; GNFS against RSA runs in sub-exponential time, and the security decay ratio gets worse with longer keys (roughly 1/18 for RSA-2048, roughly 1/60 for RSA-15360). Pollard's rho against ECC stays at 1/2 regardless, so short keys provide equal or better security.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RSA-2048 gets deprecated in 2030.&lt;/strong&gt; Per NIST IR 8547. By 2035, all quantum-vulnerable public key crypto, including RSA-3072, P-256, and P-384, will be prohibited.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quantum computers are not a key length problem.&lt;/strong&gt; Shor's algorithm destroys RSA and ECC at the algorithmic level. Longer keys do not help. Migration to post-quantum crypto (ML-KEM, ML-DSA) is required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grover's algorithm can be handled with longer keys.&lt;/strong&gt; It halves AES security, but AES-256 still maintains 128-bit security.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run a crypto inventory now.&lt;/strong&gt; Know what your systems are using and build in crypto agility.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://csrc.nist.gov/publications/detail/sp/800-57-part-1/rev-5/final" rel="noopener noreferrer"&gt;NIST SP 800-57 Part 1 Rev. 5: Recommendation for Key Management&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://csrc.nist.gov/publications/detail/sp/800-131a/rev-2/final" rel="noopener noreferrer"&gt;NIST SP 800-131A Rev. 2: Transitioning the Use of Cryptographic Algorithms and Key Lengths&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://csrc.nist.gov/publications/detail/nistir/8547/final" rel="noopener noreferrer"&gt;NIST IR 8547: Transition to Post-Quantum Cryptography Standards&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://csrc.nist.gov/publications/detail/fips/203/final" rel="noopener noreferrer"&gt;FIPS 203: Module-Lattice-Based Key-Encapsulation Mechanism Standard (ML-KEM)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://csrc.nist.gov/publications/detail/fips/204/final" rel="noopener noreferrer"&gt;FIPS 204: Module-Lattice-Based Digital Signature Standard (ML-DSA)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://csrc.nist.gov/publications/detail/fips/205/final" rel="noopener noreferrer"&gt;FIPS 205: Stateless Hash-Based Digital Signature Standard (SLH-DSA)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://media.defense.gov/2022/Sep/07/2003071834/-1/-1/0/CSA_CNSA_2.0_ALGORITHMS_.PDF" rel="noopener noreferrer"&gt;CNSA 2.0: Commercial National Security Algorithm Suite&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://csrc.nist.gov/projects/post-quantum-cryptography" rel="noopener noreferrer"&gt;NIST Post-Quantum Cryptography: Resource Center&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>cryptography</category>
      <category>beginners</category>
      <category>todayilearned</category>
    </item>
    <item>
      <title>Why I Built awesome-authorization: Mapping the World of Auth Engines onto a Single Page</title>
      <dc:creator>kt</dc:creator>
      <pubDate>Sat, 18 Apr 2026 15:16:48 +0000</pubDate>
      <link>https://dev.to/kanywst/why-i-built-awesome-authorization-mapping-the-world-of-auth-engines-onto-a-single-page-4mof</link>
      <guid>https://dev.to/kanywst/why-i-built-awesome-authorization-mapping-the-world-of-auth-engines-onto-a-single-page-4mof</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;"Which authorization engine should I use?"&lt;/p&gt;

&lt;p&gt;Very few people can answer this instantly. OPA, Cedar, OpenFGA, SpiceDB, Casbin, Cerbos... That’s six just off the top of my head. They are based on entirely different access control models (RBAC, ABAC, ReBAC), with different design philosophies and use cases.&lt;/p&gt;

&lt;p&gt;I've always been an auth nerd. I wrote an AuthZEN-compatible plugin for OPA, read through the SPIFFE/SPIRE implementations, and deep-dived into the Google Zanzibar paper. Through all of this, I realized something: &lt;strong&gt;There is no single place to get a bird's-eye view of the entire authorization landscape.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A repository called &lt;code&gt;awesome-authorization&lt;/code&gt; already existed. However, it was mostly a collection of articles and concepts—it didn't answer the practical question of "What engines actually exist and how do they differ?". With AuthZEN 1.0 officially approved, the authorization space is moving fast, but the information is too scattered to track.&lt;/p&gt;

&lt;p&gt;So, I built one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/kanywst/awesome-authorization" rel="noopener noreferrer"&gt;awesome-authorization&lt;/a&gt;&lt;/strong&gt; : A curated list of tools, frameworks, standards, and learning resources for authorization and access control.&lt;/p&gt;

&lt;p&gt;In this post, I want to explain why this list needed to exist and map out the state of authorization engines in 2026.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Setting the Stage: PEP vs. PDP
&lt;/h2&gt;

&lt;p&gt;Before looking at the engines themselves, let's clarify where authorization sits within the architecture and what exactly an authorization engine—or Policy Decision Point (PDP)—does.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7qeohm7mu1bmtuw88xcz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7qeohm7mu1bmtuw88xcz.png" alt="PEP vs PDP" width="442" height="866"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Authentication (Who are you?) → Token Issuance → Authorization (What can you do?) → Resource Access. In this flow, the authorization engine (PDP) and the AuthZEN API are strictly responsible for step 4: "Query AuthZ Decision."&lt;/p&gt;

&lt;p&gt;OAuth 2.0 and AuthZEN operate on different layers. OAuth is about passing tokens between a client and a resource server. AuthZEN is about the application asking a policy engine for a decision. I see articles conflating these two all the time, but they are entirely different beasts.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The Cambrian Explosion of Authorization Engines
&lt;/h2&gt;

&lt;p&gt;Let’s look at reality. As of April 2026, here is what the major authorization engine landscape looks like.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv3hh0qboatr6808fhnpa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv3hh0qboatr6808fhnpa.png" alt="Authorization Engines" width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's a lot. And while they might look similar from the outside, their foundational design philosophies are radically different.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. You Can't Choose If You Don't Know the Models
&lt;/h2&gt;

&lt;p&gt;The very first thing to understand when picking an authorization engine is the underlying &lt;strong&gt;access control model&lt;/strong&gt;. If you skip this, you will inevitably hit a wall where the engine simply cannot express your use case.&lt;/p&gt;

&lt;h3&gt;
  
  
  RBAC: Managing by Roles
&lt;/h3&gt;

&lt;p&gt;The simplest approach. Assign roles to users, and bind permissions to those roles.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuk6mxlcmulbzqj97662v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuk6mxlcmulbzqj97662v.png" alt="RBAC" width="557" height="157"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Kubernetes RBAC is a perfect example. It's simple, but as conditions grow, you suffer from Role Explosion. Try expressing "Only full-time engineers in the Tokyo office assigned to Project A can access the production environment" in strict RBAC. The number of role permutations becomes unmanageable.&lt;/p&gt;

&lt;h3&gt;
  
  
  ABAC: Deciding by Attributes
&lt;/h3&gt;

&lt;p&gt;Decisions are evaluated based on the &lt;strong&gt;attributes&lt;/strong&gt; of the user, the resource, and the environment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fowhjx1lkrsup4gjbpjs9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fowhjx1lkrsup4gjbpjs9.png" alt="ABAC" width="800" height="109"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is where OPA (Rego) and Cedar shine. It’s highly flexible, but the policies themselves can get complicated very quickly.&lt;/p&gt;

&lt;h3&gt;
  
  
  ReBAC: Deciding by Relationships
&lt;/h3&gt;

&lt;p&gt;Coined by the Google Zanzibar paper. It manages &lt;strong&gt;relationships&lt;/strong&gt; as a graph—for example, "alice is a viewer of doc:readme," or "all members of the eng group are viewers."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0su6p1mr81mwjtwjy9kz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0su6p1mr81mwjtwjy9kz.png" alt="ReBac" width="527" height="321"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;SpiceDB, OpenFGA, and Permify implement this model. It operates identically to how sharing works in Google Drive, making it the most natural fit for collaborative apps. However, it struggles with attribute-based conditions like "allow access only during business hours."&lt;/p&gt;

&lt;h3&gt;
  
  
  So, Which One Should You Use?
&lt;/h3&gt;

&lt;p&gt;Roughly speaking:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you want to do&lt;/th&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Candidates&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Simple role management&lt;/td&gt;
&lt;td&gt;RBAC&lt;/td&gt;
&lt;td&gt;Casbin, Spring Security, Kubernetes RBAC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Complex branching logic (Attributes)&lt;/td&gt;
&lt;td&gt;ABAC&lt;/td&gt;
&lt;td&gt;OPA, Cedar, Cerbos&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google Drive-style sharing / Hierarchies&lt;/td&gt;
&lt;td&gt;ReBAC&lt;/td&gt;
&lt;td&gt;SpiceDB, OpenFGA, Permify&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kubernetes policy control&lt;/td&gt;
&lt;td&gt;ABAC/RBAC&lt;/td&gt;
&lt;td&gt;OPA Gatekeeper, Kyverno&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In reality, you often end up with a hybrid of RBAC + ABAC or a combination of ReBAC + ABAC. Cedar natively supports both RBAC and ABAC, while Aserto's Topaz combines Zanzibar-style ReBAC with an OPA engine for ABAC.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. AuthZEN: Standardizing the AuthZ API
&lt;/h2&gt;

&lt;p&gt;All the authorization engines we’ve looked at share a glaring problem: &lt;strong&gt;Their APIs are completely fragmented.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;OPA wants &lt;code&gt;{"input": {...}}&lt;/code&gt; sent via &lt;code&gt;POST /v1/data/...&lt;/code&gt;. Cedar uses a different API entirely. SpiceDB expects a gRPC &lt;code&gt;CheckPermission&lt;/code&gt; call. They are all different.&lt;/p&gt;

&lt;p&gt;This means if you ever decide to swap engines, you have to rewrite all of your application code. We successfully separated PDP (decision) from PEP (enforcement), but we never standardized the protocol between them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftmvwdfwmjei6wdn5r00q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftmvwdfwmjei6wdn5r00q.png" alt="AuthZEN" width="800" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In January 2026, the OpenID Foundation officially approved the &lt;strong&gt;AuthZEN Authorization API 1.0&lt;/strong&gt; as a Final Specification. It standardizes the communication between the PEP and PDP, allowing you to use the exact same JSON API regardless of which underlying engine is running.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Request:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Can&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;subject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;perform&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;action&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;resource?&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;POST&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/access/v&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;/evaluation&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subject"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"alice"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"read"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"document"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"doc-123"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Response&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"decision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why does this matter? It decouples your engine choice from your application code. Starting with OPA and later swapping it out for Cedar as your use case evolves is suddenly a realistic option.&lt;/p&gt;

&lt;h3&gt;
  
  
  Making OPA AuthZEN-Compatible
&lt;/h3&gt;

&lt;p&gt;I built a plugin that makes OPA natively speak AuthZEN.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/kanywst/opa-authzen-plugin" rel="noopener noreferrer"&gt;opa-authzen-plugin&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;OPA is a generic policy engine with its own REST API. Its paths, request structures, and response structures are entirely different from AuthZEN's. There was an &lt;code&gt;authzen-proxy&lt;/code&gt; built in Node.js sitting in the contrib repo, but running a separate proxy process alongside OPA felt less than ideal for production.&lt;/p&gt;

&lt;p&gt;So, I used OPA’s plugin architecture to run an AuthZEN server directly inside the OPA process itself. It’s the exact same pattern used by the &lt;code&gt;opa-envoy-plugin&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F83so54opozu23tx7h0v0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F83so54opozu23tx7h0v0.png" alt="opa-authzen-plugin" width="800" height="166"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Engines like Cerbos and Topaz have already started natively supporting AuthZEN. As more engines adopt it, the switching costs between them will continue to drop.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Why the Existing awesome-authorization Failed
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/warrant-dev/awesome-authorization" rel="noopener noreferrer"&gt;warrant-dev/awesome-authorization&lt;/a&gt; has around 420 stars and decent visibility. But looking closely at the content, there are obvious gaps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It focuses heavily on articles and concepts, lacking actual tools.&lt;/strong&gt; OPA gets exactly one line. Major engines like Cedar, OpenFGA, SpiceDB, Casbin, and Cerbos are entirely absent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It completely ignores modern standards.&lt;/strong&gt; The authorization spec world doesn’t end with OAuth 2.0. We have AuthZEN, SPIFFE, UMA, and GNAP reshaping the space, but none of them are covered.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It feels like a vendor proxy.&lt;/strong&gt; The repo puts the Warrant company banner right at the very top. It’s hard to call it a vendor-neutral community resource.&lt;/p&gt;

&lt;p&gt;So, I decided to build a better one.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Curating awesome-authorization
&lt;/h2&gt;

&lt;p&gt;I designed &lt;a href="https://github.com/kanywst/awesome-authorization" rel="noopener noreferrer"&gt;kanywst/awesome-authorization&lt;/a&gt; to cover the entire authorization landscape through the following sections:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7b5uw3hea886ujyds39l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7b5uw3hea886ujyds39l.png" alt="awesome-authorization" width="800" height="85"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The biggest differentiator from the old list is the &lt;strong&gt;Policy Engines section&lt;/strong&gt;. I explicitly categorized them into General Purpose, Zanzibar-based, Kubernetes Native, and AuthZEN-compatible. I wanted to create a place where anyone looking for an auth engine could instantly understand the entire current market.&lt;/p&gt;

&lt;p&gt;The Standards section is just as comprehensive, covering AuthZEN, OAuth/OIDC, SPIFFE/SPIRE, XACML, and GNAP. You can't grasp the "big picture" of authorization by just looking at tools—you need to understand the underlying specifications driving them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;There are too many authorization engines, and no place to make sense of them all. So I fixed that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/kanywst/awesome-authorization" rel="noopener noreferrer"&gt;kanywst/awesome-authorization&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Whether you are trying to select a policy engine, research a standard specification, or find that one specific engineering blog post you read months ago, treat this repository as your starting point.&lt;/p&gt;

&lt;p&gt;PRs are absolutely welcome. If a good tool or article is missing, please add it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Related Articles
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/kanywst/authzen-authorization-api-10-deep-dive-the-standard-api-that-separates-authorization-decisions-1m2a"&gt;AuthZEN Authorization API 1.0 Deep Dive&lt;/a&gt; : Deep Dive into the AuthZEN Spec&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/kanywst/i-built-an-opa-plugin-that-turns-it-into-an-authzen-compatible-pdp-eac"&gt;I Built an OPA Plugin That Turns It Into an AuthZEN-Compatible PDP&lt;/a&gt; : Design and Implementation of opa-authzen-plugin&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/kanywst/google-zanzibar-deep-dive-handling-2-trillion-acls-in-under-10ms-456d"&gt;Google Zanzibar Deep Dive&lt;/a&gt; : Explaining the Zanzibar Paper&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/kanywst/rbac-vs-abac-vs-rebac-how-to-choose-and-implement-access-control-models-3c89"&gt;RBAC vs ABAC vs ReBAC&lt;/a&gt; : Comparing Access Control Models&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>authorization</category>
      <category>security</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>RFC 7523 Deep Dive: JWT Profile</title>
      <dc:creator>kt</dc:creator>
      <pubDate>Mon, 13 Apr 2026 11:20:54 +0000</pubDate>
      <link>https://dev.to/kanywst/rfc-7523-deep-dive-jwt-profile-2df5</link>
      <guid>https://dev.to/kanywst/rfc-7523-deep-dive-jwt-profile-2df5</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;I perfectly understand how human users authenticate in modern web apps via OAuth 2.0. A browser opens, the user clicks "Allow" on a consent screen, and an access token is issued. &lt;/p&gt;

&lt;p&gt;But the other day, while looking at a backend architecture involving dozens of microservices, a fundamental question hit me: &lt;strong&gt;"How does machine-to-machine (M2M) authentication actually work when there is no human sitting in front of a browser?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For a long time, the answer was passing around a &lt;code&gt;client_secret&lt;/code&gt;—essentially a static password. However, in 2026, hardcoding access keys in CI/CD pipelines like GitHub Actions or configuration files is considered a risky, legacy practice. In its place, mechanisms that dynamically generate temporary credentials—most notably &lt;strong&gt;Workload Identity Federation&lt;/strong&gt;—have become the standard.&lt;/p&gt;

&lt;p&gt;Do you know the protocol specification that underpins this "secretless" M2M authentication infrastructure? That would be &lt;strong&gt;RFC 7523 (JSON Web Token (JWT) Profile for OAuth 2.0)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this article, I will dissect RFC 7523 to deeply explore why we are finally being liberated from the technical debt of &lt;code&gt;client_secret&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Relationship Between RFC 7521 and 7523: The Box and Its Contents
&lt;/h2&gt;

&lt;p&gt;To understand RFC 7523, we must first look at its parent, &lt;strong&gt;RFC 7521&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;RFC 7521 defines a "framework" stating that, "In OAuth 2.0, you may use an &lt;strong&gt;assertion&lt;/strong&gt; (verifiable data containing a claim) for client authentication or authorization grants." However, this specification does not dictate the actual data structure of the assertion.&lt;/p&gt;

&lt;p&gt;So, the rulebook that materialized this by saying, "Well, let's use the easily handled &lt;strong&gt;JWT (JSON Web Token)&lt;/strong&gt; as the data format for that assertion," is &lt;strong&gt;RFC 7523&lt;/strong&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Corresponding RFC&lt;/th&gt;
&lt;th&gt;Summary&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Abstract Framework&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;RFC 7521&lt;/td&gt;
&lt;td&gt;Common rules for using assertions (The Box).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;JWT Profile&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;RFC 7523&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The star of this article. Specific implementation rules using JWT (The Contents).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SAML Profile&lt;/td&gt;
&lt;td&gt;RFC 7522&lt;/td&gt;
&lt;td&gt;For SAML XML, often seen in enterprise legacy systems.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  2. The "Two Powerful Use Cases" Brought by JWT
&lt;/h2&gt;

&lt;p&gt;To integrate JWT into the OAuth 2.0 paradigm, RFC 7523 clearly defines &lt;strong&gt;two independent use cases&lt;/strong&gt;. Mixing these up will invariably lead to pitfalls during implementation, so pay close attention.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;JWT as an Authorization Grant&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JWT as Client Authentication&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These are completely different concepts. They can be used separately or both at the same time. Let's look at the raw mechanics of each.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Case 1: Using JWT as an "Authorization Grant" (The Secret of M2M Communication)
&lt;/h3&gt;

&lt;p&gt;Imagine a log aggregation worker for a batch process running late at night. There is no human sitting in front of a browser to click the "Allow Access" button.&lt;/p&gt;

&lt;p&gt;The standard Authorization Code Grant relies on human interaction. However, by using the JWT grant defined in RFC 7523, the client (such as a batch server) can &lt;strong&gt;cryptographically sign a JWT itself using its private key, hurl it directly at the authorization server, and pillage an access token&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Flow Diagram
&lt;/h4&gt;

&lt;p&gt;It is an incredibly simple, almost violently streamlined flow with all the fat trimmed off.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fggtntv2fwts8qh3r350n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fggtntv2fwts8qh3r350n.png" alt="Usecase 1" width="667" height="497"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this point, the HTTP request looks like the following. The most striking detail is that there is absolutely no &lt;code&gt;client_secret&lt;/code&gt; included in the request (assuming the authorization server's policy allows this). The signed JWT itself serves as a rock-solid proof of "who I am and what I am asking for."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="nf"&gt;POST&lt;/span&gt; &lt;span class="nn"&gt;/token&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;span class="na"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;as.example.com&lt;/span&gt;
&lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/x-www-form-urlencoded&lt;/span&gt;

grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer
&amp;amp;assertion=eyJhbGciOiJSUzI1NiIsImtp...[Signed_JWT]...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Use Case 2: Using JWT for "Client Authentication" (Goodbye, client_secret)
&lt;/h3&gt;

&lt;p&gt;The other use case is substituting the infamous &lt;code&gt;client_secret&lt;/code&gt; with a JWT as proof of identity in a standard flow (e.g., when exchanging an authorization code for an access token). If you've ever implemented backend communication for "Sign in with Apple," you've undoubtedly gone through this exact flow.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb9ef0yeez20xtlzpul4s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb9ef0yeez20xtlzpul4s.png" alt="usecase 2" width="638" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this case, the JWT is bundled into the &lt;code&gt;client_assertion&lt;/code&gt; parameter. Because you no longer need to save static passwords in network routing paths or GitHub environment variables, the security risk dramatically plummets.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. The Validation "Claims" You Must Never Compromise On
&lt;/h2&gt;

&lt;p&gt;"If throwing a JWT gets you a token, how do we block an attacker who just makes up a random JWT?"&lt;/p&gt;

&lt;p&gt;Section 3 of RFC 7523 lays out &lt;strong&gt;absolutely merciless, strict validation rules&lt;/strong&gt; for the claims that must be included in the JWT payload. The authorization server &lt;strong&gt;MUST reject&lt;/strong&gt; immediately any JWT that breaks even a single one of these rules.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Claim&lt;/th&gt;
&lt;th&gt;Required&lt;/th&gt;
&lt;th&gt;The Truth of Implementation &amp;amp; Attack Vectors&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;&lt;code&gt;iss&lt;/code&gt;&lt;/strong&gt; (Issuer)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Required&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;"Who issued this?"&lt;/strong&gt; For client authentication, this is your own &lt;code&gt;client_id&lt;/code&gt;. The comparison is done via Simple String Comparison based on RFC 3986.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;&lt;code&gt;sub&lt;/code&gt;&lt;/strong&gt; (Subject)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Required&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;"Who is this claim about?"&lt;/strong&gt; For authorization grants, this must match the entity delegating authority (e.g., user). For client authentication, it must match the &lt;code&gt;client_id&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;&lt;code&gt;aud&lt;/code&gt;&lt;/strong&gt; (Audience)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Required&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;"Who is this addressed to?"&lt;/strong&gt; This must contain the &lt;strong&gt;token endpoint URL of the authorization server&lt;/strong&gt;. Forgetting to validate this leads to a fatal vulnerability where &lt;strong&gt;a legitimate JWT issued for a different service is intercepted and diverted to yours&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;&lt;code&gt;exp&lt;/code&gt;&lt;/strong&gt; (Expiration)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Required&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;"The expiration date."&lt;/strong&gt; If it's in the past, reject it. In realistic cloud deployments, setting an extremely short window of 5–15 minutes is standard practice to minimize risk if intercepted.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Furthermore, the following properties are critical for security:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;jti&lt;/code&gt; (JWT ID)&lt;/strong&gt;: A unique identifier for the JWT. By caching this alongside the &lt;code&gt;exp&lt;/code&gt; claim, the authorization server can memorize used &lt;code&gt;jti&lt;/code&gt;s and refuse to accept them again, &lt;strong&gt;perfectly preventing replay attacks&lt;/strong&gt;. While technically MAY (optional) by spec, it is virtually mandatory in production.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cryptographic Algorithm&lt;/strong&gt;: RFC 7523 Section 5 explicitly states that &lt;strong&gt;&lt;code&gt;RS256&lt;/code&gt; (RSA signature) is Mandatory-to-Implement&lt;/strong&gt;. Lazy implementations that try to get away with &lt;code&gt;none&lt;/code&gt; or &lt;code&gt;HS256&lt;/code&gt; (which requires sharing a secret key) are strictly forbidden under this specification.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;RFC 7523 is not just "one of the ways to use JWT." It represents the &lt;strong&gt;final end to the endless, agonizing game of "where do we hide the static passwords?"&lt;/strong&gt; that we've suffered through for years.&lt;/p&gt;

&lt;p&gt;Accidentally pushing a Google Cloud service account key (JSON) to GitHub, only to have a Bitcoin mining cluster spun up seconds later leading to thousands of dollars in damages—such tragedies could be completely prevented with RFC 7523 and the Workload Identity architectural patterns built on top of it.&lt;/p&gt;

&lt;p&gt;The era of manually pasting &lt;code&gt;client_secret&lt;/code&gt; into source code and CI/CD environment variables is over. If your system is still executing legacy password-based authentication, you should immediately consider transitioning to a modern architecture utilizing public-key cryptography and JWT grants.&lt;/p&gt;




&lt;h3&gt;
  
  
  Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://datatracker.ietf.org/doc/html/rfc7523" rel="noopener noreferrer"&gt;RFC 7523 - JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://datatracker.ietf.org/doc/html/rfc7521" rel="noopener noreferrer"&gt;RFC 7521 - Assertion Framework for OAuth 2.0 Client Authentication and Authorization Grants&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>oauth</category>
      <category>jwt</category>
      <category>security</category>
      <category>authentication</category>
    </item>
    <item>
      <title>RFC 7636 Deep Dive: How PKCE Kills Authorization Code Interception Attacks</title>
      <dc:creator>kt</dc:creator>
      <pubDate>Sun, 12 Apr 2026 05:31:58 +0000</pubDate>
      <link>https://dev.to/kanywst/rfc-7636-deep-dive-how-pkce-kills-authorization-code-interception-attacks-91i</link>
      <guid>https://dev.to/kanywst/rfc-7636-deep-dive-how-pkce-kills-authorization-code-interception-attacks-91i</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;Last time, we tore apart the core mechanics of &lt;strong&gt;RFC 6749 (Authorization Code Grant)&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/kanywst/rfc-6749-deep-dive-understanding-oauth-20-design-decisions-from-the-specification-2amb"&gt;RFC 6749 Deep Dive: Understanding OAuth 2.0 Design Decisions from the Specification&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hopefully, those fundamentals clicked. But here’s the thing: the second you try writing your own OAuth client or start poking around IdP dashboards, you almost inevitably smash into this weird, lingering mystery.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"So... what exactly does this 'PKCE' thing protect against? Isn't the &lt;code&gt;state&lt;/code&gt; parameter enough?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You'll see that "Require PKCE" toggle sitting right there in the console. An alarming number of developers blindly flip it to "ON" just because it sounds like a sensible security upgrade.&lt;br&gt;
But if you can't instantly explain &lt;em&gt;why&lt;/em&gt; you MUST use &lt;code&gt;S256&lt;/code&gt;, or &lt;em&gt;how exactly&lt;/em&gt; your access tokens would get hijacked without PKCE, then congrats. You're essentially just praying to a magic internet spell.&lt;/p&gt;

&lt;p&gt;The true identity of this "feature everyone checks but nobody actually understands" is a clever cryptographic trick purposely built to shield authorization codes from the completely unhinged routing behavior of mobile OSes. &lt;strong&gt;It perfectly kills interception attacks. It is RFC 7636.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Consider this article the sequel to our RFC 6749 deep dive.&lt;br&gt;
We’re going to rip through the original RFC 7636 spec, uncover the beautifully simple cryptography behind PKCE, and expose the "don't ever do this" configurations that could ruin your app.&lt;/p&gt;


&lt;h2&gt;
  
  
  Scoping It Out
&lt;/h2&gt;

&lt;p&gt;Where exactly does RFC 7636 fit in the sprawling OAuth 2.0 universe?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft7eomj4af0lpffgdr6xb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft7eomj4af0lpffgdr6xb.png" alt="scope" width="800" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While RFC 6749 tells you "how to get a token," RFC 7636 tells you &lt;strong&gt;"how to stop people from stealing it while you're trying to get it."&lt;/strong&gt; We'll assume you already know how the standard Authorization Code Grant works.&lt;/p&gt;


&lt;h2&gt;
  
  
  1. The Problem: Auth Code Interception Attacks
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1.1 The Gap Between Server-Side Strictness and Mobile OS Chaos
&lt;/h3&gt;

&lt;p&gt;If you've spent your career in backend web dev, you probably think, "As long as the IdP strictly validates the &lt;code&gt;redirect_uri&lt;/code&gt;, we're bulletproof." And for server-side apps redirecting to a dedicated, TLS-secured &lt;code&gt;https://myapp.example.com/callback&lt;/code&gt;, you'd be right.&lt;/p&gt;

&lt;p&gt;But the minute you step into &lt;strong&gt;native mobile apps (iOS / Android)&lt;/strong&gt;, everything falls apart. To receive the callback (the auth code) from the browser, native apps rely on &lt;strong&gt;custom URI schemes&lt;/strong&gt; like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;myapp://oauth/callback?code=AUTH_CODE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There’s a fatal, gaping vulnerability here. The moment the auth server tells the browser to redirect to &lt;code&gt;myapp://&lt;/code&gt;, &lt;strong&gt;it completely surrenders control over which app on that phone actually catches the URL.&lt;/strong&gt; The ball gets thrown out of your safe, locked-down server environment and straight into the lawless wasteland of the mobile OS.&lt;/p&gt;

&lt;p&gt;And guess what? Android and iOS do &lt;em&gt;not&lt;/em&gt; guarantee custom URI schemes are unique.&lt;br&gt;
A malicious app doesn’t have to pull off an Ocean's Eleven heist on your auth server. It just sits there. When it gets installed, it casually tells the OS (via a few lines of text in &lt;code&gt;Info.plist&lt;/code&gt; or &lt;code&gt;AndroidManifest.xml&lt;/code&gt;): &lt;em&gt;"Hey, I can open &lt;code&gt;myapp://&lt;/code&gt; too."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When the browser fires that redirect with your precious authorization code, the OS can't tell which app is the "real" one. The routing decision is a complete coin toss. If the OS capriciously hands it to the malicious app, your auth code is gone.&lt;/p&gt;
&lt;h3&gt;
  
  
  1.2 The Attack Sequence
&lt;/h3&gt;

&lt;p&gt;RFC 7636 §1 sketches this out vividly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsiv9stao8tuw79tcphh0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsiv9stao8tuw79tcphh0.png" alt="Attack Sequence" width="800" height="564"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You might think, &lt;em&gt;"Does this actually happen in the wild, or is it just academic paranoia?"&lt;/em&gt; RFC 7636 §1 delivers a cold reality check:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;While this is a long list of pre-conditions, the described attack has been observed in the wild.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  1.3 Why Not Just Use Existing Defenses?
&lt;/h3&gt;

&lt;p&gt;I know what you're thinking: &lt;em&gt;"Why not just authenticate with a &lt;code&gt;client_secret&lt;/code&gt;?"&lt;/em&gt;&lt;br&gt;
Because &lt;strong&gt;Public Clients (like mobile apps and SPAs) cannot keep a secret.&lt;/strong&gt; If you ship a hardcoded secret in a mobile binary, it will be extracted before you finish your morning coffee.&lt;/p&gt;

&lt;p&gt;Since the attacker and the real app both show up at the Token Endpoint holding the exact same credentials (&lt;code&gt;code&lt;/code&gt; + &lt;code&gt;client_id&lt;/code&gt;), the auth server is blind. It has absolutely no way to tell the good guys from the bad guys.&lt;/p&gt;


&lt;h2&gt;
  
  
  2. The Core Concept of PKCE
&lt;/h2&gt;

&lt;p&gt;The fix for this mess is so simple it almost feels anticlimactic.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The app starting the authorization request generates a &lt;strong&gt;"one-time, highly random secret string,"&lt;/strong&gt; hashes it, and sends the &lt;strong&gt;hash&lt;/strong&gt; with the request.&lt;/li&gt;
&lt;li&gt;The auth server issues the code, tying it to that stored hash.&lt;/li&gt;
&lt;li&gt;When exchanging the code for a token, the app must prove it's the real deal by presenting the &lt;strong&gt;original, unhashed secret string&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The attacker doesn't know the original string. It only lives in the RAM of the legitimate app. You can steal the code, but without the secret string, the auth server will slam the door in your face. That is PKCE.&lt;/p&gt;


&lt;h2&gt;
  
  
  3. PKCE Protocol Details
&lt;/h2&gt;

&lt;p&gt;We can step right through the actual protocol (RFC 7636 §4) to see how this works.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbk6f09boj5rt2zvhluej.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbk6f09boj5rt2zvhluej.png" alt="PKCE Protocol Details" width="800" height="765"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  3.1 Generating the code_verifier (§4.1)
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;code_verifier&lt;/code&gt; is a cryptographically secure random string that the client generates freshly for every single request.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Length&lt;/strong&gt;: 43 to 128 characters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Characters allowed&lt;/strong&gt;: &lt;code&gt;[A-Z]&lt;/code&gt;, &lt;code&gt;[a-z]&lt;/code&gt;, &lt;code&gt;[0-9]&lt;/code&gt;, &lt;code&gt;-&lt;/code&gt;, &lt;code&gt;.&lt;/code&gt;, &lt;code&gt;_&lt;/code&gt;, &lt;code&gt;~&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Entropy&lt;/strong&gt;: Recommended 256 bits
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;secrets&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;

&lt;span class="c1"&gt;# Generate 32 bytes (256 bits) of pure randomness
&lt;/span&gt;&lt;span class="n"&gt;random_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;token_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# base64url encode (remove padding)
&lt;/span&gt;&lt;span class="n"&gt;code_verifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlsafe_b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random_bytes&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;rstrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ascii&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code_verifier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Example output: dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;⚠️ &lt;strong&gt;Never&lt;/strong&gt; use predictable pseudo-random number generators like &lt;code&gt;Math.random()&lt;/code&gt;. Just don't.&lt;/p&gt;
&lt;h3&gt;
  
  
  3.2 Calculating the code_challenge (§4.2)
&lt;/h3&gt;

&lt;p&gt;Next up, we derive the &lt;code&gt;code_challenge&lt;/code&gt; from that &lt;code&gt;code_verifier&lt;/code&gt;. The industry standard is &lt;code&gt;S256&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The spec says you &lt;em&gt;MAY&lt;/em&gt; use &lt;code&gt;plain&lt;/code&gt; (i.e., no hashing) if some technical constraint absolutely prevents &lt;code&gt;S256&lt;/code&gt;. To put it bluntly: &lt;strong&gt;In the modern era, there is zero excuse to use &lt;code&gt;plain&lt;/code&gt;. Ever.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3.3 Hands-On: Auth &amp;amp; Token Requests
&lt;/h3&gt;

&lt;p&gt;Staring at Mermaid diagrams gets boring fast. Let’s simulate the raw PKCE flow using your terminal and &lt;code&gt;curl&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;① Authorization Request (via browser)&lt;/strong&gt;&lt;br&gt;
The client includes &lt;code&gt;code_challenge&lt;/code&gt; and &lt;code&gt;code_challenge_method&lt;/code&gt; as parameters and redirects the user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# The URL actually hit in the browser's address bar&lt;/span&gt;
curl &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"https://auth.example.com/authorize?&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
response_type=code&amp;amp;&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
client_id=my-native-app&amp;amp;&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
redirect_uri=myapp://callback&amp;amp;&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
state=xyz123&amp;amp;&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&amp;amp;&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
code_challenge_method=S256"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Behind the scenes, the server ties &lt;code&gt;E9Melhoa...&lt;/code&gt; to the issued &lt;code&gt;code&lt;/code&gt;, stores it, and returns a redirect back to the app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;② Token Request&lt;/strong&gt;&lt;br&gt;
The app, having caught the incoming authorization code, sends the &lt;code&gt;code_verifier&lt;/code&gt; (which it kept safely tucked away in its own memory) straight to the Token Endpoint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://auth.example.com/token"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/x-www-form-urlencoded"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"grant_type=authorization_code"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"client_id=my-native-app"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"redirect_uri=myapp://callback"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"code=AUTH_CODE_RECEIVED"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server hashes the received &lt;code&gt;code_verifier&lt;/code&gt; with SHA-256 and checks if it matches the stored &lt;code&gt;code_challenge&lt;/code&gt;. If they match, congratulations—you get the access token.&lt;br&gt;
If an attacker intercepted the &lt;code&gt;code&lt;/code&gt; and sends it here, their &lt;code&gt;code_verifier&lt;/code&gt; will be empty or garbage, and the server will mercilessly kick them out with an &lt;code&gt;invalid_grant&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. The Fatal Flaw of the "plain" Method
&lt;/h2&gt;

&lt;p&gt;Why am I relentlessly telling you to avoid &lt;code&gt;plain&lt;/code&gt;?&lt;br&gt;
Because &lt;code&gt;plain&lt;/code&gt; literally means &lt;code&gt;code_challenge = code_verifier&lt;/code&gt;. You are passing your secret completely unhashed.&lt;/p&gt;

&lt;p&gt;If the smartphone's HTTP history leaks, or if the auth request gets intercepted over a compromised TLS proxy, the attacker instantly sees your &lt;code&gt;code_challenge&lt;/code&gt;. If you're using &lt;code&gt;plain&lt;/code&gt;, they just got your &lt;code&gt;code_verifier&lt;/code&gt; for free. Once they snatch the auth code, they have all the pieces, and your entire PKCE defensive wall collapses.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;S256&lt;/code&gt;, even if they intercept every single auth request and steal the &lt;code&gt;code_challenge&lt;/code&gt;, trying to reverse-engineer the original &lt;code&gt;code_verifier&lt;/code&gt; out of that SHA-256 hash requires a pre-image attack. Good luck with that. They'll be crunching hashes until the sun burns out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using &lt;code&gt;plain&lt;/code&gt; is essentially opting out of security while pretending you didn't.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  5. PKCE vs. The 'state' Parameter
&lt;/h2&gt;

&lt;p&gt;"Wait, doesn't the &lt;code&gt;state&lt;/code&gt; parameter prevent CSRF? Why do I need both?"&lt;br&gt;
Simply put: &lt;strong&gt;They block attacks coming from totally opposite directions.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;state&lt;/code&gt; (RFC 6749)&lt;/th&gt;
&lt;th&gt;PKCE (RFC 7636)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;What it stops&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CSRF (Auth code &lt;em&gt;injection&lt;/em&gt;)&lt;/td&gt;
&lt;td&gt;Auth code &lt;em&gt;interception&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Attack direction&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Attacker → Victim (Forces the attacker's code into your session)&lt;/td&gt;
&lt;td&gt;Victim → Attacker (Steals your code for the attacker's session)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Where it's verified&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Client-side (Your app checks the callback)&lt;/td&gt;
&lt;td&gt;Server-side (Token Endpoint validates the verifier)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;state&lt;/code&gt; ensures nobody shoves a sketchy auth code into your app. PKCE ensures nobody takes your auth code and uses it elsewhere. They aren't mutually exclusive. &lt;strong&gt;You absolutely must use both.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Security Design Details
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why Doesn't S256 Use a Salt? (§7.3)
&lt;/h3&gt;

&lt;p&gt;If you know anything about passwords, you know you need a salt. So why doesn't PKCE use one for its hashes?&lt;br&gt;
Because the base entropy is already at lethal levels.&lt;/p&gt;

&lt;p&gt;Passwords need salts because human-readable passwords have pathetically low entropy, leaving them wide open to pre-computation (rainbow table) attacks. A &lt;code&gt;code_verifier&lt;/code&gt;, however, is 256 bits of pure cryptographic randomness. Brute-forcing it is physically impossible in our universe. Adding a salt achieves absolutely nothing except making your code messier.&lt;/p&gt;

&lt;h3&gt;
  
  
  Surviving Downgrade Attacks (§7.2)
&lt;/h3&gt;

&lt;p&gt;RFC 7636 has a strict rule:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Clients MUST NOT downgrade to "plain" after trying the "S256" method.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you send &lt;code&gt;S256&lt;/code&gt; and the auth server throws an error, do &lt;em&gt;not&lt;/em&gt; try to be helpful and resend the request using &lt;code&gt;plain&lt;/code&gt;. That’s exactly how a Man-In-The-Middle attacker strips away your security (a downgrade attack). If &lt;code&gt;S256&lt;/code&gt; fails, the flow is compromised. Kill it immediately.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;RFC 7636 is a breezy 20-page specification, but it fundamentally fixed one of the scariest vulnerabilities in the OAuth 2.0 ecosystem: delivering the authorization code.&lt;/p&gt;

&lt;p&gt;To sum it up:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Custom URI schemes on native apps are a free-for-all.&lt;/strong&gt; Anyone can register them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PKCE locks down the code&lt;/strong&gt; using a mathematical secret only the request initiator knows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use S256.&lt;/strong&gt; Anyone telling you to use &lt;code&gt;plain&lt;/code&gt; is reckless.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;PKCE was initially pitched as an "optional extension for Public Clients." Today, under the current OAuth 2.0 Security Best Current Practice (BCP), &lt;strong&gt;it is strictly mandatory for everyone&lt;/strong&gt;, even Confidential Clients sitting on secure backend servers.&lt;/p&gt;

&lt;p&gt;Skipping PKCE in a modern system isn't an "option." It's just plain wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://datatracker.ietf.org/doc/html/rfc7636" rel="noopener noreferrer"&gt;RFC 7636 - Proof Key for Code Exchange by OAuth Public Clients&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://datatracker.ietf.org/doc/html/rfc6749" rel="noopener noreferrer"&gt;RFC 6749 - The OAuth 2.0 Authorization Framework&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://datatracker.ietf.org/doc/html/rfc8252" rel="noopener noreferrer"&gt;RFC 8252 - OAuth 2.0 for Native Apps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://datatracker.ietf.org/doc/html/rfc9700" rel="noopener noreferrer"&gt;OAuth 2.0 Security Best Current Practice&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>oauth</category>
      <category>security</category>
      <category>authentication</category>
      <category>pkce</category>
    </item>
    <item>
      <title>Why Do SSL/TLS Certificate Lifetimes Keep Getting Shorter?: Everything You Need to Know for the 47-Day Era</title>
      <dc:creator>kt</dc:creator>
      <pubDate>Sat, 11 Apr 2026 05:16:18 +0000</pubDate>
      <link>https://dev.to/kanywst/why-do-ssltls-certificate-lifetimes-keep-getting-shorter-everything-you-need-to-know-for-the-2log</link>
      <guid>https://dev.to/kanywst/why-do-ssltls-certificate-lifetimes-keep-getting-shorter-everything-you-need-to-know-for-the-2log</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;The other day, I was casually scrolling through my server's certificate renewal logs when something caught my eye.&lt;/p&gt;

&lt;p&gt;"...Wait, this renewed again? Didn't it just do that?"&lt;/p&gt;

&lt;p&gt;Let's Encrypt certificate renewals hit every 90 days. Since I've automated the process, I don't usually think about it—but looking at the logs, certbot was running practically every month. And then it hit me. Just a few years ago, certificate lifetimes were &lt;strong&gt;1 year&lt;/strong&gt;. Go further back, and they were &lt;strong&gt;3 years&lt;/strong&gt;. Why did they get so short?&lt;/p&gt;

&lt;p&gt;Down the rabbit hole I went, and the story was far deeper than I expected.&lt;/p&gt;

&lt;p&gt;In April 2025, the CA/Browser Forum unanimously passed &lt;strong&gt;Ballot SC-081v3&lt;/strong&gt;. Here's what it says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;By March 2029, the maximum validity period for SSL/TLS certificates will be reduced to 47 days.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;47 days. For anyone who remembers buying "3-year certificates," this sounds absurd. But behind this decision lies a fatal problem that Web PKI has carried for years: &lt;strong&gt;certificate revocation is structurally broken.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The first half of this article explains &lt;em&gt;why&lt;/em&gt; certificates need to be shorter. The second half covers &lt;em&gt;what that means for us&lt;/em&gt; as engineers.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. What Even Is a "Certificate Validity Period"?
&lt;/h2&gt;

&lt;p&gt;Before we can talk about shortening lifetimes, we need to understand what the validity period actually does.&lt;/p&gt;

&lt;p&gt;An SSL/TLS certificate has two fields: &lt;code&gt;Not Before&lt;/code&gt; (start date) and &lt;code&gt;Not After&lt;/code&gt; (end date). You can check them with &lt;code&gt;openssl&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; | openssl s_client &lt;span class="nt"&gt;-connect&lt;/span&gt; example.com:443 2&amp;gt;/dev/null | openssl x509 &lt;span class="nt"&gt;-noout&lt;/span&gt; &lt;span class="nt"&gt;-dates&lt;/span&gt;
&lt;span class="c"&gt;# notBefore=Jan 30 00:00:00 2025 GMT&lt;/span&gt;
&lt;span class="c"&gt;# notAfter=Mar 1 23:59:59 2026 GMT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The span between &lt;code&gt;notBefore&lt;/code&gt; and &lt;code&gt;notAfter&lt;/code&gt; is the "validity period." Browsers categorically refuse to trust certificates outside this window.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So why does a validity period exist in the first place?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Certificate trust depends on the private key. Because the server holds the private key, it can prove it's the legitimate owner of that certificate. But what if the private key leaks? An attacker can impersonate the legitimate server using that certificate.&lt;/p&gt;

&lt;p&gt;The validity period is the &lt;strong&gt;last line of defense&lt;/strong&gt; against private key compromise. Even if nobody notices the leak, the certificate automatically becomes worthless once it expires.&lt;/p&gt;

&lt;p&gt;In other words: &lt;strong&gt;validity period = the maximum window an attacker can exploit a compromised key.&lt;/strong&gt; The shorter it is, the smaller the blast radius.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The History of Validity Periods: 10 Years → 47 Days
&lt;/h2&gt;

&lt;p&gt;Certificate maximum validity has been shrinking consistently since the 2000s. Understanding this history makes it clear that the current trend isn't a sudden policy shift—it's the &lt;strong&gt;continuation of a 20-year trajectory&lt;/strong&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Period&lt;/th&gt;
&lt;th&gt;Max Validity&lt;/th&gt;
&lt;th&gt;Trigger&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;~2011&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8–10 years&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Early SSL era. Long-lived certificates were the norm&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2012&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5 years (60m)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CA/Browser Forum establishes Baseline Requirements&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2015&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3 years (39m)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Baseline Requirements amendment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mar 2018&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2 years (825d)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Ballot 193&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Sep 2020&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1 year (398d)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Apple unilaterally enforced in Safari. Google and Mozilla followed&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mar 2026&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;200 days&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;SC-081v3 Phase 1 (※ we are here now)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mar 2027&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100 days&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;SC-081v3 Phase 2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mar 2029&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;47 days&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;SC-081v3 Phase 3 (final target)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 2020 event deserves extra context. A vote within the CA/Browser Forum failed to reach consensus, yet &lt;strong&gt;Apple unilaterally announced that Safari would refuse to trust any certificate with a validity exceeding 398 days.&lt;/strong&gt; Google and Mozilla followed suit, making it a de facto industry standard.&lt;/p&gt;

&lt;p&gt;This revealed the power dynamics of Web PKI in stark terms. No matter how many long-lived certificates a CA issues, &lt;strong&gt;if the browsers say "we don't trust it," that's the end of the discussion.&lt;/strong&gt; A certificate that Safari, Chrome, and Firefox reject is effectively useless on the internet.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Revocation Is Broken — The Real Reason Behind Shorter Lifetimes
&lt;/h2&gt;

&lt;p&gt;Here's the core of this article.&lt;/p&gt;

&lt;p&gt;"If a private key leaks, can't you just revoke the certificate immediately? Why bother shortening the validity period itself?"&lt;/p&gt;

&lt;p&gt;Fair question. But here's the thing: the "just revoke it" mechanism &lt;strong&gt;barely works in practice.&lt;/strong&gt; This is the single biggest reason certificate lifetimes are being pushed shorter.&lt;/p&gt;

&lt;p&gt;Let's walk through it.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.1 What Is Revocation?
&lt;/h3&gt;

&lt;p&gt;First, the basics.&lt;/p&gt;

&lt;p&gt;Certificate revocation means &lt;strong&gt;a CA (Certificate Authority) declares that a still-valid certificate should no longer be trusted.&lt;/strong&gt; This is used when a private key is compromised, domain ownership transfers, or a certificate was mis-issued—situations where trust needs to be withdrawn before the natural expiration date.&lt;/p&gt;

&lt;p&gt;The problem is: how do you communicate this "declaration" to clients (browsers)? Historically, two mechanisms were built for this: CRL and OCSP.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.2 The Problem with CRL (Certificate Revocation Lists)
&lt;/h3&gt;

&lt;p&gt;CRL is the oldest revocation-checking mechanism. The CA periodically publishes a file containing serial numbers of all revoked certificates, and browsers download it to cross-check.&lt;/p&gt;

&lt;p&gt;Sounds simple enough, but it has three fatal flaws:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The file gets enormous.&lt;/strong&gt; When a major CA revokes a large number of certificates, the CRL can balloon to tens of megabytes. Downloading this every time a user visits an HTTPS site is wildly impractical&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Update frequency is low.&lt;/strong&gt; A CRL specifies a "Next Update" date, and the stale list is cached until then. Any certificates revoked in the interim are not reflected&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If the CA's infrastructure goes down, you can't fetch it.&lt;/strong&gt; If the CRL Distribution Point (CDP) is overloaded or experiencing an outage, revocation checking becomes flat-out impossible&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  3.3 The Problem with OCSP (Online Certificate Status Protocol)
&lt;/h3&gt;

&lt;p&gt;If CRL is "downloading the entire phone book," OCSP is "calling to ask about one number at a time." The browser queries the CA's OCSP responder in real time for each individual certificate: "Is this certificate still valid?"&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7lgr0acpi1wu4525g646.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7lgr0acpi1wu4525g646.png" alt="OSCP" width="771" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Looks better than CRL, but this is where Web PKI's &lt;strong&gt;most critical design flaw&lt;/strong&gt; lives. Look at the "else" branch above.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.4 Soft-fail: Revocation Fails Exactly When You Need It Most
&lt;/h3&gt;

&lt;p&gt;What happens when the browser can't reach the OCSP responder?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nearly every browser chooses to "trust it anyway" (soft-fail).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Why? If browsers hard-failed (refused the connection when OCSP is unreachable), &lt;strong&gt;a single outage on a CA's OCSP server would make every website issued by that CA inaccessible worldwide.&lt;/strong&gt; You couldn't even reach a hotel Wi-Fi captive portal to log in. That user experience is unacceptable, so browser vendors chose soft-fail.&lt;/p&gt;

&lt;p&gt;But soft-fail is &lt;strong&gt;trivially exploitable by attackers.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2r45mc2jhzmdza6xjkrd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2r45mc2jhzmdza6xjkrd.png" alt="OSCP Soft-fail" width="780" height="654"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Consider a MITM (Man-in-the-Middle) attack scenario:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The attacker uses a &lt;strong&gt;leaked private key&lt;/strong&gt; to present a &lt;strong&gt;revoked certificate&lt;/strong&gt; to the victim&lt;/li&gt;
&lt;li&gt;The victim's browser attempts to verify revocation status via OCSP&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The attacker controls the network and blocks the OCSP request&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;The browser gets no response—soft-fail triggers, and it "trusts it anyway"&lt;/li&gt;
&lt;li&gt;The attacker maintains the impersonation using a revoked certificate, intercepting and modifying traffic at will&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Revocation checking fails precisely in the scenario where it's needed most—when you're under attack.&lt;/strong&gt; This is a design-level contradiction. It looks like a seat belt, but it's actually a harness that's not attached to anything.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.5 OCSP Stapling — A Partial Fix
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;OCSP Stapling&lt;/strong&gt; was created to address this. Instead of the browser reaching out to the CA, the &lt;strong&gt;web server itself&lt;/strong&gt; pre-fetches a CA-signed OCSP response and delivers it to the client during the TLS handshake, bundled with the certificate.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F02wzrj2l67maz66qjznq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F02wzrj2l67maz66qjznq.png" alt="OCSP Stapling" width="800" height="523"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This eliminates the attacker's ability to block OCSP requests. It also sidesteps the privacy issue of browsers leaking browsing history to the CA.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Must-Staple&lt;/strong&gt;, a certificate extension, takes this further by instructing the browser: "Reject the connection if OCSP Stapling is not present."&lt;/p&gt;

&lt;p&gt;However, &lt;strong&gt;Must-Staple adoption is extremely low.&lt;/strong&gt; If a server operator fails to fetch the OCSP response, even a legitimate site becomes inaccessible. Most sites don't take that risk.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.6 Browser Vendors' Response: "We Gave Up on Revocation Checking"
&lt;/h3&gt;

&lt;p&gt;Faced with these problems, major browsers went their own way.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Browser&lt;/th&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Chrome&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;CRLSets&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Google curates a high-risk revocation list bundled with and pushed to the browser. Real-time OCSP was &lt;strong&gt;discontinued in 2012&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Firefox&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;CRLite&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A Bloom filter–based compressed revocation database distributed to the browser. Covers all certificates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Safari&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;OCSP-based&lt;/td&gt;
&lt;td&gt;Routes through Apple's proprietary OCSP proxy. Privacy concerns remain&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Chrome dropped real-time OCSP checks entirely in 2012. In Adam Langley's words (then Chrome's security engineer), OCSP was &lt;strong&gt;"a seat belt that looks like it works but isn't actually attached to anything."&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3.7 So the Only Option Is to Make Them Shorter
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgy6k9n5nii0b4wl7gyzx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgy6k9n5nii0b4wl7gyzx.png" alt="Revocation is broken" width="800" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If revocation is broken, just &lt;strong&gt;build a world that doesn't rely on revocation.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Make certificate lifetimes short enough that even if a private key is compromised, the certificate naturally dies within days to weeks. It doesn't matter if OCSP is broken. The certificate expires on its own.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is the essence of short-lived certificates.&lt;/strong&gt; It's not "shortening them because it's convenient." It's "shortening them because revocation is broken and there's no other viable path."&lt;/p&gt;




&lt;h2&gt;
  
  
  4. CA/Browser Forum Ballot SC-081v3: The Phased Transition to 47 Days
&lt;/h2&gt;

&lt;p&gt;With the shared understanding that "revocation is broken," the CA/Browser Forum unanimously passed SC-081v3 in April 2025. Apple, Google, Mozilla, Microsoft—all major browser vendors—along with major CAs like DigiCert, Sectigo, and Let's Encrypt signed on.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.1 Phased Transition Schedule
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Effective Date&lt;/th&gt;
&lt;th&gt;Max Certificate Validity&lt;/th&gt;
&lt;th&gt;DCV Reuse Period&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;March 15, 2026&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;200 days&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;200 days&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;March 15, 2027&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100 days&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100 days&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;March 15, 2029&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;47 days&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10 days&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  4.2 What Is DCV and Why Is It Being Shortened Too?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;DCV (Domain Control Validation)&lt;/strong&gt; is the process by which a CA verifies that the applicant actually owns or controls the domain they're requesting a certificate for. In Let's Encrypt's ACME protocol, this corresponds to HTTP-01 challenges (proving you can place a file at a specific path) or DNS-01 challenges (proving you can add a TXT record to DNS).&lt;/p&gt;

&lt;p&gt;Currently, once DCV succeeds, the result can be reused for up to 398 days—meaning certificates can be renewed without re-validation during that window.&lt;/p&gt;

&lt;p&gt;SC-081v3 &lt;strong&gt;shortens the DCV reuse period to 10 days by 2029.&lt;/strong&gt; Why? If domain ownership changes, stale DCV results could allow a certificate to be issued for the &lt;em&gt;previous&lt;/em&gt; owner. The shorter the DCV reuse period, the more accurately it reflects current ownership.&lt;/p&gt;

&lt;p&gt;In practice, this means &lt;strong&gt;every certificate renewal will effectively require re-proving domain ownership.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4.3 Why Specifically 47 Days?
&lt;/h3&gt;

&lt;p&gt;The number 47 originated from Apple's initial proposal (45 days) and was finalized after discussion within the CA/Browser Forum.&lt;/p&gt;

&lt;p&gt;No official formula has been published, but from an operational standpoint, the number is rational:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Days&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Certificate validity period&lt;/td&gt;
&lt;td&gt;47 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recommended renewal point (2/3 elapsed)&lt;/td&gt;
&lt;td&gt;~Day 31&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recovery window if renewal fails&lt;/td&gt;
&lt;td&gt;~16 days&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Renew once a month, and even if it fails a couple of times, you've still got roughly two weeks of recovery cushion.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Let's Encrypt's Foresight: 90 Days, Then 6
&lt;/h2&gt;

&lt;p&gt;Let's Encrypt has been &lt;strong&gt;leading this short-lived revolution ahead of the industry.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1 Why Let's Encrypt Started at 90 Days
&lt;/h3&gt;

&lt;p&gt;When Let's Encrypt launched in 2015, the industry standard was 2–3 year certificates. They deliberately chose 90 days, and explained their reasoning in an official blog post ("Why ninety-day lifetimes for certificates?"):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Limiting the damage window from a compromise&lt;/strong&gt;: Even if a private key leaks, the certificate automatically becomes invalid after at most 90 days&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forcing automation&lt;/strong&gt;: 90 days is too short for comfortable manual management. This effectively made automated renewal via ACME (RFC 8555) a requirement, not an option&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reducing dependence on revocation&lt;/strong&gt;: Exactly the problem covered in Section 3. When certificates are short-lived, broken OCSP is far less of an issue&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The 90-day choice was strategic. 60 days was too close to manual renewal cycles, causing frequent mishaps. 120 days left room for the "I can still do this manually" mentality. 90 days hit the sweet spot: "painfully tight without automation, but with the recommended 60-day renewal interval, you can run it monthly."&lt;/p&gt;

&lt;h3&gt;
  
  
  5.2 6-Day Certificates Arrive (January 2026)
&lt;/h3&gt;

&lt;p&gt;In January 2026, Let's Encrypt went even further with &lt;strong&gt;6-day (160-hour) certificates&lt;/strong&gt;, now generally available.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Requesting a 6-day certificate with certbot&lt;/span&gt;
certbot certonly &lt;span class="nt"&gt;--preferred-profile&lt;/span&gt; shortlived &lt;span class="nt"&gt;-d&lt;/span&gt; example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key points about 6-day certificates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validity period of 160 hours (~6.7 days)&lt;/li&gt;
&lt;li&gt;Requested by &lt;strong&gt;specifying the &lt;code&gt;shortlived&lt;/code&gt; certificate profile in ACME&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Also supports certificate issuance for IP addresses (short-lived only)&lt;/li&gt;
&lt;li&gt;Opt-in; coexists with standard 90-day certificates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No OCSP response embedding needed.&lt;/strong&gt; The certificate dies naturally in 6 days, making revocation checking meaningless&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;6-day certificates are the current cutting edge of the "world without revocation" described in Section 3.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. The Other Goal of Shorter Lifetimes: Preparing for Post-Quantum
&lt;/h2&gt;

&lt;p&gt;The primary driver behind shorter certificate lifetimes is "revocation is broken," but there's another critical context: &lt;strong&gt;Crypto Agility.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Crypto agility is &lt;strong&gt;the ability to rapidly switch cryptographic algorithms without causing widespread disruption.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Why does this matter? Current SSL/TLS certificates are signed with RSA or ECDSA, but when practical quantum computers arrive, &lt;strong&gt;both RSA and ECDSA are theoretically broken&lt;/strong&gt; (Shor's algorithm efficiently solves large integer factorization and discrete logarithm problems). When that happens, the entire web's certificates will need to migrate to NIST-standardized post-quantum algorithms (ML-KEM, ML-DSA, etc.).&lt;/p&gt;

&lt;p&gt;What separates organizations here is their "migration muscle":&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Organization State&lt;/th&gt;
&lt;th&gt;Response During Algorithm Migration&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Auto-renewing every 47 days&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Apply the new algorithm on the next renewal cycle. Full migration in weeks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Manually renewing once a year&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manually inventory and replace every certificate. Months-to-years emergency work&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Automating short-lived certificates isn't just about "automating a chore"—it's a &lt;strong&gt;structural hedge against future cryptographic crises.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Operational Problems Created by Shorter Lifetimes
&lt;/h2&gt;

&lt;p&gt;Up to this point, I've been making the case for &lt;em&gt;why&lt;/em&gt; shorter lifetimes are necessary. Now let's be honest about the downsides.&lt;/p&gt;

&lt;h3&gt;
  
  
  7.1 The End of Manual Management
&lt;/h3&gt;

&lt;p&gt;With 47-day certificates, you need roughly 8 renewals per year. Humans will absolutely drop the ball at that frequency.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Validity&lt;/th&gt;
&lt;th&gt;Renewals/Year&lt;/th&gt;
&lt;th&gt;Manual Management&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1 year (398d)&lt;/td&gt;
&lt;td&gt;~1&lt;/td&gt;
&lt;td&gt;Barely manageable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;200 days&lt;/td&gt;
&lt;td&gt;~2&lt;/td&gt;
&lt;td&gt;Doable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100 days&lt;/td&gt;
&lt;td&gt;~4&lt;/td&gt;
&lt;td&gt;Getting painful&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;47 days&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~8&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Physically impossible&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;ACME (RFC 8555) automation becomes the de facto standard. The tools already exist—certbot, acme.sh, Caddy's automatic HTTPS. The problem is &lt;strong&gt;the environments that haven't adopted them yet.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  7.2 Certificate Expiry Outages — Even Microsoft and SoftBank Got Burned
&lt;/h3&gt;

&lt;p&gt;"Forgetting to renew a certificate" happens inevitably when humans are in the loop. This isn't theoretical.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;Incident&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Impact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2017&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Equifax data breach&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;An expired certificate on a network monitoring device caused attack traffic to go &lt;strong&gt;undetected for 10 months&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;150 million individuals' personal data exposed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2018&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;SoftBank nationwide outage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;An expired certificate embedded in Ericsson's telecom switch software&lt;/td&gt;
&lt;td&gt;~4-hour outage across 11 countries including Japan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2020&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Microsoft Teams outage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Forgotten authentication server certificate renewal&lt;/td&gt;
&lt;td&gt;~3 hours of worldwide inaccessibility&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Even Microsoft—one of the largest tech companies on Earth—"forgot to renew a certificate." The SoftBank outage wasn't even their fault: it was an expired certificate embedded in vendor (Ericsson) switch software. When a certificate rots somewhere in the supply chain, everything downstream collapses.&lt;/p&gt;

&lt;p&gt;With renewal frequency increasing 8x in the 47-day era, the opportunities for these incidents multiply. &lt;strong&gt;Any system without automation becomes a ticking time bomb.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  7.3 Systems That Can't Support ACME
&lt;/h3&gt;

&lt;p&gt;Not every system supports ACME.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv9m63zcsawhbtr291xb5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv9m63zcsawhbtr291xb5.png" alt="Support ACME" width="800" height="998"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The categories that cause the most pain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hardware load balancers&lt;/strong&gt; (F5 BIG-IP, etc.): Many older models don't support ACME. Certificate installation is limited to manual upload via web UI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Firewall / VPN appliances&lt;/strong&gt;: Entirely dependent on vendor ACME support&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IoT / embedded devices&lt;/strong&gt;: Firmware updates alone can be challenging&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Legacy systems&lt;/strong&gt;: The "pickled" systems you can neither update nor decommission&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Manually uploading certificates to these devices every 47 days is, to put it mildly, torture.&lt;/p&gt;

&lt;h3&gt;
  
  
  7.4 DCV Reuse Period Shrinking to 10 Days
&lt;/h3&gt;

&lt;p&gt;As covered in Section 4.2, but worth emphasizing: after 2029, the DCV reuse period drops to 10 days.&lt;/p&gt;

&lt;p&gt;If you're using DNS-01 challenges, you'll be rewriting DNS TXT records on every certificate renewal. If your DNS API is unreliable or DNS propagation is slow, the renewal failure risk shoots up. In environments spanning multiple DNS providers, renewal script complexity explodes.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Surviving the Short-Lived Certificate Era
&lt;/h2&gt;

&lt;h3&gt;
  
  
  8.1 Full ACME Adoption
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foh92mtldj0eq5v80mb49.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foh92mtldj0eq5v80mb49.png" alt="Full ACME Adoption" width="674" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the bare minimum, but it's worth periodically verifying that certbot's auto-renewal is actually working.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check if the systemd timer is active&lt;/span&gt;
systemctl status certbot.timer

&lt;span class="c"&gt;# Rehearse to see if renewal actually succeeds&lt;/span&gt;
certbot renew &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  8.2 At Scale: Introducing CLM
&lt;/h3&gt;

&lt;p&gt;Once you're managing dozens to hundreds of servers, certbot alone isn't enough. You need a &lt;strong&gt;CLM (Certificate Lifecycle Management)&lt;/strong&gt; platform to centrally manage certificates across the organization.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;What It Does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Discovery&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Automatically detect all certificates on the network. Visualize "what's where"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Expiry Monitoring&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Alert on certificates approaching expiration. Slack / PagerDuty integration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Auto-renewal&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Execute automated renewal across ACME-compatible systems in bulk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Policy Enforcement&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Enforce rules like "RSA 4096+ only" or "no key reuse"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Reporting&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Compliance dashboard. Audit-ready reporting&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  9. Public vs. Private Certificates
&lt;/h2&gt;

&lt;p&gt;One more critical clarification. &lt;strong&gt;SC-081v3 only applies to "publicly trusted" TLS certificates—those issued by CAs in browsers' trust stores.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Certificate Type&lt;/th&gt;
&lt;th&gt;SC-081v3 Scope&lt;/th&gt;
&lt;th&gt;Examples&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Public certificates&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ In scope&lt;/td&gt;
&lt;td&gt;Public-facing websites, external APIs, mail servers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Private certificates&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ Out of scope&lt;/td&gt;
&lt;td&gt;Internal PKI-issued certs, inter-service mTLS, IoT device certs, dev/staging environments&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Certificates issued by your organization's private CA can have whatever validity period your policies dictate.&lt;/p&gt;

&lt;p&gt;That said, the best practices proven with public certificates—short lifetimes and automation—should be applied to private PKI as well. Workload identity frameworks like SPIFFE/SPIRE default to certificate lifetimes of roughly 1 hour, which sits squarely on this same trajectory.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The reason certificate lifetimes keep shrinking can be summed up in one sentence: &lt;strong&gt;"Because revocation is broken."&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;CRL and OCSP are structurally broken.&lt;/strong&gt; OCSP's soft-fail, in particular, harbors a fatal contradiction: it stops working precisely when you're under attack—the exact moment revocation checking matters most&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CA/Browser Forum SC-081v3 mandates a maximum validity of 47 days by March 2029.&lt;/strong&gt; DCV reuse periods are also being cut to 10 days&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Let's Encrypt led the charge with 90-day certificates and launched 6-day certificates in 2026&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shorter lifetimes directly support post-quantum migration readiness (Crypto Agility)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In the 47-day era, full ACME automation becomes a survival requirement.&lt;/strong&gt; Manual management is physically impossible&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SC-081v3 only applies to public certificates.&lt;/strong&gt; Private PKI can operate under its own policies&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Phase 1 (200 days) took effect in March 2026. It's already here. If your organization hasn't adopted automated certificate renewal yet, you'd better start moving before Phase 2 (100 days) arrives in 2027.&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cabforum.org/2025/04/07/ballot-sc-081v3-reduce-ssl-tls-certificate-validities/" rel="noopener noreferrer"&gt;CA/Browser Forum - Ballot SC-081v3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://letsencrypt.org/2015/11/09/why-90-days.html" rel="noopener noreferrer"&gt;Let's Encrypt - Why Ninety-Day Lifetimes for Certificates?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://letsencrypt.org/2024/12/04/short-lived-certs/" rel="noopener noreferrer"&gt;Let's Encrypt - Short-Lived Certificates (2024/2026)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://datatracker.ietf.org/doc/html/rfc8555" rel="noopener noreferrer"&gt;RFC 8555 - Automatic Certificate Management Environment (ACME)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://datatracker.ietf.org/doc/html/rfc6960" rel="noopener noreferrer"&gt;RFC 6960 - X.509 Internet PKI Online Certificate Status Protocol (OCSP)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.imperialviolet.org/2012/02/05/crlsets.html" rel="noopener noreferrer"&gt;Adam Langley - Revocation Checking and Chrome's CRL (2012)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.digicert.com/blog/tls-certificate-validity-changes" rel="noopener noreferrer"&gt;DigiCert - TLS Certificate Validity Changes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>tls</category>
      <category>devops</category>
      <category>pki</category>
    </item>
    <item>
      <title>Why I Built opa-authzen-interop: Verifying OPA on AuthZEN Interop</title>
      <dc:creator>kt</dc:creator>
      <pubDate>Sun, 05 Apr 2026 13:14:55 +0000</pubDate>
      <link>https://dev.to/kanywst/why-i-built-opa-authzen-interop-verifying-opa-on-authzen-interop-52i9</link>
      <guid>https://dev.to/kanywst/why-i-built-opa-authzen-interop-verifying-opa-on-authzen-interop-52i9</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;In my earlier post, &lt;a href="https://dev.to/kanywst/i-built-an-opa-plugin-that-turns-it-into-an-authzen-compatible-pdp-i81"&gt;I Built an OPA Plugin That Turns It Into an AuthZEN-Compatible PDP&lt;/a&gt;, I covered how I added AuthZEN API support (&lt;code&gt;POST /access/v1/evaluation&lt;/code&gt;) directly to OPA.&lt;/p&gt;

&lt;p&gt;When I looked at the interop results page, I saw multiple projects already validating on the same scenario.&lt;br&gt;&lt;br&gt;
That became the main reason I started this repo: &lt;strong&gt;I wanted to put my implementation on the same field and verify it there too.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So I built &lt;code&gt;opa-authzen-interop&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/kanywst/opa-authzen-interop" rel="noopener noreferrer"&gt;https://github.com/kanywst/opa-authzen-interop&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  What This Article Covers
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Why I separated an interop-focused repo from the plugin repo
&lt;/li&gt;
&lt;li&gt;How other projects are participating in OpenID AuthZEN interop
&lt;/li&gt;
&lt;li&gt;Where &lt;code&gt;opa-authzen-interop&lt;/code&gt; stands today and what comes next
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The article assumes basic familiarity with &lt;code&gt;PDP/PEP&lt;/code&gt; and the &lt;code&gt;AuthZEN Authorization API&lt;/code&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why A Separate Repo
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;opa-authzen-plugin&lt;/code&gt; is responsible for one thing: &lt;strong&gt;implementing the AuthZEN API in OPA&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
&lt;code&gt;interop&lt;/code&gt; is responsible for something different: &lt;strong&gt;locking scenario-specific policy/data/tests and validating compatibility&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If both live in one repo, the boundary between product implementation and spec-validation tests gets blurry.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjz6tgsbct1v6y9gzf8bg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjz6tgsbct1v6y9gzf8bg.png" alt="Separate repo" width="800" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In short: &lt;code&gt;plugin&lt;/code&gt; is product code, &lt;code&gt;interop&lt;/code&gt; is verification infrastructure.&lt;/p&gt;


&lt;h2&gt;
  
  
  Are Other Projects Doing Interop Too?
&lt;/h2&gt;

&lt;p&gt;Yes, absolutely.&lt;/p&gt;

&lt;p&gt;According to the official &lt;a href="https://authzen-interop.net/docs/intro/" rel="noopener noreferrer"&gt;AuthZEN Interop Introduction&lt;/a&gt;, seven formal interop events were organized from June 2024 through December 2025.&lt;/p&gt;

&lt;p&gt;On the Todo 1.1 (&lt;code&gt;authorization-api-1_0-02&lt;/code&gt;) results pages, OPA appears alongside Cerbos, Topaz, OpenFGA, Aserto, Axiomatics, WSO2, and others.&lt;/p&gt;

&lt;p&gt;One concrete public implementation example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/aserto-dev/authzen-topaz-proxy" rel="noopener noreferrer"&gt;aserto-dev/authzen-topaz-proxy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So this “interop-focused verification repo” approach is not unique to my project; it's already a practical pattern in the ecosystem. I aligned with that approach and split out verification assets for &lt;code&gt;opa-authzen-plugin&lt;/code&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  Current Status (as of April 5, 2026)
&lt;/h2&gt;

&lt;p&gt;Based on the &lt;code&gt;opa-authzen-interop&lt;/code&gt; README:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;authorization-api-1_0-01&lt;/code&gt; (single evaluation): 40/40 PASS&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POST /access/v1/evaluations&lt;/code&gt; (batch): not implemented yet, returns 404&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So there is still a clear gap.&lt;/p&gt;

&lt;p&gt;At the same time, the official OPA interop results page for Todo 1.1 already shows &lt;code&gt;authorization-api-1_0-02&lt;/code&gt; runs.&lt;br&gt;&lt;br&gt;
That makes the next goal straightforward: &lt;strong&gt;stabilize &lt;code&gt;1_0-02&lt;/code&gt; support in my own repo as well&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  Next Action
&lt;/h2&gt;

&lt;p&gt;The next three steps are clear:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Implement &lt;code&gt;POST /access/v1/evaluations&lt;/code&gt; in &lt;code&gt;opa-authzen-plugin&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Make &lt;code&gt;opa-authzen-interop&lt;/code&gt; green for &lt;code&gt;authorization-api-1_0-02&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Add interop tests to CI so regressions are caught at PR time
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Longer-term, as interop scenarios evolve, Search/IdP paths (&lt;code&gt;/access/v1/search/*&lt;/code&gt;) should also become tracking targets.&lt;/p&gt;


&lt;h2&gt;
  
  
  Authorization Model In The Todo Scenario
&lt;/h2&gt;

&lt;p&gt;The Todo scenario is “5 users × 5 actions”.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;admin (Rick)&lt;/th&gt;
&lt;th&gt;editor (Morty, Summer)&lt;/th&gt;
&lt;th&gt;viewer (Beth, Jerry)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;can_read_user&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;allow&lt;/td&gt;
&lt;td&gt;allow&lt;/td&gt;
&lt;td&gt;allow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;can_read_todos&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;allow&lt;/td&gt;
&lt;td&gt;allow&lt;/td&gt;
&lt;td&gt;allow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;can_create_todo&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;allow&lt;/td&gt;
&lt;td&gt;allow&lt;/td&gt;
&lt;td&gt;deny&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;can_update_todo&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;allow (any)&lt;/td&gt;
&lt;td&gt;allow (own only)&lt;/td&gt;
&lt;td&gt;deny&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;can_delete_todo&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;allow (any)&lt;/td&gt;
&lt;td&gt;allow (own only)&lt;/td&gt;
&lt;td&gt;deny&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In Rego, &lt;code&gt;input.subject.id&lt;/code&gt; is mapped through &lt;code&gt;data.users&lt;/code&gt;, roles are evaluated, and ownership-scoped actions check &lt;code&gt;ownerID&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rego"&gt;&lt;code&gt;&lt;span class="c1"&gt;# can_create_todo: allow for admin or editor roles&lt;/span&gt;
&lt;span class="n"&gt;allow&lt;/span&gt; &lt;span class="n"&gt;if&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"can_create_todo"&lt;/span&gt;
  &lt;span class="ow"&gt;some&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;roles&lt;/span&gt;
  &lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"admin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"editor"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# can_update_todo: editor can update only their own todos&lt;/span&gt;
&lt;span class="n"&gt;allow&lt;/span&gt; &lt;span class="n"&gt;if&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"can_update_todo"&lt;/span&gt;
  &lt;span class="s2"&gt;"editor"&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;roles&lt;/span&gt;
  &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ownerID&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  How To Run
&lt;/h2&gt;

&lt;p&gt;Shortest path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to run the official harness directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make up

git clone https://github.com/openid/authzen.git
&lt;span class="nb"&gt;cd &lt;/span&gt;authzen/interop/authzen-todo-backend
yarn &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; yarn build
yarn &lt;span class="nb"&gt;test &lt;/span&gt;http://localhost:8181 authorization-api-1_0-01 console
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;make test&lt;/code&gt; flow:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fev4gl7ppeqp67h0trl2e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fev4gl7ppeqp67h0trl2e.png" alt="make test" width="800" height="353"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Note (Issue / PR)
&lt;/h2&gt;

&lt;p&gt;There are still many parts I need to improve. If you spot anything, feedback would really help.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bug reports: &lt;a href="https://github.com/kanywst/opa-authzen-interop/issues/new/choose" rel="noopener noreferrer"&gt;Issue&lt;/a&gt; with reproducible steps&lt;/li&gt;
&lt;li&gt;Spec mismatches: &lt;a href="https://github.com/kanywst/opa-authzen-interop/issues/new/choose" rel="noopener noreferrer"&gt;Issue&lt;/a&gt; with the relevant scenario URL&lt;/li&gt;
&lt;li&gt;Improvements / implementation changes: &lt;a href="https://github.com/kanywst/opa-authzen-interop/pulls" rel="noopener noreferrer"&gt;Pull Request&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;evaluations&lt;/code&gt; support is still in progress, so suggestions and fixes are both very welcome.&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;opa-authzen-interop&lt;/code&gt; (this repo): &lt;a href="https://github.com/kanywst/opa-authzen-interop" rel="noopener noreferrer"&gt;https://github.com/kanywst/opa-authzen-interop&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;opa-authzen-plugin&lt;/code&gt;: &lt;a href="https://github.com/kanywst/opa-authzen-plugin" rel="noopener noreferrer"&gt;https://github.com/kanywst/opa-authzen-plugin&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AuthZEN Interop Introduction: &lt;a href="https://authzen-interop.net/docs/intro/" rel="noopener noreferrer"&gt;https://authzen-interop.net/docs/intro/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Todo 1.1 payload spec: &lt;a href="https://authzen-interop.net/docs/scenarios/todo-1.1/" rel="noopener noreferrer"&gt;https://authzen-interop.net/docs/scenarios/todo-1.1/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;OPA interop results (Todo 1.1): &lt;a href="https://authzen-interop.net/docs/scenarios/todo-1.1/results/opa" rel="noopener noreferrer"&gt;https://authzen-interop.net/docs/scenarios/todo-1.1/results/opa&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Topaz interop proxy code: &lt;a href="https://github.com/aserto-dev/authzen-topaz-proxy" rel="noopener noreferrer"&gt;https://github.com/aserto-dev/authzen-topaz-proxy&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>showdev</category>
      <category>opa</category>
      <category>authorization</category>
      <category>rego</category>
    </item>
    <item>
      <title>I Built an OPA Plugin That Turns It Into an AuthZEN-Compatible PDP</title>
      <dc:creator>kt</dc:creator>
      <pubDate>Wed, 01 Apr 2026 17:10:01 +0000</pubDate>
      <link>https://dev.to/kanywst/i-built-an-opa-plugin-that-turns-it-into-an-authzen-compatible-pdp-i81</link>
      <guid>https://dev.to/kanywst/i-built-an-opa-plugin-that-turns-it-into-an-authzen-compatible-pdp-i81</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;In my &lt;a href="https://dev.to/kanywst/authzen-authorization-api-10-deep-dive-the-standard-api-that-separates-authorization-decisions-1m2a"&gt;previous article&lt;/a&gt;, I did a deep dive into the AuthZEN Authorization API 1.0 spec. It standardizes communication between PEPs and PDPs. You send a JSON request asking "can this subject do this action on this resource?" and get back &lt;code&gt;{"decision": true/false}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So the spec makes sense. But how do you actually use OPA as an AuthZEN-compatible PDP?&lt;/p&gt;

&lt;p&gt;OPA already has a REST API (&lt;code&gt;POST /v1/data/...&lt;/code&gt;), but it doesn't match the AuthZEN API.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Different path: AuthZEN uses &lt;code&gt;POST /access/v1/evaluation&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Different request structure: OPA requires wrapping in &lt;code&gt;{"input": {...}}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Different response structure: OPA returns &lt;code&gt;{"result": ...}&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's an &lt;a href="https://github.com/open-policy-agent/contrib/tree/main/authzen" rel="noopener noreferrer"&gt;authzen-proxy&lt;/a&gt; in contrib, a Node.js proxy, but it requires a separate process.&lt;/p&gt;

&lt;p&gt;So I built a plugin that runs the AuthZEN API directly inside the OPA process using OPA's plugin mechanism.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo&lt;/strong&gt;: &lt;a href="https://github.com/kanywst/opa-authzen-plugin" rel="noopener noreferrer"&gt;github.com/kanywst/opa-authzen-plugin&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The OPA Community Discussion
&lt;/h2&gt;

&lt;p&gt;Before getting into the code, some context on why this ended up as a plugin.&lt;/p&gt;

&lt;p&gt;I opened an issue (&lt;a href="https://github.com/open-policy-agent/opa/issues/8449" rel="noopener noreferrer"&gt;#8449&lt;/a&gt;) on the OPA repository and brought it up in the &lt;code&gt;#contributors&lt;/code&gt; Slack channel.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Initial Proposal: Route Aliases
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;route_aliases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;/access/v1/evaluation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/v1/data/authzen/allow&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The idea was to add configurable path mapping to the OPA server. I also put up a PR (&lt;a href="https://github.com/open-policy-agent/opa/pull/8451" rel="noopener noreferrer"&gt;#8451&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;But path mapping alone can't handle request/response transformation, so it wouldn't fully satisfy the AuthZEN spec.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the Community Said
&lt;/h3&gt;

&lt;p&gt;The conclusion was:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Not in OPA core.&lt;/strong&gt; OPA is a general-purpose policy engine used beyond just authorization. Adding use-case-specific features increases the surface area.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plugin / distribution is the right approach.&lt;/strong&gt; Same pattern as opa-envoy-plugin. Single binary, OPA + AuthZEN server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evaluation API is enough.&lt;/strong&gt; The only REQUIRED endpoint in the well-known metadata is &lt;code&gt;access_evaluation_endpoint&lt;/code&gt;. Evaluations (batch) and Search APIs are all OPTIONAL.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Nobody was against AuthZEN support itself. The stance was: start as a plugin, and if demand grows, it can move closer to core later.&lt;/p&gt;

&lt;p&gt;There aren't many production AuthZEN users yet, so adding it to core didn't have strong justification at this point.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;Just like opa-envoy-plugin bundles OPA + Envoy External Authorization (gRPC) into a single binary, opa-authzen-plugin bundles OPA + an AuthZEN HTTP server into one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff3fmj57oh0m64cnc4bx3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff3fmj57oh0m64cnc4bx3.png" alt="Architecture" width="552" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Key points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The AuthZEN request body (&lt;code&gt;subject&lt;/code&gt;, &lt;code&gt;resource&lt;/code&gt;, &lt;code&gt;action&lt;/code&gt;, &lt;code&gt;context&lt;/code&gt;) becomes OPA's &lt;code&gt;input&lt;/code&gt; as-is. No wrapping needed.&lt;/li&gt;
&lt;li&gt;The plugin evaluates &lt;code&gt;data.&amp;lt;path&amp;gt;.&amp;lt;decision&amp;gt;&lt;/code&gt; (default: &lt;code&gt;data.authzen.allow&lt;/code&gt;) and returns the bool result as &lt;code&gt;{"decision": ...}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;OPA's REST API (&lt;code&gt;:8181&lt;/code&gt;) still works. Bundles, decision logs, and all other OPA features are available.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1915r04x7qufkogfsr6o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1915r04x7qufkogfsr6o.png" alt="opa authzen plugin" width="321" height="618"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  OPA's Plugin Mechanism
&lt;/h2&gt;

&lt;p&gt;OPA lets you register plugins at runtime. Call &lt;code&gt;runtime.RegisterPlugin&lt;/code&gt; with a Factory, and OPA will instantiate and start your plugin based on the config file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjib8at8uorbtiqaqihyq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjib8at8uorbtiqaqihyq.png" alt="OPA Plugin" width="369" height="763"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The entrypoint is just this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RegisterPlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plugin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PluginName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plugin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Factory&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RootCommand&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;opa-envoy-plugin uses the exact same pattern. &lt;code&gt;runtime.RegisterPlugin&lt;/code&gt; to register a gRPC server plugin.&lt;/p&gt;




&lt;h2&gt;
  
  
  How AuthZEN Requests Reach OPA
&lt;/h2&gt;

&lt;p&gt;An AuthZEN Access Evaluation API request looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subject"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"alice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"admin"&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"document"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"doc-123"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"delete"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-04-01T12:00:00Z"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The plugin passes this body directly as OPA's &lt;code&gt;input&lt;/code&gt;. In Rego, you reference it like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rego"&gt;&lt;code&gt;&lt;span class="ow"&gt;package&lt;/span&gt; &lt;span class="n"&gt;authzen&lt;/span&gt;

&lt;span class="ow"&gt;default&lt;/span&gt; &lt;span class="n"&gt;allow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

&lt;span class="n"&gt;allow&lt;/span&gt; &lt;span class="n"&gt;if&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"admin"&lt;/span&gt;

&lt;span class="n"&gt;allow&lt;/span&gt; &lt;span class="n"&gt;if&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"read"&lt;/span&gt;
    &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With OPA's Data API, you'd need to wrap it as &lt;code&gt;{"input": {...}}&lt;/code&gt; and &lt;code&gt;POST /v1/data/authzen/allow&lt;/code&gt;. The plugin handles that conversion internally.&lt;/p&gt;

&lt;p&gt;Same for the response. OPA returns &lt;code&gt;{"result": true}&lt;/code&gt;, and the plugin converts it to &lt;code&gt;{"decision": true}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvxkfdxnh9c06xt73osr7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvxkfdxnh9c06xt73osr7.png" alt="Request Reach OPA" width="800" height="467"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Policy Dispatch
&lt;/h2&gt;

&lt;p&gt;AuthZEN uses a single endpoint (&lt;code&gt;/access/v1/evaluation&lt;/code&gt;) for all authorization decisions. If you want to route to different policies per resource type or action, you do that in Rego by checking &lt;code&gt;input.resource.type&lt;/code&gt; or &lt;code&gt;input.action.name&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rego"&gt;&lt;code&gt;&lt;span class="ow"&gt;package&lt;/span&gt; &lt;span class="n"&gt;authzen&lt;/span&gt;

&lt;span class="ow"&gt;default&lt;/span&gt; &lt;span class="n"&gt;allow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

&lt;span class="c1"&gt;# anyone can read todolists&lt;/span&gt;
&lt;span class="n"&gt;allow&lt;/span&gt; &lt;span class="n"&gt;if&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"todolist"&lt;/span&gt;
    &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"read"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# only editors can create todolists&lt;/span&gt;
&lt;span class="n"&gt;allow&lt;/span&gt; &lt;span class="n"&gt;if&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"todolist"&lt;/span&gt;
    &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"create"&lt;/span&gt;
    &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"editor"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# only admins can manage accounts&lt;/span&gt;
&lt;span class="n"&gt;allow&lt;/span&gt; &lt;span class="n"&gt;if&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"account"&lt;/span&gt;
    &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"manage"&lt;/span&gt;
    &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"admin"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As mentioned in the community discussion, PDP-specific hints (like which policy to evaluate) can also be passed via the &lt;code&gt;context&lt;/code&gt; object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subject"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"alice"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"read"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"todolist"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"policy_hint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"todolist"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Well-Known Metadata
&lt;/h2&gt;

&lt;p&gt;The PDP metadata endpoint defined in Section 9 of the spec. PEPs can use it to dynamically discover which endpoints are available.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://localhost:9292/.well-known/authzen-configuration | jq &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"policy_decision_point"&lt;/span&gt;: &lt;span class="s2"&gt;"http://localhost:9292"&lt;/span&gt;,
  &lt;span class="s2"&gt;"access_evaluation_endpoint"&lt;/span&gt;: &lt;span class="s2"&gt;"http://localhost:9292/access/v1/evaluation"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OPTIONAL endpoints (&lt;code&gt;access_evaluations_endpoint&lt;/code&gt;, &lt;code&gt;search_*&lt;/code&gt;) aren't returned since they're not implemented. Per the spec, parameters with no value MUST be omitted.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It Out
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Build and Run
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/kanywst/opa-authzen-plugin.git
&lt;span class="nb"&gt;cd &lt;/span&gt;opa-authzen-plugin
make build
./opa-authzen-plugin run &lt;span class="nt"&gt;--server&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--config-file&lt;/span&gt; example/config.yaml &lt;span class="se"&gt;\&lt;/span&gt;
  example/policy.rego
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also works with Docker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make docker-build
make docker-run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Send Requests
&lt;/h3&gt;

&lt;p&gt;Admin user deleting a document (allowed):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:9292/access/v1/evaluation &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "subject": {"type": "user", "id": "alice", "properties": {"role": "admin"}},
    "resource": {"type": "document", "id": "doc-123"},
    "action": {"name": "delete"}
  }'&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"decision"&lt;/span&gt;:true&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Regular user deleting a document (denied):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:9292/access/v1/evaluation &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "subject": {"type": "user", "id": "bob", "properties": {"role": "viewer"}},
    "resource": {"type": "document", "id": "doc-123"},
    "action": {"name": "delete"}
  }'&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"decision"&lt;/span&gt;:false&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;X-Request-ID&lt;/code&gt; header is echoed back in the response (Section 10.1.3):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-si&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:9292/access/v1/evaluation &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-Request-ID: req-abc-123"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "subject": {"type": "user", "id": "alice", "properties": {"role": "admin"}},
    "resource": {"type": "document", "id": "1"},
    "action": {"name": "read"}
  }'&lt;/span&gt;
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: req-abc-123

&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"decision"&lt;/span&gt;:true&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;This is a PoC with just the Evaluation API.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Evaluations API (batch)&lt;/strong&gt;: OPTIONAL, but there's demand. Topaz has 1000+ edge users asking for it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search APIs&lt;/strong&gt;: Hard to implement generically on top of OPA (ABAC). Partial evaluation might work, but nobody has explored that yet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;gRPC&lt;/strong&gt;: There's interest in having gRPC alongside REST, but REST comes first.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Depending on how the OPA community responds, this could move to contrib or be integrated with opa-envoy-plugin.&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repo&lt;/strong&gt;: &lt;a href="https://github.com/kanywst/opa-authzen-plugin" rel="noopener noreferrer"&gt;github.com/kanywst/opa-authzen-plugin&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OPA Issue&lt;/strong&gt;: &lt;a href="https://github.com/open-policy-agent/opa/issues/8449" rel="noopener noreferrer"&gt;open-policy-agent/opa#8449&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AuthZEN Spec&lt;/strong&gt;: &lt;a href="https://openid.net/specs/authorization-api-1_0.html" rel="noopener noreferrer"&gt;openid.net/specs/authorization-api-1_0.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Previous Article (AuthZEN Deep Dive)&lt;/strong&gt;: &lt;a href="https://dev.to/kanywst/authzen-authorization-api-10-deep-dive-the-standard-api-that-separates-authorization-decisions-1m2a"&gt;dev.to/kanywst/authzen-authorization-api-10-deep-dive&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>showdev</category>
      <category>opa</category>
      <category>authorization</category>
      <category>go</category>
    </item>
    <item>
      <title>Google Zanzibar Deep Dive: Handling 2 Trillion ACLs in Under 10ms</title>
      <dc:creator>kt</dc:creator>
      <pubDate>Wed, 01 Apr 2026 13:58:38 +0000</pubDate>
      <link>https://dev.to/kanywst/google-zanzibar-deep-dive-handling-2-trillion-acls-in-under-10ms-f06</link>
      <guid>https://dev.to/kanywst/google-zanzibar-deep-dive-handling-2-trillion-acls-in-under-10ms-f06</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;Spend any time in the authorization space and you'll notice that SpiceDB and OpenFGA both claim to be "inspired by Google Zanzibar." I got curious and pulled up the 2019 USENIX ATC paper — "Zanzibar: Google's Consistent, Global Authorization System" — to see what the fuss was about.&lt;/p&gt;

&lt;p&gt;My first reaction after reading it: the data model is almost offensively simple. Over two trillion ACLs, ten million authorization checks per second, p95 latency under 10ms — and the whole thing runs on a single string: &lt;code&gt;object#relation@user&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Why does something this minimal hold up at planetary scale without falling apart? This post is my attempt to answer that.&lt;/p&gt;




&lt;h2&gt;
  
  
  Scope
&lt;/h2&gt;

&lt;p&gt;First, let's place Zanzibar in the authorization stack.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Examples&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Authentication&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Verifies who the user is&lt;/td&gt;
&lt;td&gt;OpenID Connect, SAML&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Token issuance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hands out access tokens&lt;/td&gt;
&lt;td&gt;OAuth 2.0 (RFC 6749)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Authorization&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;"Can this user access this resource?"&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Zanzibar (this post)&lt;/strong&gt;, XACML, OPA&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Zanzibar is purely about authorization decisions. It's a different layer from OAuth. If OAuth is "handing someone a key," Zanzibar is "deciding which rooms that key opens."&lt;/p&gt;

&lt;p&gt;Prerequisites:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Basic access control concepts (who, what, can do what)&lt;/li&gt;
&lt;li&gt;A rough idea of how RBAC works&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  1. The Problem Zanzibar Solves
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1.1 Why Google Needed a Unified Authorization System
&lt;/h3&gt;

&lt;p&gt;When dozens of services each implement their own access control, things get messy fast:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Problem&lt;/th&gt;
&lt;th&gt;Why it hurts&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Inconsistency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"Sharing" behaves differently in Drive vs. Photos — confusing for users&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cross-service coordination&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A Google Photos image embedded in a Docs file: whose ACL applies?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Access-aware search&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Building a search index that respects permissions across services is a nightmare if every team solves it separately&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Engineering waste&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Every team reinventing consistency and scalability from scratch&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Zanzibar solves all of this by acting as a &lt;strong&gt;single unified ACL store + evaluation engine&lt;/strong&gt; for all Google services.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.2 Design Goals
&lt;/h3&gt;

&lt;p&gt;The paper lists five goals:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Goal&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Concrete target&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Correctness&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Respect user intent&lt;/td&gt;
&lt;td&gt;Prevent the "New Enemy" problem (Section 5)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Flexibility&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Support diverse access control policies&lt;/td&gt;
&lt;td&gt;RBAC, ReBAC, hierarchical ACLs, etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Low latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Authorization shouldn't slow down user interactions&lt;/td&gt;
&lt;td&gt;p95 &amp;lt; 10ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;High availability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;If authorization is down, everything is down&lt;/td&gt;
&lt;td&gt;99.999%+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scale&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;One system for all of Google&lt;/td&gt;
&lt;td&gt;2T+ ACLs, 10M+ requests/sec&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;"Correctness" is where the New Enemy problem comes in — it's not just about returning the right answer, it's about respecting the causal order of changes.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Relation Tuples — The Data Model
&lt;/h2&gt;

&lt;p&gt;Zanzibar's core is a data model so simple it almost looks wrong: the &lt;strong&gt;Relation Tuple&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.1 Syntax
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;⟨object⟩#⟨relation⟩@⟨user⟩
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"&lt;strong&gt;This user&lt;/strong&gt; has &lt;strong&gt;this relation&lt;/strong&gt; to &lt;strong&gt;this object&lt;/strong&gt;." That's it.&lt;/p&gt;

&lt;p&gt;Some examples:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tuple&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;doc:readme#owner@alice&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;alice is an &lt;strong&gt;owner&lt;/strong&gt; of doc:readme&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;group:eng#member@bob&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bob is a &lt;strong&gt;member&lt;/strong&gt; of group:eng&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;doc:readme#viewer@group:eng#member&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Every &lt;strong&gt;member&lt;/strong&gt; of group:eng is a &lt;strong&gt;viewer&lt;/strong&gt; of doc:readme&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;doc:readme#parent@folder:A#...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;doc:readme lives inside folder:A&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Look at the third row. The &lt;code&gt;@&lt;/code&gt; side isn't a user ID — it's &lt;code&gt;group:eng#member&lt;/code&gt;. This is called a &lt;strong&gt;userset&lt;/strong&gt;: "all users who have the member relation to group:eng." It lets ACLs point to groups, and it lets groups contain other groups.&lt;/p&gt;

&lt;p&gt;The neat consequence: &lt;strong&gt;ACLs and groups are the same data structure.&lt;/strong&gt; A group is just an object that uses "member" semantics.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.2 What Relation Tuples Can Express
&lt;/h3&gt;

&lt;p&gt;All four of these common patterns fit the same one-line format:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Direct access&lt;/strong&gt; — &lt;code&gt;doc:readme#viewer@alice&lt;/code&gt; (alice is a viewer)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Group membership&lt;/strong&gt; — &lt;code&gt;group:eng#member@alice&lt;/code&gt; (alice is in the eng group)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Indirect access&lt;/strong&gt; — &lt;code&gt;doc:readme#viewer@group:eng#member&lt;/code&gt; (everyone in eng is a viewer)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Object hierarchy&lt;/strong&gt; — &lt;code&gt;doc:readme#parent@folder:A#...&lt;/code&gt; (readme is inside folder:A)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No special syntax. One format for everything.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.3 Why Tuples?
&lt;/h3&gt;

&lt;p&gt;Traditional ACLs are usually stored per-object. Zanzibar switched to a tuple-based model for three reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Efficient reads&lt;/strong&gt;: "What groups is user X a member of?" is just a reverse lookup on tuples&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incremental updates&lt;/strong&gt;: Adding or removing access means writing or deleting one tuple, not rewriting an entire ACL&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unified model&lt;/strong&gt;: ACLs and groups don't need separate data structures&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. Namespace Configuration — Defining Policies
&lt;/h2&gt;

&lt;p&gt;Tuples alone can't express rules like "editors are automatically viewers too." That's what &lt;strong&gt;Namespace Configuration&lt;/strong&gt; is for.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.1 Userset Rewrites
&lt;/h3&gt;

&lt;p&gt;Here's the namespace config from Figure 1 of the paper (written in a Protocol Buffers-style config language):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight protobuf"&gt;&lt;code&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"doc"&lt;/span&gt;

&lt;span class="n"&gt;relation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"owner"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;relation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"editor"&lt;/span&gt;
  &lt;span class="n"&gt;userset_rewrite&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;union&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;child&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_this&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;                              &lt;span class="c1"&gt;// directly added editors&lt;/span&gt;
      &lt;span class="n"&gt;child&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;computed_userset&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"owner"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// + all owners&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;relation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"viewer"&lt;/span&gt;
  &lt;span class="n"&gt;userset_rewrite&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;union&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;child&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_this&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;                               &lt;span class="c1"&gt;// directly added viewers&lt;/span&gt;
      &lt;span class="n"&gt;child&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;computed_userset&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"editor"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// + all editors&lt;/span&gt;
      &lt;span class="n"&gt;child&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;tuple_to_userset&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;                        &lt;span class="c1"&gt;// + viewers of the parent folder&lt;/span&gt;
        &lt;span class="n"&gt;tupleset&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"parent"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;computed_userset&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;TUPLE_USERSET_OBJECT&lt;/span&gt;
          &lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"viewer"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The structure is concentric: &lt;code&gt;owner ⊂ editor ⊂ viewer&lt;/code&gt;. And the "inherit viewer permissions from the parent folder" rule is defined in one place, globally, with no per-object tuples needed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fahbfchapez4no1nb1q8t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fahbfchapez4no1nb1q8t.png" alt="Name Configuration" width="800" height="152"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3.2 The Three Leaf Node Types
&lt;/h3&gt;

&lt;p&gt;Userset rewrite rules are trees built from three kinds of leaf nodes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Leaf node&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;_this&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Users directly assigned this relation on this object&lt;/td&gt;
&lt;td&gt;Someone explicitly added as &lt;code&gt;doc:readme#editor&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;computed_userset&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Another relation on the same object&lt;/td&gt;
&lt;td&gt;"owners are automatically editors"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;tuple_to_userset&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Follow a relation to another object and inherit&lt;/td&gt;
&lt;td&gt;"look up the parent folder and take its viewers"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Leaf nodes can be combined with &lt;strong&gt;union&lt;/strong&gt;, &lt;strong&gt;intersection&lt;/strong&gt;, and &lt;strong&gt;exclusion&lt;/strong&gt;. This gives you RBAC, ReBAC, and ABAC-style policies — all from the same config language.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. API
&lt;/h2&gt;

&lt;p&gt;Zanzibar exposes five API methods:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;API&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Check&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Authorization check&lt;/td&gt;
&lt;td&gt;"Does user U have relation R to object O?"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Read&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tuple lookup&lt;/td&gt;
&lt;td&gt;Returns raw tuples as stored (does not expand rewrites)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Write&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tuple modification&lt;/td&gt;
&lt;td&gt;Add or remove tuples&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Watch&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Change streaming&lt;/td&gt;
&lt;td&gt;Stream tuple changes in real time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Expand&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Effective userset&lt;/td&gt;
&lt;td&gt;Returns the full tree of users who have a relation, following rewrites&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  4.2 How Check Works
&lt;/h3&gt;

&lt;p&gt;Check is the core API. Under the hood:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff4annl1i98pstwkm2jbj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff4annl1i98pstwkm2jbj.png" alt="Check" width="740" height="835"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Spanner&lt;/strong&gt; is the underlying storage — covered in Section 6. Check requests can also carry a &lt;strong&gt;Zookie&lt;/strong&gt; to control consistency — explained in Section 5.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The key thing happening here is &lt;strong&gt;recursive pointer chasing&lt;/strong&gt;. To check &lt;code&gt;viewer&lt;/code&gt;, Zanzibar looks at &lt;code&gt;editor&lt;/code&gt;. To check &lt;code&gt;editor&lt;/code&gt;, it looks at &lt;code&gt;owner&lt;/code&gt;. Deeply nested groups make this recursion expensive. The Leopard index (Section 7) exists specifically to solve that.&lt;/p&gt;

&lt;p&gt;One gotcha with Read: it &lt;strong&gt;does not expand userset rewrites&lt;/strong&gt;. Reading the &lt;code&gt;viewer&lt;/code&gt; relation won't return owners or editors, even if the namespace config says they're viewers. For that, use Expand.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. The "New Enemy" Problem
&lt;/h2&gt;

&lt;p&gt;Zanzibar's most distinctive feature is &lt;strong&gt;external consistency&lt;/strong&gt;. The guarantee: if transaction A commits before transaction B starts, B will always see A's result. The causal order of real-world events is reflected in the database.&lt;/p&gt;

&lt;p&gt;This matters because of the "New Enemy" problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1 What Is the New Enemy Problem?
&lt;/h3&gt;

&lt;p&gt;The paper walks through two examples.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example A: Ignoring ACL update order&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1jq3zbyqoa2rr8v5t51r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1jq3zbyqoa2rr8v5t51r.png" alt="New Enemy" width="800" height="614"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example B: Old ACL applied to new content&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Alice removes Bob from a document's ACL&lt;/li&gt;
&lt;li&gt;Alice asks Charlie to add new content to the document&lt;/li&gt;
&lt;li&gt;Bob shouldn't be able to see the new content. But if the ACL check evaluates against the &lt;strong&gt;pre-removal ACL&lt;/strong&gt;, Bob gets through&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is the New Enemy problem: ignoring the causal ordering between ACL changes and content changes breaks access control in ways that are hard to reason about.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.2 Zookie — Encoding Causality in a Token
&lt;/h3&gt;

&lt;p&gt;Zanzibar fixes this with &lt;strong&gt;Zookies&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd2czocypeh711mqow0xj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd2czocypeh711mqow0xj.png" alt="Zookie" width="733" height="710"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How it works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When content is modified, the client sends a &lt;strong&gt;content-change check&lt;/strong&gt; to Zanzibar&lt;/li&gt;
&lt;li&gt;Zanzibar encodes the &lt;strong&gt;current global timestamp&lt;/strong&gt; into a Zookie and returns it&lt;/li&gt;
&lt;li&gt;The client stores the Zookie alongside the new content&lt;/li&gt;
&lt;li&gt;Subsequent ACL checks include the Zookie — telling Zanzibar: "evaluate using a snapshot &lt;strong&gt;at least as fresh as this timestamp&lt;/strong&gt;"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Zookies are &lt;strong&gt;opaque byte strings&lt;/strong&gt; by design. Clients can't set arbitrary timestamps; they can only pass back what Zanzibar gave them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why not always evaluate at the latest snapshot?&lt;/strong&gt; That would require global synchronization across all replicas — wrecking latency and availability. The Zookie's "at-least-as-fresh" semantics let Zanzibar pick any snapshot newer than the encoded timestamp. In practice, most checks use already-replicated local data and never wait on cross-region round trips.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Architecture
&lt;/h2&gt;

&lt;p&gt;To understand the architecture, you first need to know &lt;strong&gt;Spanner&lt;/strong&gt;. It's Google's globally distributed database: data is replicated across data centers worldwide, and it provides &lt;strong&gt;external consistency&lt;/strong&gt; (the same guarantee Zanzibar builds on). The mechanism is &lt;strong&gt;TrueTime&lt;/strong&gt; — a time API backed by GPS and atomic clocks that produces timestamps with bounded error, allowing causally ordered commits across the globe.&lt;/p&gt;

&lt;p&gt;Zanzibar is built on top of Spanner's consistency guarantees.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.1 System Overview
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8y1cbzjocah3891qfcy3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8y1cbzjocah3891qfcy3.png" alt="Architecture" width="800" height="638"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;aclserver&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Core server. Handles Check, Read, Expand, Write. Fans out work to other aclservers in the cluster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;watchserver&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Handles Watch requests. Tails the changelog and streams changes in near real time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Spanner&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;ACL storage. Provides external consistency and snapshot reads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Leopard&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Specialized index for deeply nested group membership evaluation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Periodic batch pipeline&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Offline jobs: snapshot dumps, tuple garbage collection, etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  6.2 Storage
&lt;/h3&gt;

&lt;p&gt;Each namespace's relation tuples live in a separate Spanner database. The primary key is &lt;code&gt;(shard ID, object ID, relation, user, commit timestamp)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The critical detail: &lt;strong&gt;commit timestamp is part of the primary key.&lt;/strong&gt; Multiple versions of the same tuple are stored as separate rows. This lets Zanzibar do snapshot reads at any timestamp within the GC window — which is the foundation for Zookie semantics.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.3 Replication
&lt;/h3&gt;

&lt;p&gt;Zanzibar replicates all ACL data to &lt;strong&gt;30+ locations worldwide&lt;/strong&gt;. Geographic partitioning doesn't work here — you can't predict which region will check which object's ACL.&lt;/p&gt;

&lt;p&gt;Write consensus uses &lt;strong&gt;Paxos&lt;/strong&gt; (a distributed consensus protocol where multiple nodes agree on a single value). The 5 voting replicas sit in 3 metropolitan areas in the eastern and central US, within 25ms of each other, keeping Paxos commit latency predictable.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. How They Hit 10ms
&lt;/h2&gt;

&lt;p&gt;Two trillion ACLs. Ten million requests per second. p95 under 10ms. Here's how.&lt;/p&gt;

&lt;h3&gt;
  
  
  7.1 The Leopard Index
&lt;/h3&gt;

&lt;p&gt;Recursive pointer chasing breaks down when groups are deeply nested or have huge numbers of sub-groups. Leopard preprocesses this.&lt;/p&gt;

&lt;p&gt;Leopard maintains two index types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GROUP_2_GROUP(G)&lt;/strong&gt;: All direct and indirect descendant groups of group G (pre-expanded)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MEMBER_2_GROUP(U)&lt;/strong&gt;: All groups user U is a direct member of&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;"Is user U a member of group G?" becomes a single set intersection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MEMBER_2_GROUP(U) ∩ GROUP_2_GROUP(G) ≠ ∅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visualized:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F55259eh5gk1ki89l7g71.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F55259eh5gk1ki89l7g71.png" alt="Leopard Index" width="800" height="244"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No deep recursion. The index is stored as skip lists, and set intersection runs in O(min(|A|,|B|)).&lt;/p&gt;

&lt;p&gt;Leopard uses a two-tier design: an &lt;strong&gt;offline batch layer&lt;/strong&gt; that periodically rebuilds the full index from a Spanner snapshot, and an &lt;strong&gt;online incremental layer&lt;/strong&gt; that watches for changes via the Watch API and keeps the index current. The incremental layer is what makes Leopard queries consistent at any given snapshot timestamp.&lt;/p&gt;

&lt;h3&gt;
  
  
  7.2 Hot Spot Handling
&lt;/h3&gt;

&lt;p&gt;When Google Drive shows search results, it fires dozens to hundreds of ACL checks simultaneously. If those documents all share a common group (say, &lt;code&gt;group:all-employees&lt;/code&gt;), you get a hot spot on the data backing that group.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Technique&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Distributed cache&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;aclservers form a consistent-hash cache cluster. Both the caller and callee of delegated RPCs cache results&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Lock table&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Deduplicates concurrent requests for the same cache key. One request runs; the rest wait for the result&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Timestamp quantization&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Rounds evaluation timestamps up to 1-second or 10-second boundaries, dramatically increasing cache hit rate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hot object prefetch&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;When a specific object sees a burst of checks, prefetch all its tuples into cache&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Request hedging&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;If a Spanner or Leopard request is slow, send the same request to a second server and use whichever responds first&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Timestamp quantization deserves special mention. Spanner timestamps have microsecond resolution, but Zanzibar rounds them up to 1s or 10s at evaluation time. This means the vast majority of requests land on the same handful of timestamps and share cache results. Rounding up doesn't break consistency — a Spanner snapshot read at timestamp T includes all writes up to T, and if T is in the future, Spanner just waits.&lt;/p&gt;

&lt;h3&gt;
  
  
  7.3 Performance Isolation
&lt;/h3&gt;

&lt;p&gt;In a shared service, one bad client can drag everyone down. Zanzibar's isolation mechanisms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Per-client CPU budget (throttled if exceeded and system is under load)&lt;/li&gt;
&lt;li&gt;Per-server limit on outstanding RPCs&lt;/li&gt;
&lt;li&gt;Per &lt;code&gt;(object, client)&lt;/code&gt; limit on concurrent Spanner reads&lt;/li&gt;
&lt;li&gt;Per-client lock table keys (so one client's throttling doesn't block others)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  8. Production Numbers (December 2018)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Namespaces&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1,500+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Relation tuples&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2 trillion+ (~100 TB)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Replication&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;30+ locations worldwide&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Servers&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;10,000+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Check QPS (peak)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~4.2M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Read QPS (peak)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~8.2M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Check Safe p50&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Check Safe p95&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~10ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Check Safe p99&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~15ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Check Recent p95&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~60ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Availability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;99.999% (sustained over 3 years)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two request categories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Safe&lt;/strong&gt;: Zookie is older than 10 seconds → serves from local replica (fast)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recent&lt;/strong&gt;: Zookie is within the last 10 seconds → may require cross-region round trips (~60ms)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Safe requests outnumber Recent by roughly 100:1. That gap is entirely by design — Zookies make the common case local.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Lessons from the Paper
&lt;/h2&gt;

&lt;p&gt;Section 4.5 of the paper (Lessons Learned) is unusually candid for a systems paper. Five years of production, written honestly.&lt;/p&gt;

&lt;h3&gt;
  
  
  9.1 Flexibility Comes Later
&lt;/h3&gt;

&lt;p&gt;The initial userset rewrite only had &lt;code&gt;_this&lt;/code&gt;. &lt;code&gt;computed_userset&lt;/code&gt; and &lt;code&gt;tuple_to_userset&lt;/code&gt; were added later, in response to actual client requirements from Drive and Photos. Google didn't design the perfect abstraction upfront — they extended it as real use cases showed up.&lt;/p&gt;

&lt;h3&gt;
  
  
  9.2 Most Freshness Requirements Are Loose
&lt;/h3&gt;

&lt;p&gt;The majority of clients are fine with slightly stale ACL evaluations. Zanzibar's Zookie protocol was designed around this: default to loose freshness, tighten only when necessary. That's the key to hitting p95 10ms — most requests never need cross-region coordination.&lt;/p&gt;

&lt;h3&gt;
  
  
  9.3 You Can't Skip Hot Spot Mitigation
&lt;/h3&gt;

&lt;p&gt;The Drive search case — hundreds of simultaneous ACL checks that all fan into the same popular group — created hot spots that general caching couldn't fully absorb. They added targeted optimizations: hotness detection, full-tuple prefetch, delayed cancellation of secondary checks when waiters are queued. Hot spot handling wasn't nice-to-have; it was essential.&lt;/p&gt;

&lt;h3&gt;
  
  
  9.4 ReBAC as a Paradigm
&lt;/h3&gt;

&lt;p&gt;Zanzibar has effectively become the reference implementation for &lt;strong&gt;Relationship-Based Access Control (ReBAC)&lt;/strong&gt;. The comparison:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Decision basis&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RBAC&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;User's role&lt;/td&gt;
&lt;td&gt;"admin role → can delete"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ABAC&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;User and resource attributes&lt;/td&gt;
&lt;td&gt;"same department + business hours → access granted"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ReBAC&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Relationship between user and resource&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"owner of the doc, or viewer of its parent folder"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;ReBAC's advantage is that real-world organizational structure — folder hierarchies, team memberships, org charts — maps directly to the authorization model. Google Drive's "share this folder with your team" is a textbook ReBAC problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. The OSS Ecosystem
&lt;/h2&gt;

&lt;p&gt;The paper landed in 2019 and immediately spawned a cluster of projects:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Project&lt;/th&gt;
&lt;th&gt;By&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SpiceDB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AuthZed&lt;/td&gt;
&lt;td&gt;The most faithful OSS implementation. Founded by former Zanzibar team members. Uses its own schema language (Zed)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OpenFGA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Okta / Auth0&lt;/td&gt;
&lt;td&gt;Zanzibar-inspired ReBAC engine. CNCF incubating (2024). Integrated into the Auth0 ecosystem&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ory Keto&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Ory&lt;/td&gt;
&lt;td&gt;Zanzibar-based authorization service, part of Ory's auth stack&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Google Cloud IAM&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Google&lt;/td&gt;
&lt;td&gt;Explicitly built on top of Zanzibar (stated in the paper itself)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;It's not just OSS. &lt;strong&gt;Airbnb built an internal system called "Himeji"&lt;/strong&gt; directly inspired by Zanzibar. Carta and Notion have both adopted Zanzibar-style approaches.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The key ideas from Zanzibar:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;object#relation@user&lt;/code&gt; — a single tuple format that unifies ACLs and groups&lt;/strong&gt;. The same structure handles direct permissions, group membership, and object hierarchies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Userset Rewrites for flexible policy definition.&lt;/strong&gt; &lt;code&gt;owner ⊂ editor ⊂ viewer&lt;/code&gt; and folder-to-document inheritance, all declared in one namespace config&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zookies solve the New Enemy problem.&lt;/strong&gt; An opaque token encodes a causal timestamp; subsequent checks respect that ordering&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built on Spanner's external consistency.&lt;/strong&gt; TrueTime (GPS + atomic clocks) makes causally ordered commits possible at global scale&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leopard makes deep nesting fast.&lt;/strong&gt; Pre-expand group-to-group relationships; membership checks become a set intersection in O(min(|A|,|B|))&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timestamp quantization + distributed caching absorb hot spots.&lt;/strong&gt; Hundreds of simultaneous checks from a single search query don't blow up the system&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zanzibar defined the ReBAC landscape.&lt;/strong&gt; SpiceDB, OpenFGA, and Ory Keto are all downstream of this paper&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Think of Zanzibar as the GFS of authorization systems. GFS established the design patterns for distributed storage. Zanzibar did the same for relationship-based access control.&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.usenix.org/conference/atc19/presentation/pang" rel="noopener noreferrer"&gt;Zanzibar: Google's Consistent, Global Authorization System (USENIX ATC '19)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/authzed/spicedb" rel="noopener noreferrer"&gt;SpiceDB (AuthZed)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://openfga.dev/" rel="noopener noreferrer"&gt;OpenFGA (Okta/Auth0)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.ory.sh/keto/" rel="noopener noreferrer"&gt;Ory Keto&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/iam/" rel="noopener noreferrer"&gt;Google Cloud IAM&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>authorization</category>
      <category>security</category>
      <category>architecture</category>
      <category>google</category>
    </item>
    <item>
      <title>AuthZEN Authorization API 1.0 Deep Dive: The Standard API That Separates Authorization Decisions from Enforcement</title>
      <dc:creator>kt</dc:creator>
      <pubDate>Mon, 30 Mar 2026 12:08:40 +0000</pubDate>
      <link>https://dev.to/kanywst/authzen-authorization-api-10-deep-dive-the-standard-api-that-separates-authorization-decisions-1m2a</link>
      <guid>https://dev.to/kanywst/authzen-authorization-api-10-deep-dive-the-standard-api-that-separates-authorization-decisions-1m2a</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;In the authentication space, OpenID Connect has become the de facto standard, centralizing identity around Identity Providers. In the authorization space — specifically delegated authorization — OAuth 2.0 stands as a robust standard.&lt;/p&gt;

&lt;p&gt;But what about a standard API for &lt;strong&gt;fine-grained authorization&lt;/strong&gt; within applications?&lt;/p&gt;

&lt;p&gt;In the era of microservice architectures, everyone eventually hits the same wall: "Where and how should we evaluate authorization?"&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Service A uses OPA (Open Policy Agent)&lt;/li&gt;
&lt;li&gt;Service B adopted Cedar&lt;/li&gt;
&lt;li&gt;Service C is still dragging around a legacy in-house authorization library&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When authorization logic is scattered across services like this, maintaining a consistent view of "who can do what on which resource" across the entire system becomes extremely difficult — auditing and policy changes become a nightmare.&lt;/p&gt;

&lt;p&gt;The established best practice is to separate the &lt;strong&gt;decision&lt;/strong&gt; (PDP) from the &lt;strong&gt;enforcement&lt;/strong&gt; (PEP). But there was another problem: &lt;strong&gt;no standard protocol existed to connect PDP and PEP.&lt;/strong&gt; While excellent policy engines like OPA, Cedar, and Topaz kept emerging, the communication interface between applications (PEP) and policy engines (PDP) remained proprietary — an era without a standard.&lt;/p&gt;

&lt;p&gt;This situation has finally been resolved by the &lt;strong&gt;AuthZEN Authorization API 1.0&lt;/strong&gt;, developed by the AuthZEN Working Group under the OpenID Foundation. Published as a Standards Track specification in March 2026, this simple JSON-based API is the long-awaited standard protocol for asking "Can &lt;strong&gt;who&lt;/strong&gt; do &lt;strong&gt;what&lt;/strong&gt; on &lt;strong&gt;which resource&lt;/strong&gt;?" between PDP and PEP.&lt;/p&gt;




&lt;h2&gt;
  
  
  Scope of This Article
&lt;/h2&gt;

&lt;p&gt;First, let's clarify where the Authorization API fits in the landscape.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo5gfrr0525qyx7ob4aub.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo5gfrr0525qyx7ob4aub.png" alt="scope" width="800" height="251"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;OAuth 2.0 is a mechanism for "a client to obtain tokens to access a resource server." AuthZEN Authorization API is a mechanism for "externalizing whether a given request should be allowed or denied within an application." They operate at different layers.&lt;/p&gt;

&lt;p&gt;OAuth deals with Client-AS-RS communication. AuthZEN deals with PEP-PDP communication within applications. It's crucial not to confuse the two.&lt;/p&gt;

&lt;p&gt;The Authorization API is version &lt;strong&gt;1.0&lt;/strong&gt;, and endpoints SHOULD include &lt;code&gt;v1&lt;/code&gt; in their paths (e.g., &lt;code&gt;/access/v1/evaluation&lt;/code&gt;). Future revisions MUST NOT modify the existing API — only augment it. This means methods and parameters may be added, but the semantics of existing fields will never change. Additionally, receivers &lt;strong&gt;MUST ignore unknown fields&lt;/strong&gt;, ensuring forward compatibility when old and new PDPs/PEPs coexist.&lt;/p&gt;

&lt;p&gt;This article assumes the following prerequisite knowledge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Basics of HTTP / REST APIs (request-response structure)&lt;/li&gt;
&lt;li&gt;Reading and writing JSON&lt;/li&gt;
&lt;li&gt;Basic access control concepts (who, what, on which resource)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  1. Why Separate Authorization Decisions?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1.1 The Limits of Embedded Authorization Logic (Silos and Technical Debt)
&lt;/h3&gt;

&lt;p&gt;In a monolithic application, middleware could handle access control in one place. However, in modern microservice environments, authorization logic often gets embedded directly in each service's application code, creating silos.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fge6i6fp6sha4icq4oo11.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fge6i6fp6sha4icq4oo11.png" alt="Limits" width="446" height="782"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This pattern of "distributed and embedded authorization" causes increasingly severe problems as systems grow:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Problem&lt;/th&gt;
&lt;th&gt;Real-World Impact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Policy Silos&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The conditions for "who can view a document" are scattered across multiple services, and no one can grasp the consistent policy across the entire system.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;High Cost of Change&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A business requirement change like "restrict external collaborator permissions" requires modifying multiple repositories and redeploying each one.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Inconsistency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Different languages and implementation approaches (DB lookups, JWT evaluation, etc.) per service lead to permission bypass or contradictions in edge cases.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Audit Difficulty (Compliance)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Proving "who can access what" requires reading through each codebase, making security audits and SOC2 compliance requirements practically impossible.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  1.2 PDP/PEP Model — Separating Decision from Enforcement
&lt;/h3&gt;

&lt;p&gt;The solution is to &lt;strong&gt;externalize authorization decisions to a dedicated service&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu9rm2vyf7alwpgnvbq1s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu9rm2vyf7alwpgnvbq1s.png" alt="PDP/PEP" width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The terminology for this model is defined in XACML and NIST SP 800-162 (ABAC):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Term&lt;/th&gt;
&lt;th&gt;Full Name&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PDP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Policy Decision Point&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Decides&lt;/strong&gt; whether to allow access based on policy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PEP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Policy Enforcement Point&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Enforces&lt;/strong&gt; the PDP's decision&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PAP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Policy Administration Point&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Manages&lt;/strong&gt; policies (out of AuthZEN's scope)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PIP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Policy Information Point&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Provides&lt;/strong&gt; attribute information needed for decisions (out of AuthZEN's scope)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The AuthZEN Authorization API standardizes the &lt;strong&gt;PEP -&amp;gt; PDP communication&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.3 Why a Standard API Is Needed
&lt;/h3&gt;

&lt;p&gt;The PDP/PEP model itself is not a new concept. So why is a standard API needed now?&lt;/p&gt;

&lt;p&gt;Because the PDP implementation landscape is highly fragmented.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;PDP Implementation&lt;/th&gt;
&lt;th&gt;Developer&lt;/th&gt;
&lt;th&gt;Policy Language&lt;/th&gt;
&lt;th&gt;API&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OPA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Styra / CNCF&lt;/td&gt;
&lt;td&gt;Rego&lt;/td&gt;
&lt;td&gt;Proprietary REST API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cedar&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AWS&lt;/td&gt;
&lt;td&gt;Cedar&lt;/td&gt;
&lt;td&gt;Proprietary API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Topaz&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Aserto&lt;/td&gt;
&lt;td&gt;Rego / OPA-compatible&lt;/td&gt;
&lt;td&gt;Proprietary REST / gRPC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Axiomatics&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Axiomatics&lt;/td&gt;
&lt;td&gt;ALFA / XACML&lt;/td&gt;
&lt;td&gt;Proprietary REST API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SpiceDB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AuthZed&lt;/td&gt;
&lt;td&gt;Zanzibar-style&lt;/td&gt;
&lt;td&gt;Proprietary gRPC&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Since each PDP has its own API, applications become tightly coupled to a specific PDP. If you want to switch PDPs, you need to rewrite all authorization call code on the application side.&lt;/p&gt;

&lt;p&gt;The AuthZEN Authorization API aims to &lt;strong&gt;standardize the PDP-PEP interface&lt;/strong&gt;, making PDP implementations interchangeable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6zj0mig5ic7raz9lje3m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6zj0mig5ic7raz9lje3m.png" alt="Authzen" width="800" height="165"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The Information Model of the Authorization API
&lt;/h2&gt;

&lt;p&gt;The core of the Authorization API is the information model called the "4-tuple." An Access Evaluation request consists of four elements.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faeuog1alfp54zj70damt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faeuog1alfp54zj70damt.png" alt="Decision" width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's examine each one in detail.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.1 Subject (Who)
&lt;/h3&gt;

&lt;p&gt;A Subject represents the user or machine principal (such as a service account) requesting access.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Required&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;type&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;REQUIRED&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;The type of Subject (&lt;code&gt;user&lt;/code&gt;, &lt;code&gt;service&lt;/code&gt;, &lt;code&gt;device&lt;/code&gt;, etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;REQUIRED&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;Unique identifier for the Subject (unique within its &lt;code&gt;type&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;properties&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OPTIONAL&lt;/td&gt;
&lt;td&gt;object&lt;/td&gt;
&lt;td&gt;Additional attributes (department, IP address, device ID, etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"alice@acmecorp.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"department"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sales"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ip_address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"172.217.22.14"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"device_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"8:65:ee:17:7e:0b"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;properties&lt;/code&gt; field is important. Many authorization systems operate statelessly, so the PEP needs to pass all attributes required for policy evaluation at request time. For example, if there's a policy stating "only Sales department users can access," the PEP must include &lt;code&gt;department&lt;/code&gt; in properties — otherwise the PDP cannot make a decision.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.2 Resource (On What)
&lt;/h3&gt;

&lt;p&gt;A Resource represents the target of the access request.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Required&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;type&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;REQUIRED&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;The type of Resource (&lt;code&gt;document&lt;/code&gt;, &lt;code&gt;account&lt;/code&gt;, &lt;code&gt;book&lt;/code&gt;, etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;REQUIRED&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;Unique identifier for the Resource&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;properties&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OPTIONAL&lt;/td&gt;
&lt;td&gt;object&lt;/td&gt;
&lt;td&gt;Additional attributes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"book"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"library_record"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AuthZEN in Action"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"isbn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"978-0593383322"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since &lt;code&gt;properties&lt;/code&gt; can contain nested JSON objects, resource metadata can be expressed flexibly.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.3 Action (What)
&lt;/h3&gt;

&lt;p&gt;An Action represents the operation the Subject intends to perform on the Resource.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Required&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;REQUIRED&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;Name of the action&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;properties&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OPTIONAL&lt;/td&gt;
&lt;td&gt;object&lt;/td&gt;
&lt;td&gt;Additional attributes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"can_read"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The specification defines common action names corresponding to CRUD operations:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;can_access&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Generic access (when not distinguishing by type)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;can_create&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;can_read&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Read (including list retrieval)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;can_update&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Update (partial or full replacement)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;can_delete&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Delete&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are recommended common names, but application-specific action names (e.g., &lt;code&gt;can_approve&lt;/code&gt;, &lt;code&gt;can_publish&lt;/code&gt;) can be freely used.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.4 Context (Under What Circumstances)
&lt;/h3&gt;

&lt;p&gt;Context is an arbitrary JSON object representing environmental information or circumstances of the request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1985-10-26T01:22-07:00"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Time, network information, risk scores, and other information needed for decisions but not belonging to Subject, Action, or Resource go here.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Access Evaluation API — Request and Response
&lt;/h2&gt;

&lt;p&gt;Now that we understand the information model, let's look at the actual API.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.1 Request
&lt;/h3&gt;

&lt;p&gt;Assemble the 4-tuple into JSON and send it to the PDP.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subject"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"alice@acmecorp.com"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"account"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"123"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"can_read"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1985-10-26T01:22-07:00"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In plain language, this query asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can the user &lt;strong&gt;&lt;a href="mailto:alice@acmecorp.com"&gt;alice@acmecorp.com&lt;/a&gt;&lt;/strong&gt; &lt;strong&gt;read (can_read)&lt;/strong&gt; &lt;strong&gt;account #123&lt;/strong&gt;? (At time 1985-10-26T01:22-07:00)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  3.2 Response — Decision
&lt;/h3&gt;

&lt;p&gt;The simplest response is just a JSON with a &lt;code&gt;decision&lt;/code&gt; field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"decision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;decision&lt;/code&gt; is a boolean — &lt;code&gt;true&lt;/code&gt; = allow, &lt;code&gt;false&lt;/code&gt; = deny. &lt;strong&gt;That's it.&lt;/strong&gt; The specification is intentionally designed to be simple.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.3 Response — Additional Context
&lt;/h3&gt;

&lt;p&gt;The PDP can return additional information via a &lt;code&gt;context&lt;/code&gt; field alongside the &lt;code&gt;decision&lt;/code&gt;. Use cases cited by the specification:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Advice" or "obligations"&lt;/strong&gt; — Additional instructions like "record this access in the audit log" (a concept from XACML; Section 9 compares XACML in detail)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UI rendering hints&lt;/strong&gt; — "This operation is not permitted, so gray out the button"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step-up authentication instructions&lt;/strong&gt; — "This operation requires additional authentication"
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"decision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"reason_admin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Request failed policy C076E82F"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"reason_user"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"en-403"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Insufficient privileges. Contact your administrator"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"es-403"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Privilegios insuficientes. Póngase en contacto con su administrador"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;reason_admin&lt;/code&gt; provides administrator-facing reasons (not to be shown to end users), while &lt;code&gt;reason_user&lt;/code&gt; provides user-facing reasons. Multi-language support is also possible.&lt;/p&gt;

&lt;p&gt;An example returning step-up authentication hints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"decision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"acr_values"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"urn:com:example:loa:3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"amr_values"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mfa hwk"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, the PEP can instruct the user to "re-authenticate at LOA 3 or higher (MFA + hardware key) and retry."&lt;/p&gt;

&lt;p&gt;Notably, even when &lt;code&gt;decision: true&lt;/code&gt;, if the PEP does not understand the &lt;code&gt;context&lt;/code&gt;, it MAY choose to reject the access. Conversely, &lt;code&gt;decision: false&lt;/code&gt; is strict — the PEP MUST NOT permit access.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Access Evaluations API — Batch Evaluation
&lt;/h2&gt;

&lt;p&gt;The Access Evaluation API in Section 3 was "one request = one decision." But in real applications, there are scenarios where you need to check permissions for multiple resources simultaneously when rendering a screen.&lt;/p&gt;

&lt;p&gt;For example, on a document list screen, checking "can this user read?" for each of 30 documents individually would mean 30 API calls — highly inefficient.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Access Evaluations API&lt;/strong&gt; (note the trailing &lt;code&gt;s&lt;/code&gt;) is a batch evaluation API that solves this problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.1 Batch Request
&lt;/h3&gt;

&lt;p&gt;Bundle multiple evaluation requests in an &lt;code&gt;evaluations&lt;/code&gt; array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subject"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"alice@example.com"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"can_read"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"evaluations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"document"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"boxcarring.md"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"document"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"subject-search.md"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"document"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"resource-search.md"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key mechanism here is &lt;strong&gt;default values&lt;/strong&gt;. Top-level &lt;code&gt;subject&lt;/code&gt;, &lt;code&gt;action&lt;/code&gt;, &lt;code&gt;resource&lt;/code&gt;, and &lt;code&gt;context&lt;/code&gt; serve as defaults for each element in the &lt;code&gt;evaluations&lt;/code&gt; array. In the example above, all three evaluations use the same &lt;code&gt;subject&lt;/code&gt; and &lt;code&gt;action&lt;/code&gt;, so they're specified at the top level to avoid duplication.&lt;/p&gt;

&lt;p&gt;Individual evaluations can override defaults:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subject"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"alice@example.com"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"can_read"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"evaluations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"document"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"document"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"can_edit"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"document"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only the third evaluation overrides the &lt;code&gt;action&lt;/code&gt; to &lt;code&gt;can_edit&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.2 Batch Response
&lt;/h3&gt;

&lt;p&gt;The response also uses an &lt;code&gt;evaluations&lt;/code&gt; array, corresponding to the request in the same order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"evaluations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"decision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"decision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"resource not found"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"decision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Subject is a viewer of the resource"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4.3 Evaluation Semantics — Three Execution Modes
&lt;/h3&gt;

&lt;p&gt;Batch evaluation supports three execution modes via &lt;code&gt;options.evaluations_semantic&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Semantic&lt;/th&gt;
&lt;th&gt;Behavior&lt;/th&gt;
&lt;th&gt;Programming Analogy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;execute_all&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Execute all requests and return all results (default)&lt;/td&gt;
&lt;td&gt;Array &lt;code&gt;map&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;deny_on_first_deny&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Short-circuit on first denial&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; operator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;permit_on_first_permit&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Short-circuit on first permit&lt;/td&gt;
&lt;td&gt;`\&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Let's see how they work with a concrete example. Suppose we check {% raw %}&lt;code&gt;read&lt;/code&gt; permissions for three documents (doc#1, doc#2, doc#3), with results of &lt;code&gt;true&lt;/code&gt;, &lt;code&gt;false&lt;/code&gt;, &lt;code&gt;true&lt;/code&gt; respectively:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;execute_all&lt;/code&gt;&lt;/strong&gt;: Evaluates all three -&amp;gt; returns &lt;code&gt;[true, false, true]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;deny_on_first_deny&lt;/code&gt;&lt;/strong&gt;: doc#1 -&amp;gt; true, doc#2 -&amp;gt; false, stops here. doc#3 is not evaluated -&amp;gt; returns &lt;code&gt;[true, false]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;permit_on_first_permit&lt;/code&gt;&lt;/strong&gt;: doc#1 -&amp;gt; true, stops here. doc#2 and doc#3 are not evaluated -&amp;gt; returns &lt;code&gt;[true]&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;deny_on_first_deny&lt;/code&gt; is useful for scenarios where "all checks must pass." For example, sequentially verifying that a user has a specific role AND is the resource owner AND it's within business hours. &lt;code&gt;permit_on_first_permit&lt;/code&gt; is for scenarios where "matching any one rule is sufficient."&lt;/p&gt;

&lt;h3&gt;
  
  
  4.4 Error Handling
&lt;/h3&gt;

&lt;p&gt;Batch evaluation has two types of errors:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Transport-level errors&lt;/strong&gt; — Errors affecting the entire request (HTTP 4XX / 5XX)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Individual evaluation errors&lt;/strong&gt; — A specific evaluation within the &lt;code&gt;evaluations&lt;/code&gt; array fails. Returns &lt;code&gt;decision: false&lt;/code&gt; + error information in &lt;code&gt;context&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"evaluations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"decision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"decision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Resource not found"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"decision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Subject is a viewer"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Individual errors default to deny (&lt;code&gt;false&lt;/code&gt;), and the overall request does not fail.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Search APIs — Reverse Lookups for "Who?" and "What?"
&lt;/h2&gt;

&lt;p&gt;The Access Evaluation API was a &lt;strong&gt;forward lookup&lt;/strong&gt;: "Can Alice read document#123?" But real applications also need reverse lookups:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Who can read document#123?"&lt;/strong&gt; — Display in sharing settings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Which documents can Alice read?"&lt;/strong&gt; — Filter the document list&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"What operations can Alice perform on document#123?"&lt;/strong&gt; — Control UI button visibility&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are the &lt;strong&gt;Search APIs&lt;/strong&gt;. By omitting the &lt;code&gt;id&lt;/code&gt; of one element from the 4-tuple in the request, the PDP returns a list of permitted entities.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1 Three Types of Search APIs
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;API&lt;/th&gt;
&lt;th&gt;Omitted Element&lt;/th&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Subject Search&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Subject's &lt;code&gt;id&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;"Who can perform this action on this resource?"&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/access/v1/search/subject&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Resource Search&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Resource's &lt;code&gt;id&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;"Which resources can this user perform this action on?"&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/access/v1/search/resource&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Action Search&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The entire Action&lt;/td&gt;
&lt;td&gt;"What actions can this user perform on this resource?"&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/access/v1/search/action&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  5.2 Subject Search Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Request:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Which&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;users&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;can&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;can_read&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;account#&lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subject"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"can_read"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"account"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"123"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;subject&lt;/code&gt; has a &lt;code&gt;type&lt;/code&gt; but no &lt;code&gt;id&lt;/code&gt;. This signals "search for Subjects."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Response&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"results"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"alice@example.com"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bob@example.com"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5.3 Resource Search Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Request:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Which&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;accounts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;can&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;alice&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;can_read?&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subject"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"alice@example.com"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"can_read"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"account"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5.4 Pagination
&lt;/h3&gt;

&lt;p&gt;Search APIs can return large result sets. Pagination is designed around &lt;strong&gt;opaque tokens&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu1bumw8c4vhc6nn11ndd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu1bumw8c4vhc6nn11ndd.png" alt="Pagination" width="454" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Direction&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;page.limit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Request&lt;/td&gt;
&lt;td&gt;Maximum items per page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;page.token&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Request&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;next_token&lt;/code&gt; from the previous response (for page 2+)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;page.next_token&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Response&lt;/td&gt;
&lt;td&gt;Token for the next page. Empty string means final page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;page.count&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Response&lt;/td&gt;
&lt;td&gt;Number of items in this page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;page.total&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Response&lt;/td&gt;
&lt;td&gt;Total item count (estimate, may fluctuate)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;page.properties&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Both&lt;/td&gt;
&lt;td&gt;Implementation-specific attributes such as sorting or filtering (optional)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Important constraint: During pagination, &lt;code&gt;subject&lt;/code&gt;, &lt;code&gt;action&lt;/code&gt;, &lt;code&gt;resource&lt;/code&gt;, and &lt;code&gt;context&lt;/code&gt; MUST NOT be changed. If changed, the PDP SHOULD return an error.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.5 Search API Semantics
&lt;/h3&gt;

&lt;p&gt;Important rules defined by the specification:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Results from a Search API, when passed to the Access Evaluation API, SHOULD yield &lt;code&gt;decision: true&lt;/code&gt;. However, this is not guaranteed since the decision may depend on time-varying factors&lt;/li&gt;
&lt;li&gt;Searches SHOULD be performed &lt;strong&gt;transitively&lt;/strong&gt;. For example, if user U is a member of group G, and group G is a viewer of document D, then user U should appear in Subject Search results for document D&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  6. PDP Metadata — The PDP's Self-Description
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;PDP Metadata&lt;/strong&gt; is the mechanism by which a PDP tells its clients (PEPs) which endpoints it supports.&lt;/p&gt;

&lt;p&gt;The mechanism follows the &lt;strong&gt;&lt;code&gt;.well-known&lt;/code&gt; URI&lt;/strong&gt; pattern (RFC 8615) — a web convention for publishing server configuration at a known URL. For example, OAuth authorization servers publish their configuration at &lt;code&gt;/.well-known/oauth-authorization-server&lt;/code&gt;. AuthZEN PDPs follow the same pattern.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.1 Discovery Endpoint
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /.well-known/authzen-configuration HTTP/1.1
Host: pdp.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In multi-tenant environments, tenant-specific metadata can be provided:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /.well-known/authzen-configuration/tenant1 HTTP/1.1
Host: pdp.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6.2 Metadata Response
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"policy_decision_point"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://pdp.example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"access_evaluation_endpoint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://pdp.example.com/access/v1/evaluation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"access_evaluations_endpoint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://pdp.example.com/access/v1/evaluations"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"search_subject_endpoint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://pdp.example.com/access/v1/search/subject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"search_resource_endpoint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://pdp.example.com/access/v1/search/resource"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"search_action_endpoint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://pdp.example.com/access/v1/search/action"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"capabilities"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"urn:ietf:params:authzen:some-capability"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Required&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;policy_decision_point&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;REQUIRED&lt;/td&gt;
&lt;td&gt;PDP identifier (URL)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;access_evaluation_endpoint&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;REQUIRED&lt;/td&gt;
&lt;td&gt;Single evaluation API endpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;access_evaluations_endpoint&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OPTIONAL&lt;/td&gt;
&lt;td&gt;Batch evaluation API endpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;search_subject_endpoint&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OPTIONAL&lt;/td&gt;
&lt;td&gt;Subject search endpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;search_resource_endpoint&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OPTIONAL&lt;/td&gt;
&lt;td&gt;Resource search endpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;search_action_endpoint&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OPTIONAL&lt;/td&gt;
&lt;td&gt;Action search endpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;capabilities&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OPTIONAL&lt;/td&gt;
&lt;td&gt;List of URNs for capabilities supported by the PDP&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If an optional parameter is absent, the PEP can determine that the corresponding API is not supported. For example, if &lt;code&gt;search_subject_endpoint&lt;/code&gt; is missing, Subject Search is not available.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.3 Signed Metadata
&lt;/h3&gt;

&lt;p&gt;Metadata can also be provided as a &lt;strong&gt;JWT (JSON Web Token, RFC 7519)&lt;/strong&gt; via the &lt;code&gt;signed_metadata&lt;/code&gt; parameter, enabling tamper detection through its header-payload-signature structure.&lt;/p&gt;

&lt;p&gt;Requirements for signed metadata in the specification:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The JWT MUST be signed with &lt;strong&gt;JWS (RFC 7515)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;The JWT MUST contain an &lt;code&gt;iss&lt;/code&gt; (issuer) claim&lt;/li&gt;
&lt;li&gt;Values in signed metadata &lt;strong&gt;take precedence&lt;/strong&gt; over plain JSON metadata&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;signed_metadata&lt;/code&gt; itself SHOULD NOT appear as a claim within the JWT&lt;/li&gt;
&lt;li&gt;If the PEP does not support signature verification, it MAY ignore &lt;code&gt;signed_metadata&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This enables metadata integrity verification independent of TLS/PKI. This is particularly valuable in environments with intermediate proxies, where TLS alone cannot guarantee end-to-end integrity.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.4 Validation
&lt;/h3&gt;

&lt;p&gt;The PEP MUST verify that the &lt;code&gt;policy_decision_point&lt;/code&gt; value in the metadata matches the PDP identifier used to construct the &lt;code&gt;.well-known&lt;/code&gt; URL. If they don't match, the metadata MUST NOT be used. This is the same mix-up attack countermeasure used in OAuth AS Metadata.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. HTTPS Binding — The Actual HTTP Requests
&lt;/h2&gt;

&lt;p&gt;While the Authorization API itself is designed to be transport-agnostic, the specification defines the &lt;strong&gt;HTTPS binding&lt;/strong&gt; as mandatory (with potential future additions for gRPC, CoAP, etc.).&lt;/p&gt;

&lt;h3&gt;
  
  
  7.1 Endpoint List
&lt;/h3&gt;

&lt;p&gt;All requests are sent via HTTPS POST.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;API&lt;/th&gt;
&lt;th&gt;Default Path&lt;/th&gt;
&lt;th&gt;Metadata Parameter&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Access Evaluation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/access/v1/evaluation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;access_evaluation_endpoint&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Access Evaluations&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/access/v1/evaluations&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;access_evaluations_endpoint&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Subject Search&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/access/v1/search/subject&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;search_subject_endpoint&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Resource Search&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/access/v1/search/resource&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;search_resource_endpoint&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Action Search&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/access/v1/search/action&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;search_action_endpoint&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If endpoint URLs are provided via PDP Metadata (Section 6), use those. Otherwise, use the default paths.&lt;/p&gt;

&lt;h3&gt;
  
  
  7.2 Request Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="nf"&gt;POST&lt;/span&gt; &lt;span class="nn"&gt;/access/v1/evaluation&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;span class="na"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pdp.mycompany.com&lt;/span&gt;
&lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;
&lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Bearer &amp;lt;myoauthtoken&amp;gt;&lt;/span&gt;
&lt;span class="na"&gt;X-Request-ID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bfe9eb29-ab87-4ca3-be83-a1d5d8305716&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subject"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"alice@acmecorp.com"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"todo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"can_read"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1985-10-26T01:22-07:00"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7.3 Response Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt; &lt;span class="ne"&gt;OK&lt;/span&gt;
&lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;
&lt;span class="na"&gt;X-Request-ID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bfe9eb29-ab87-4ca3-be83-a1d5d8305716&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"decision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7.4 Error Responses
&lt;/h3&gt;

&lt;p&gt;The critical point here is that &lt;strong&gt;authorization "denial" and HTTP errors are entirely different things&lt;/strong&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Status Code&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;200 + &lt;code&gt;decision: false&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The authorization request was processed successfully; the result is "deny"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;400&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Malformed request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;401&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;PEP is not authenticated to the PDP (e.g., invalid Bearer token)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;403&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;PEP is not authorized to use the PDP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;500&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;PDP internal error&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In other words, &lt;code&gt;401&lt;/code&gt; means "the PEP failed to authenticate to the PDP," while &lt;code&gt;200 + decision: false&lt;/code&gt; means "the PDP denied access based on policy." These two must not be confused.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fce9vmub3vc5ug6srobk4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fce9vmub3vc5ug6srobk4.png" alt="Response" width="501" height="638"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  7.5 Request ID
&lt;/h3&gt;

&lt;p&gt;The PEP can assign a unique ID to requests via the &lt;code&gt;X-Request-ID&lt;/code&gt; header. The PDP MUST include the same ID in the response. This is used for tracing and debugging in distributed systems.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Mapping to Authorization Models
&lt;/h2&gt;

&lt;p&gt;The Authorization API does not assume a specific authorization model. It works with RBAC, ABAC, ReBAC, or any other model.&lt;/p&gt;

&lt;h3&gt;
  
  
  8.1 Usage with Each Authorization Model
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Authorization Model&lt;/th&gt;
&lt;th&gt;Subject Usage&lt;/th&gt;
&lt;th&gt;Resource Usage&lt;/th&gt;
&lt;th&gt;Context Usage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RBAC&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Include role information in &lt;code&gt;properties&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Type and ID&lt;/td&gt;
&lt;td&gt;Often unused&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ABAC&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Include attribute information in &lt;code&gt;properties&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Include attribute information in &lt;code&gt;properties&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Environmental info: time, location, etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ReBAC&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Resolve relationships via ID&lt;/td&gt;
&lt;td&gt;Include owner/relationship information in &lt;code&gt;properties&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Often unused&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The flexibility of &lt;code&gt;properties&lt;/code&gt; and &lt;code&gt;context&lt;/code&gt; in the Authorization API enables this model-agnostic design.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Comparison with XACML
&lt;/h2&gt;

&lt;p&gt;The Authorization API is not a successor to XACML, but addresses the same problem domain. Let's compare.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Comparison&lt;/th&gt;
&lt;th&gt;XACML&lt;/th&gt;
&lt;th&gt;AuthZEN Authorization API&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Era&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2003 (1.0), 2013 (3.0)&lt;/td&gt;
&lt;td&gt;Published March 2026 (Standards Track)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data Format&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;XML&lt;/td&gt;
&lt;td&gt;JSON&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Decision Values&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Permit / Deny / NotApplicable / Indeterminate&lt;/td&gt;
&lt;td&gt;true / false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Policy Language&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;XACML (XML-based) defined&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Not defined&lt;/strong&gt; (uses OPA, Cedar, etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Batch Evaluation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Multiple Decision Profile&lt;/td&gt;
&lt;td&gt;Access Evaluations API + evaluation semantics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Search&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;None (PDP-dependent)&lt;/td&gt;
&lt;td&gt;Search APIs (Subject / Resource / Action)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Discovery&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;PDP Metadata (&lt;code&gt;.well-known/authzen-configuration&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Transport&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;SOAP / HTTP&lt;/td&gt;
&lt;td&gt;HTTPS (+ future gRPC)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Complexity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Very high&lt;/td&gt;
&lt;td&gt;Intentionally simple&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Adoption&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Large enterprises / government&lt;/td&gt;
&lt;td&gt;Designed for modern cloud-native environments&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The biggest difference is that &lt;strong&gt;XACML standardized both the decision method (policy language) and the communication protocol, while AuthZEN deliberately avoids the decision method and standardizes only the communication protocol&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is an intentional design decision. Given that OPA (Rego), Cedar, Topaz, and others already have mature policy languages, standardizing only the interface that connects PDPs to PEPs is more practical than also standardizing a policy language.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Security Considerations
&lt;/h2&gt;

&lt;p&gt;The PEP-PDP communication is the very foundation of access control. If this communication is attacked, the authorization decisions themselves can be tampered with.&lt;/p&gt;

&lt;h3&gt;
  
  
  10.1 Communication Integrity and Confidentiality
&lt;/h3&gt;

&lt;p&gt;TLS (HTTPS) is REQUIRED for PEP-PDP communication. The reasons are clear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Integrity&lt;/strong&gt;: An attacker modifying requests or responses could rewrite &lt;code&gt;decision: false&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confidentiality&lt;/strong&gt;: Requests contain "who is trying to access what," and leakage would expose internal structure and behavioral patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  10.2 PEP Authentication
&lt;/h3&gt;

&lt;p&gt;The PDP SHOULD authenticate the PEP. Without authentication, attackers could flood the PDP with requests for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DoS attacks&lt;/strong&gt; — Taking down the PDP and disabling all authorization decisions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Policy probing&lt;/strong&gt; — Testing various request patterns to infer internal policies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Authentication methods are out of scope for the specification, but the following are cited:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;mTLS&lt;/li&gt;
&lt;li&gt;OAuth-based authentication (Bearer Token)&lt;/li&gt;
&lt;li&gt;API keys&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  10.3 JSON Payload Considerations
&lt;/h3&gt;

&lt;p&gt;The specification RECOMMENDS that JSON payloads follow the &lt;strong&gt;I-JSON profile (RFC 7493)&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UTF-8 encoding (no invalid Unicode sequences)&lt;/li&gt;
&lt;li&gt;Numeric values within IEEE 754 double-precision range&lt;/li&gt;
&lt;li&gt;Unique member names after escape processing&lt;/li&gt;
&lt;li&gt;Null-valued properties SHOULD be omitted&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  10.4 Trust Model
&lt;/h3&gt;

&lt;p&gt;The specification states clearly: &lt;strong&gt;The PDP must trust the PEP.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This may seem surprising at first, but it makes sense when you think about it. The PEP is ultimately the one that allows or denies access, and no PDP can be effective if the PEP ignores its decisions. The PDP trusting the attribute values sent by the PEP is part of this trust relationship.&lt;/p&gt;

&lt;h3&gt;
  
  
  10.5 Response Integrity
&lt;/h3&gt;

&lt;p&gt;The PDP MAY add &lt;strong&gt;digital signatures&lt;/strong&gt; to responses. While TLS provides transport-layer protection, signatures provide application-layer non-repudiation and integrity verification. This is particularly valuable in environments with intermediate proxies.&lt;/p&gt;

&lt;h3&gt;
  
  
  10.6 Availability and DoS Countermeasures
&lt;/h3&gt;

&lt;p&gt;If the PDP goes down, access control for the entire application ceases to function. The specification recommends the following defenses for PDPs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Payload size limits&lt;/li&gt;
&lt;li&gt;Rate limiting&lt;/li&gt;
&lt;li&gt;Protection against invalid JSON and deeply nested JSON&lt;/li&gt;
&lt;li&gt;Memory consumption limits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmwa2rbk8494wflnlv535.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmwa2rbk8494wflnlv535.png" alt="Availability and DoS" width="800" height="180"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  11. The Ecosystem in Practice
&lt;/h2&gt;

&lt;h3&gt;
  
  
  11.1 AuthZEN Working Group
&lt;/h3&gt;

&lt;p&gt;AuthZEN (Authorization Zone) was established as a Working Group under the OpenID Foundation in 2023. Key contributors and companies:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Company&lt;/th&gt;
&lt;th&gt;Contribution&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Aserto&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Specification editor (Omri Gazitt), Topaz PDP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Axiomatics&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Specification editor (David Brossard), XACML/ALFA expertise&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SGNL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Specification editor (Atul Tulshibagwale), contributor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Styra&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Developer of OPA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AWS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Developer of Cedar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AuthZed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Developer of SpiceDB (Zanzibar)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Okta / Auth0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Developer of OpenFGA (ReBAC engine)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  11.2 Interop Demo — Can You Really Swap PDPs?
&lt;/h3&gt;

&lt;p&gt;"Standardize the API and PDPs become interchangeable" might sound idealistic. However, the AuthZEN WG has demonstrated this at Interop events.&lt;/p&gt;

&lt;p&gt;At &lt;strong&gt;Identiverse 2024&lt;/strong&gt; (June 2024), a demo "Todo App" operated with a single PEP implementation that connected to 5+ different PDPs (Topaz, Axiomatics, OpenFGA, etc.) by simply switching endpoint URLs. It was proven that PDPs can be swapped without any code changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  11.3 Relationship to Zero Trust Architecture
&lt;/h3&gt;

&lt;p&gt;AuthZEN directly aligns with NIST SP 800-207 (Zero Trust Architecture). Zero Trust is not just about "not trusting the network boundary" — it also demands that "every request is individually authorized." AuthZEN provides exactly this "per-request authorization decision" through a standard API.&lt;/p&gt;

&lt;h3&gt;
  
  
  11.4 Current Status
&lt;/h3&gt;

&lt;p&gt;Authorization API 1.0 was &lt;strong&gt;officially published as Standards Track on March 11, 2026.&lt;/strong&gt; After progressing through the Implementer's Draft stage, all the APIs discussed in this article are defined:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Access Evaluation API (single evaluation)&lt;/li&gt;
&lt;li&gt;Access Evaluations API (batch evaluation)&lt;/li&gt;
&lt;li&gt;Search APIs (Subject / Resource / Action search)&lt;/li&gt;
&lt;li&gt;PDP Metadata (discovery)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The specification also includes the establishment of IANA registries (PDP Metadata, PDP Capabilities, Well-Known URI &lt;code&gt;authzen-configuration&lt;/code&gt;, URN sub-namespace &lt;code&gt;authzen&lt;/code&gt;).&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ic6b0453z4hj8a3ngp4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ic6b0453z4hj8a3ngp4.png" alt="Conclusion" width="800" height="147"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Key takeaways of AuthZEN Authorization API 1.0:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A standard API that separates authorization decisions (PDP) from enforcement (PEP).&lt;/strong&gt; Externalize authorization logic from application code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The 4-tuple information model (Subject / Action / Resource / Context).&lt;/strong&gt; Express "who," "what," "on which resource," and "under what circumstances" in simple JSON&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decision is boolean (true / false).&lt;/strong&gt; Intentionally simpler than XACML's 4 possible values&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access Evaluations API for batch evaluation.&lt;/strong&gt; Supports default values and 3 evaluation semantics (execute_all / deny_on_first_deny / permit_on_first_permit)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search APIs for reverse lookups.&lt;/strong&gt; Search "who can access," "what can be accessed," and "what actions are allowed" with pagination&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PDP Metadata for discovery.&lt;/strong&gt; Advertise PDP capabilities via &lt;code&gt;.well-known/authzen-configuration&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Does not define a policy language.&lt;/strong&gt; Existing PDPs like OPA, Cedar, XACML, and Topaz work as-is. Only the API is standardized&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTPS binding is mandatory.&lt;/strong&gt; gRPC and others may be added in the future. PEP-PDP communication is protected with TLS + authentication&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authorization "denial" and HTTP errors are distinct.&lt;/strong&gt; &lt;code&gt;200 + decision: false&lt;/code&gt; is a policy-based denial; &lt;code&gt;401&lt;/code&gt; is a PEP authentication failure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developed by the AuthZEN WG under the OpenID Foundation, officially published March 2026.&lt;/strong&gt; Major authorization engine developers participated, and interoperability was demonstrated at Interop events&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If XACML was "the XML of the authorization world," AuthZEN is "the JSON REST API of the authorization world." They do the same thing — connect PDPs and PEPs via a standard protocol — but AuthZEN has been redesigned for simplicity to match the modern development experience.&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://openid.net/specs/authorization-api-1_0.html" rel="noopener noreferrer"&gt;Authorization API 1.0 (OpenID Foundation)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://openid.net/wg/authzen/" rel="noopener noreferrer"&gt;OpenID AuthZEN Working Group&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://docs.oasis-open.org/xacml/3.0/xacml-3.0-core-spec-os-en.html" rel="noopener noreferrer"&gt;XACML 3.0 (OASIS)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://csrc.nist.gov/publications/detail/sp/800-162/final" rel="noopener noreferrer"&gt;NIST SP 800-162: Guide to Attribute Based Access Control (ABAC)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>oauth</category>
      <category>security</category>
      <category>authorization</category>
      <category>api</category>
    </item>
    <item>
      <title>RBAC vs ABAC vs ReBAC: How to Choose and Implement Access Control Models</title>
      <dc:creator>kt</dc:creator>
      <pubDate>Sun, 29 Mar 2026 12:02:44 +0000</pubDate>
      <link>https://dev.to/kanywst/rbac-vs-abac-vs-rebac-how-to-choose-and-implement-access-control-models-3i2d</link>
      <guid>https://dev.to/kanywst/rbac-vs-abac-vs-rebac-how-to-choose-and-implement-access-control-models-3i2d</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;With the shift toward microservices and the widespread adoption of multi-tenant SaaS, requirements that cannot be expressed by traditional access control are rapidly increasing.&lt;/p&gt;

&lt;p&gt;Have you ever heard the term &lt;strong&gt;Role Explosion&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;RBAC alone is not enough. So, is ABAC the answer? Or ReBAC, which we hear a lot about lately? What exactly is the difference?&lt;/p&gt;

&lt;p&gt;In this article, we will compare three access control models—RBAC, ABAC, and ReBAC—by looking at practical policy examples from actual products (AWS IAM, Kubernetes, Cedar, OpenFGA, and SpiceDB).&lt;/p&gt;




&lt;h2&gt;
  
  
  1. RBAC — Role-Based Access Control
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1.1 The Basic Structure of RBAC
&lt;/h3&gt;

&lt;p&gt;Let's start with RBAC. It is the simplest model and likely the first one everyone encounters.&lt;/p&gt;

&lt;p&gt;What it does is straightforward: &lt;strong&gt;Assign roles to users, and assign permissions to roles.&lt;/strong&gt; Users do not hold permissions directly; they acquire them through their roles.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffojtcye78h97gk08u021.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffojtcye78h97gk08u021.png" alt="RBAC" width="737" height="277"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The reasons this became so widespread are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For a new user, you only need to assign one role.&lt;/li&gt;
&lt;li&gt;If you ask, "Who has the Editor role?", you can get an answer immediately.&lt;/li&gt;
&lt;li&gt;It is easy for non-engineers to understand.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  1.2 NIST RBAC Model — The 4 Levels
&lt;/h3&gt;

&lt;p&gt;Although I said it's just "assigning roles," RBAC actually has stages. NIST defines four levels.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Added Functionality&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Level 1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Flat RBAC&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Basic structure: User → Role → Permission&lt;/td&gt;
&lt;td&gt;Most applications fall here&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Level 2&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Hierarchical RBAC&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Role inheritance. Higher roles include lower ones&lt;/td&gt;
&lt;td&gt;A Manager also has Member permissions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Level 3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Constrained RBAC&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Separation of Duties (SoD). Mutually exclusive roles&lt;/td&gt;
&lt;td&gt;"Approver" and "Applicant" cannot be the same person&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Level 4&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Symmetric RBAC&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Reverse lookup from Permission → Role&lt;/td&gt;
&lt;td&gt;"Which roles have S3 write permissions?"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In practice, Levels 1 and 2 are the most common. Level 3 (Separation of Duties) is required in highly regulated fields like finance and healthcare.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.3 RBAC in Real Products
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Kubernetes RBAC
&lt;/h4&gt;

&lt;p&gt;Kubernetes RBAC is essentially NIST Level 1 (Flat RBAC) with the addition of Namespace scoping. It uses four main resources.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxlh0qlcnh4e89vcqdpwl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxlh0qlcnh4e89vcqdpwl.png" alt="Kubernetes RBAC" width="800" height="205"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Permission definition (ClusterRole)&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterRole&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pod-reader&lt;/span&gt;
&lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;apiGroups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pods"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;verbs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;list"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;watch"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="c1"&gt;# Assignment to user (RoleBinding)&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RoleBinding&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read-pods&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;subjects&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;User&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jane&lt;/span&gt;
  &lt;span class="na"&gt;apiGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io&lt;/span&gt;
&lt;span class="na"&gt;roleRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterRole&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pod-reader&lt;/span&gt;
  &lt;span class="na"&gt;apiGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A key characteristic of Kubernetes RBAC is that it is &lt;strong&gt;Allow-only&lt;/strong&gt; (explicit denys do not exist). Permissions are purely additive; if multiple RoleBindings overlap, all allowed actions are combined.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Role-Based Part of AWS IAM
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3:PutObject"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::my-bucket/*"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This policy is attached to an IAM Role (e.g., &lt;code&gt;S3-ReadWrite-Role&lt;/code&gt;), and users or services assume that role (&lt;code&gt;AssumeRole&lt;/code&gt;). This is the RBAC portion.&lt;/p&gt;

&lt;p&gt;However, AWS IAM is not pure RBAC. As we will see later, it also allows ABAC-like controls via the &lt;code&gt;Condition&lt;/code&gt; block.&lt;/p&gt;

&lt;h4&gt;
  
  
  Azure RBAC
&lt;/h4&gt;

&lt;p&gt;Azure provides over 120 built-in roles (Owner, Contributor, Reader, etc.) and features a hierarchical scope structure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyjvyqzc2ba3th6i2vf0k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyjvyqzc2ba3th6i2vf0k.png" alt="Azure RBAC" width="569" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Roles assigned at a higher scope are inherited by lower scopes. This is different from NIST Level 2 role hierarchies; this is inheritance through the &lt;strong&gt;resource hierarchy&lt;/strong&gt;. This concept of "permission inheritance via resource hierarchy" is actually quite close to ReBAC principles.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Role Explosion — The Limits of RBAC
&lt;/h2&gt;

&lt;h3&gt;
  
  
  2.1 What is Role Explosion?
&lt;/h3&gt;

&lt;p&gt;RBAC works well at a small scale. However, as an organization or system grows, &lt;strong&gt;the number of roles increases exponentially&lt;/strong&gt;. This is Role Explosion.&lt;/p&gt;

&lt;p&gt;Let's look at five causes identified in Evolveum's documentation.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.2 Cause 1: The Cartesian Product Effect
&lt;/h3&gt;

&lt;p&gt;This is the most typical cause. When there are multiple "axes" determining access rights, the combinations multiply the number of roles.&lt;/p&gt;

&lt;p&gt;For example, assume we have these three axes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Job Function&lt;/strong&gt;: Sales, Engineering, HR (3 types)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Location&lt;/strong&gt;: Tokyo, Osaka, Fukuoka (3 locations)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project&lt;/strong&gt;: Project A, B, C (3 projects)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;3 × 3 × 3 = &lt;strong&gt;27 roles&lt;/strong&gt;. Just three axes yield 27 roles.&lt;/p&gt;

&lt;p&gt;In a real enterprise: 50 Job Functions × 20 Locations × 10 Projects = &lt;strong&gt;10,000 roles&lt;/strong&gt;. Managing 10,000 roles like "Sales-Tokyo-ProjA", "Sales-Tokyo-ProjB", "Sales-Osaka-ProjA"... is an administrative nightmare.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.3 Cause 2: Atomization
&lt;/h3&gt;

&lt;p&gt;In an attempt to make roles reusable, they are broken down into granular pieces. This creates a massive amount of fine-grained shared component roles like "Basic Access," "Email Usage," or "VPN Access." The total number of roles to manage actually increases.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.4 Cause 3: The "One Role Per User" Problem
&lt;/h3&gt;

&lt;p&gt;As cases of "this person needs special access" pile up, you end up with effectively as many roles as there are users. The very purpose of using roles is defeated.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.5 Causes 4 &amp;amp; 5: Lack of Policy and Lifecycle Management
&lt;/h3&gt;

&lt;p&gt;There are no criteria for creating roles, and obsolete roles are never deleted. As Evolveum points out: "&lt;strong&gt;Roles are created as needed, but rarely deleted.&lt;/strong&gt;"&lt;/p&gt;

&lt;h3&gt;
  
  
  2.6 The Core of Role Explosion
&lt;/h3&gt;

&lt;p&gt;Ultimately, the root of role explosion is &lt;strong&gt;the limitation of RBAC's expressive power&lt;/strong&gt;. RBAC can only express "&lt;strong&gt;Who&lt;/strong&gt; → Role → &lt;strong&gt;Can do what&lt;/strong&gt;". But in reality, questions like these arise:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;When?&lt;/strong&gt; — I only want to allow access during business hours.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;From where?&lt;/strong&gt; — I only want to allow access from the corporate network.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Whose resources?&lt;/strong&gt; — Users should only be able to edit resources they own.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In what relationship?&lt;/strong&gt; — Contents of a folder should inherit the parent folder's permissions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you try to express these in RBAC, you have to create a new role for every condition. That is why roles explode.&lt;/p&gt;

&lt;p&gt;ABAC and ReBAC break through this limitation.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. ABAC — Attribute-Based Access Control
&lt;/h2&gt;

&lt;h3&gt;
  
  
  3.1 The Basic Structure of ABAC
&lt;/h3&gt;

&lt;p&gt;ABAC (Attribute-Based Access Control) solves RBAC's inability to answer "When? From where?". Instead of roles, access decisions are based on &lt;strong&gt;Attributes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The attributes used for decisions fall into four categories.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8v4owm2bh2adancpwtto.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8v4owm2bh2adancpwtto.png" alt="ABAC" width="800" height="217"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Do you see the difference from RBAC? Actually, a "role" can be treated as just another attribute in ABAC (like &lt;code&gt;role == "Manager"&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;The fundamental difference is that &lt;strong&gt;while RBAC requires static pre-classification ("assigning a role beforehand"), ABAC evaluates dynamically based on the facts (attributes) available at that exact moment.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of creating a role for "Sales Manager in the Tokyo Office", you write a policy: &lt;code&gt;department == "Sales" AND location == "Tokyo" AND role == "Manager"&lt;/code&gt;. If a new branch opens or an external contractor temporarily joins, the policy remains unchanged; only the attribute values change.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.2 XACML Architecture — The Reference Model for ABAC
&lt;/h3&gt;

&lt;p&gt;When implementing ABAC, there is an unavoidable architectural concept to understand. It comes from XACML (eXtensible Access Control Markup Language). While XACML itself is XML-based and rarely used today, its core concepts of the &lt;strong&gt;PEP / PDP / PAP / PIP&lt;/strong&gt; components live on in engines like OPA and Cedar.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa1p9mkun8kyp883yishd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa1p9mkun8kyp883yishd.png" alt="XACML" width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Modern Implementation Examples&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PEP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Intercepts the request and enforces the PDP's decision.&lt;/td&gt;
&lt;td&gt;API Gateway, Middleware&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PDP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Evaluates the policy and makes the Permit/Deny decision.&lt;/td&gt;
&lt;td&gt;OPA, Cedar, AWS Verified Permissions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PAP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Creates and manages policies.&lt;/td&gt;
&lt;td&gt;Admin UI, Git Repository&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PIP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fetches necessary attributes at runtime.&lt;/td&gt;
&lt;td&gt;LDAP, Database, External APIs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  3.3 RBAC vs ABAC — Eliminating Role Explosion
&lt;/h3&gt;

&lt;p&gt;Let's solve the "50 Job Functions × 20 Locations × 10 Projects = 10,000 roles" problem using ABAC.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With RBAC&lt;/strong&gt;: 10,000 roles are required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With ABAC&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Policy 1:
  IF subject.department == resource.allowed_department
  AND subject.location == resource.allowed_location
  AND subject.project IN resource.allowed_projects
  THEN permit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;It takes exactly one policy.&lt;/strong&gt; Where RBAC needed 10,000 roles, ABAC handles it with a single policy. If new locations or projects are added, the policy does not need to change.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.4 ABAC in Real Products
&lt;/h3&gt;

&lt;h4&gt;
  
  
  AWS IAM Conditions (Tag-Based ABAC)
&lt;/h4&gt;

&lt;p&gt;AWS IAM implements ABAC through the &lt;code&gt;Condition&lt;/code&gt; block in policies. Using tags as attributes is the typical pattern.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ec2:StartInstances"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ec2:StopInstances"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"StringEquals"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"ec2:ResourceTag/Department"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${aws:PrincipalTag/Department}"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This policy reads: "&lt;strong&gt;Allow starting and stopping EC2 instances if the resource's Department tag matches the user's Department tag.&lt;/strong&gt;" Sales personnel can only operate Sales instances. Developers can only operate Developer instances. You only need one policy.&lt;/p&gt;

&lt;p&gt;Even the official AWS documentation explicitly states: "&lt;strong&gt;ABAC requires fewer policies.&lt;/strong&gt;"&lt;/p&gt;

&lt;h4&gt;
  
  
  OPA / Rego
&lt;/h4&gt;

&lt;p&gt;OPA (Open Policy Agent) is a CNCF Graduated project and policy engine (v1.14 as of March 2026). You write policies in a declarative language called Rego.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rego"&gt;&lt;code&gt;&lt;span class="ow"&gt;package&lt;/span&gt; &lt;span class="n"&gt;httpapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;authz&lt;/span&gt;

&lt;span class="ow"&gt;default&lt;/span&gt; &lt;span class="n"&gt;allow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

&lt;span class="c1"&gt;# Users can read their own resources&lt;/span&gt;
&lt;span class="n"&gt;allow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"GET"&lt;/span&gt;
    &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"resources"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resource_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;resource_id&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;owner&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Managers can read their subordinates' resources&lt;/span&gt;
&lt;span class="n"&gt;allow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"GET"&lt;/span&gt;
    &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"resources"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resource_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;resource_id&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;owner&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;subordinate&lt;/span&gt;
    &lt;span class="n"&gt;subordinate&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reports_to&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# The admin role can do anything&lt;/span&gt;
&lt;span class="n"&gt;allow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_attributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"admin"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OPA allows you to freely mix RBAC-like evaluations (&lt;code&gt;role == "admin"&lt;/code&gt;) and ABAC-like evaluations (&lt;code&gt;owner == user&lt;/code&gt;) within policies. It is highly flexible, but because you can do almost anything, it can become chaotic if the team doesn't standardize how policies are written.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[!TIP]&lt;br&gt;
When adopting OPA in production, structural best practices are essential. For example, using a common &lt;code&gt;input&lt;/code&gt; JSON schema (&lt;code&gt;user&lt;/code&gt;, &lt;code&gt;method&lt;/code&gt;, &lt;code&gt;path&lt;/code&gt;, &lt;code&gt;resource&lt;/code&gt;, etc.) across all endpoints, and separating base common packages from microservice-specific packages using directory structures.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  3.5 Challenges of ABAC
&lt;/h3&gt;

&lt;p&gt;ABAC solves role explosion, but introduces new challenges.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Challenge&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Policy Complexity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;As the combinations of attributes grow, the policies themselves become complex.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Auditing Difficulty&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Answering "Can User X access Resource Y?" requires runtime attribute values.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Attribute Reliability (PIP)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;The biggest weakness.&lt;/strong&gt; If the PIP goes down or returns false data, the control system collapses.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Debugging Difficulty&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;It is difficult to trace exactly &lt;em&gt;why&lt;/em&gt; a request was permitted or denied.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Particularly, &lt;strong&gt;reverse lookup queries like "Who has access to this resource?" are extremely difficult&lt;/strong&gt;, which is a major weakness. In RBAC, you just list "users with this role." In ABAC, you would have to re-evaluate the attributes of every single user. You also must account for network latency to the microservice (PIP) fetching user info from the API Gateway (PEP).&lt;/p&gt;




&lt;h2&gt;
  
  
  4. ReBAC — Relationship-Based Access Control
&lt;/h2&gt;

&lt;h3&gt;
  
  
  4.1 Understanding ReBAC through Google Drive
&lt;/h3&gt;

&lt;p&gt;While ABAC solved role explosion, it struggles with &lt;strong&gt;chains of relationships&lt;/strong&gt;, such as "files within a folder should have the same permissions as the folder." Enter ReBAC (Relationship-Based Access Control), a model that determines access based on the &lt;strong&gt;relationships between objects&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The easiest way to understand this is by thinking of Google Drive.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2uhixzk6fkmhnkapvjl9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2uhixzk6fkmhnkapvjl9.png" alt="ReBAC" width="800" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Breaking down what is happening here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Group Membership&lt;/strong&gt;: Tanaka, Sato, and Suzuki are members of &lt;code&gt;engineering-team&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Folder Permission&lt;/strong&gt;: &lt;code&gt;engineering-team&lt;/code&gt; is a viewer of the &lt;code&gt;Engineering&lt;/code&gt; folder.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Permission Inheritance&lt;/strong&gt;: Documents inside the &lt;code&gt;Engineering&lt;/code&gt; folder inherit viewer permissions from the folder's viewers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Individual Addition&lt;/strong&gt;: Tanaka has been individually added as a viewer to the &lt;code&gt;Meeting Notes&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is neither RBAC nor ABAC. "I can see it because it's in the folder" and "I can see it because I'm in the group"—access rights are derived by traversing &lt;strong&gt;chains of relationships&lt;/strong&gt;. This is ReBAC.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.2 ReBAC Data Model — The Relation Tuple
&lt;/h3&gt;

&lt;p&gt;At the core of ReBAC is a simple data model called the &lt;strong&gt;Relation Tuple&lt;/strong&gt;, a format popularized by Google Zanzibar.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;object#relation@user
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It simply states: "&lt;strong&gt;A specific user (or user set)&lt;/strong&gt; belongs to &lt;strong&gt;a specific relationship&lt;/strong&gt; of &lt;strong&gt;a specific object&lt;/strong&gt;."&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tuple&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;folder:eng#viewer@group:engineering#member&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Members of the &lt;code&gt;engineering&lt;/code&gt; group are viewers of &lt;code&gt;folder:eng&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;doc:design#parent@folder:eng&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The design doc is a child (belongs to parent) of &lt;code&gt;folder:eng&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;group:engineering#member@user:tanaka&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tanaka is a member of the &lt;code&gt;engineering&lt;/code&gt; group.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;doc:minutes#viewer@user:tanaka&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tanaka is a viewer of the minutes doc (individually added).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  4.3 Permission Derivation — Graph Traversal
&lt;/h3&gt;

&lt;p&gt;The most defining characteristic of ReBAC is that &lt;strong&gt;permissions are derived through graph traversal&lt;/strong&gt;. Let's look at the process to answer "Can Tanaka view the design doc?".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqyk0c5xijuh313w0vf38.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqyk0c5xijuh313w0vf38.png" alt="Permission Derivation" width="576" height="1015"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The key point here is that &lt;strong&gt;the policy contains a defined rule stating "viewers of a folder are also viewers of the documents inside."&lt;/strong&gt; Based on this rule, the engine traverses the graph to ultimately derive the permission.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.4 Problems Solved by ReBAC
&lt;/h3&gt;

&lt;p&gt;ReBAC naturally solves the problems that RBAC and ABAC struggle with.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Problem&lt;/th&gt;
&lt;th&gt;RBAC&lt;/th&gt;
&lt;th&gt;ABAC&lt;/th&gt;
&lt;th&gt;ReBAC&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Hierarchical Permission Inheritance&lt;/strong&gt;&lt;br&gt;Folder → File&lt;/td&gt;
&lt;td&gt;Impossible (Flat structure)&lt;/td&gt;
&lt;td&gt;Difficult (Hard to express hierarchy via attr)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Naturally Expressive&lt;/strong&gt;&lt;br&gt;Traverses &lt;code&gt;parent&lt;/code&gt; relations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Reverse Lookups&lt;/strong&gt;&lt;br&gt;"Who can access this resource?"&lt;/td&gt;
&lt;td&gt;Handled via role lists&lt;/td&gt;
&lt;td&gt;Extremely difficult&lt;br&gt;Requires full re-eval&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Naturally Supported&lt;/strong&gt;&lt;br&gt;Reverse graph traversal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Sharing / Collaboration&lt;/strong&gt;&lt;br&gt;"Share this doc with Alice"&lt;/td&gt;
&lt;td&gt;Requires creating a new role&lt;/td&gt;
&lt;td&gt;Requires assigning attributes&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Just add 1 tuple&lt;/strong&gt;&lt;br&gt;&lt;code&gt;doc:X#viewer@user:Alice&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Adapting to Org Changes&lt;/strong&gt;&lt;br&gt;Team A moves under Team B&lt;/td&gt;
&lt;td&gt;Reassign roles entirely&lt;/td&gt;
&lt;td&gt;Bulk attribute updates&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Update 1 relation&lt;/strong&gt;&lt;br&gt;Repoint the &lt;code&gt;parent&lt;/code&gt; relation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  4.5 Google Zanzibar — Planetary-Scale ReBAC
&lt;/h3&gt;

&lt;p&gt;Google Zanzibar is a system that implements ReBAC at a planetary scale. A paper on it was published at USENIX ATC in 2019, revealing it manages over 2 trillion Relation Tuples and processes 10 million requests per second with a p95 latency of less than 10ms.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]&lt;br&gt;
The biggest hurdle when implementing ReBAC in distributed systems is consistency dilemmas like the &lt;strong&gt;"New Enemy Problem."&lt;/strong&gt; If a request to "access a resource" arrives before the information that "permissions were revoked" propagates, the revoked user might gain access. Zanzibar solves this using consistency tokens called &lt;strong&gt;Zookies&lt;/strong&gt; (similar to timestamped Cookies).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I will do a deep dive into Zanzibar's architecture another time.&lt;/p&gt;

&lt;p&gt;From here on, we will look at practical policy examples from OSS implementations heavily influenced by Zanzibar's design—SpiceDB and OpenFGA. By 2025–2026, both have significantly matured. The concept of Zookies has also been implemented in both as SpiceDB's &lt;code&gt;ZedToken&lt;/code&gt; and OpenFGA's &lt;code&gt;Consistency&lt;/code&gt; options.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. ReBAC in Real Products
&lt;/h2&gt;

&lt;h3&gt;
  
  
  5.1 SpiceDB (Authzed)
&lt;/h3&gt;

&lt;p&gt;SpiceDB (by Authzed) is the OSS implementation most faithful to Zanzibar's design. As of March 2026, it has reached v1.50, and recent additions focus on the AI agent era, such as LangChain integration (&lt;code&gt;langchain-spicedb&lt;/code&gt;) and Postgres FDW-based SQL querying.&lt;/p&gt;

&lt;p&gt;Models are defined using a schema language called &lt;code&gt;.zed&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;definition user {}

definition group {
    relation member: user | group#member
}

definition folder {
    relation parent: folder
    relation viewer: user | group#member
    relation editor: user | group#member

    permission view = viewer + editor + parent-&amp;gt;view
    permission edit = editor + parent-&amp;gt;edit
}

definition document {
    relation parent_folder: folder
    relation owner: user
    relation viewer: user | group#member
    relation editor: user | group#member

    permission view = viewer + editor + owner + parent_folder-&amp;gt;view
    permission edit = editor + owner + parent_folder-&amp;gt;edit
    permission delete = owner
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's organize how to read this schema:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Syntax&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;relation X: user&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Users can be assigned to relationship X.&lt;/td&gt;
&lt;td&gt;&lt;code&gt;relation owner: user&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;permission Y = A + B&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Y is the union (OR) of A and B.&lt;/td&gt;
&lt;td&gt;&lt;code&gt;view = viewer + editor&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;parent_folder-&amp;gt;view&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Traverse the &lt;code&gt;parent_folder&lt;/code&gt; relation and grab its &lt;code&gt;view&lt;/code&gt;.&lt;/td&gt;
&lt;td&gt;Inherit the folder's view perm.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;A &amp;amp; B&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Intersection (AND).&lt;/td&gt;
&lt;td&gt;Requires both relations.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;A - B&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Exclusion (Subtract).&lt;/td&gt;
&lt;td&gt;Subtract B from A.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  SpiceDB Caveats (Conditional Relations)
&lt;/h4&gt;

&lt;p&gt;SpiceDB supports &lt;strong&gt;Caveats&lt;/strong&gt;, combining ReBAC with ABAC-style conditions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;caveat ip_allowlist(user_ip ipaddress, cidr string) {
    user_ip.in_cidr(cidr)
}

definition document {
    relation viewer: user with ip_allowlist
    permission view = viewer
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows expressing conditional relationships like "Is a viewer, but only accessible from the internal corporate network." You can evaluate the ReBAC relationship graph and the ABAC attribute checks simultaneously.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.2 OpenFGA (Auth0 / Okta)
&lt;/h3&gt;

&lt;p&gt;OpenFGA is a Zanzibar-based OSS developed by Auth0 (Okta) and was promoted to a &lt;strong&gt;CNCF Incubating project in October 2025&lt;/strong&gt;. As of March 2026, it is at v1.13. Contributors have grown by 49% year-over-year to over 6,200, showing strong momentum. Models are defined via DSL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;model
  schema 1.1

type user

type group
  relations
    define member: [user, group#member]

type folder
  relations
    define owner: [user]
    define viewer: [user, group#member]
    define parent: [folder]
    define can_view: viewer or owner or can_view from parent

type document
  relations
    define owner: [user]
    define editor: [user, group#member]
    define viewer: [user, group#member]
    define parent: [folder]
    define can_view: viewer or editor or owner or can_view from parent
    define can_edit: editor or owner
    define can_delete: owner
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Summarizing the notational differences from SpiceDB:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;SpiceDB&lt;/th&gt;
&lt;th&gt;OpenFGA&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Union&lt;/td&gt;
&lt;td&gt;&lt;code&gt;A + B&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;A or B&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Graph Traversal&lt;/td&gt;
&lt;td&gt;&lt;code&gt;parent-&amp;gt;view&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;can_view from parent&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Type Constraints&lt;/td&gt;
&lt;td&gt;`relation X: user \&lt;/td&gt;
&lt;td&gt;group#member`&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Intersection&lt;/td&gt;
&lt;td&gt;&lt;code&gt;A &amp;amp; B&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;A and B&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exclusion&lt;/td&gt;
&lt;td&gt;&lt;code&gt;A - B&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;A but not B&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In terms of expressive power, SpiceDB has an edge, allowing complex models using intersection and exclusion. OpenFGA focuses on unions (&lt;code&gt;or&lt;/code&gt;), prioritizing simplicity. The choice depends on your team's specific requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.3 Writing and Checking Relationship Tuples
&lt;/h3&gt;

&lt;p&gt;Writing tuples and hitting the Check API follows a similar pattern in both engines.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7jud6mcrlwxl4yx4xxkx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7jud6mcrlwxl4yx4xxkx.png" alt="Relationship Tuple" width="800" height="490"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Cedar — Integrating RBAC, ABAC, and ReBAC
&lt;/h2&gt;

&lt;h3&gt;
  
  
  6.1 What is Cedar?
&lt;/h3&gt;

&lt;p&gt;Cedar is an open-source policy language developed by AWS (v4.9 as of March 2026). It serves as the backend for Amazon Verified Permissions. In June 2025, pricing for Verified Permissions was &lt;strong&gt;slashed by 97%&lt;/strong&gt; ($5 per million requests), drastically lowering the barrier to entry. Furthermore, in March 2026, Amazon Bedrock AgentCore Policy went GA, establishing Cedar as an &lt;strong&gt;authorization language for AI Agents&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Cedar's design philosophy is "&lt;strong&gt;Integrating RBAC, ABAC, and ReBAC into a single language.&lt;/strong&gt;"&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyttc9j0ajsc49433r4t7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyttc9j0ajsc49433r4t7.png" alt="Cedar" width="800" height="161"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  6.2 Cedar Policy Examples
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Pattern 1: Pure RBAC
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Members of the Teachers role can submit and answer problems
permit (
    principal in Role::"Teachers",
    action in [
        Action::"submitProblem",
        Action::"answerProblem"
    ],
    resource
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;principal in Role::"Teachers"&lt;/code&gt; is the role membership check. Pure RBAC.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pattern 2: Pure ABAC
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Non-confidential documents can be viewed by anyone
permit (
    principal,
    action == Action::"viewDocument",
    resource
)
when { resource.classification != "confidential" };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Checking resource attributes (&lt;code&gt;classification&lt;/code&gt;) inside the &lt;code&gt;when&lt;/code&gt; block. Pure ABAC.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pattern 3: RBAC + ABAC Hybrid
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Allow viewing data if the user is in the viewDataRole,
// is not locked out, is using MFA,
// and the resource belongs to their Tenant
permit (
    principal in Role::"viewDataRole",
    action == Action::"viewData",
    resource
)
when {
    principal.account_lockout_flag == false &amp;amp;&amp;amp;
    context.uses_mfa == true &amp;amp;&amp;amp;
    resource in principal.Tenant
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;RBAC in the scope (&lt;code&gt;in Role::"viewDataRole"&lt;/code&gt;), ABAC in &lt;code&gt;when&lt;/code&gt; (MFA checks), and ReBAC in &lt;code&gt;resource in principal.Tenant&lt;/code&gt; (traversing the tenant relationship)—three models integrated into a single policy.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pattern 4: ReBAC — Resource Owner
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Owners can view the document
permit (
    principal,
    action == Action::"viewDocument",
    resource
)
when { principal == resource.owner };

// Users in the document's viewACL can view it,
// excluding private documents
permit (
    principal,
    action == Action::"viewDocument",
    resource
)
when { principal in resource.viewACL }
unless { resource.isPrivate };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;principal in resource.viewACL&lt;/code&gt; traverses the entity graph to see if the user is in the document's Access Control List. This is the ReBAC portion.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.3 Cedar's Design Principles
&lt;/h3&gt;

&lt;p&gt;Cedar has three core design principles.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Principle&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Default Deny&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;All access is denied unless explicitly permitted by a &lt;code&gt;permit&lt;/code&gt; policy.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Forbid overrides Permit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;If even a single &lt;code&gt;forbid&lt;/code&gt; policy matches, access is denied, regardless of how many &lt;code&gt;permit&lt;/code&gt; policies match.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Formal Verification&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The ability to answer questions like "Can the admin role access HR data?" via static analysis of the policies.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Formal verification&lt;/strong&gt; is Cedar's unique strength. You can take a theorem (a property you want to prove), such as "Is there any path where an external user can view a document where &lt;code&gt;isPrivate == true&lt;/code&gt;?", and run it through an SMT solver (Automated Theorem Prover). Being able to mathematically prove that unintended access cannot occur before deploying policies is a feature that OPA and SpiceDB do not natively have.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Comparison of the 3 Models
&lt;/h2&gt;

&lt;h3&gt;
  
  
  7.1 Selection Criteria
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9e48taprqq288apq9mek.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9e48taprqq288apq9mek.png" alt="Selection Criteria" width="800" height="1349"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  7.2 Detailed Comparison
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;RBAC&lt;/th&gt;
&lt;th&gt;ABAC&lt;/th&gt;
&lt;th&gt;ReBAC&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Decision Basis&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Roles&lt;/td&gt;
&lt;td&gt;Attributes (Subject, Resource, Env)&lt;/td&gt;
&lt;td&gt;Relationships between objects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Granularity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Coarse (Role level)&lt;/td&gt;
&lt;td&gt;Fine (Combinations of attributes)&lt;/td&gt;
&lt;td&gt;Medium to Fine (Chains of relations)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Suitable Use Cases&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Internal tools, simple IAM&lt;/td&gt;
&lt;td&gt;Compliance, multi-attribute decisions&lt;/td&gt;
&lt;td&gt;SaaS, document sharing, hierarchies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Implementation Complexity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Medium to High&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Performance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fast (Simple lookups)&lt;/td&gt;
&lt;td&gt;Medium (Fetching/evaluating attributes)&lt;/td&gt;
&lt;td&gt;Watch out (Depends on graph depth)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scalability Risks&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Role Explosion&lt;/td&gt;
&lt;td&gt;Policy Complexity&lt;/td&gt;
&lt;td&gt;Massive graph size / Recursion depth&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Reverse Lookup&lt;/strong&gt;&lt;br&gt;"Who has access?"&lt;/td&gt;
&lt;td&gt;Easy&lt;/td&gt;
&lt;td&gt;Extremely difficult&lt;/td&gt;
&lt;td&gt;Supported (Graph Traversal)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Context Control&lt;/strong&gt;&lt;br&gt;"Time/Location limits"&lt;/td&gt;
&lt;td&gt;Impossible&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Limited natively (Extendable via Caveats)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Auditability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Easy&lt;/td&gt;
&lt;td&gt;Difficult&lt;/td&gt;
&lt;td&gt;Medium (Long chains are hard to trace)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Representative Implementations&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Kubernetes RBAC, Azure RBAC&lt;/td&gt;
&lt;td&gt;OPA, AWS IAM Conditions&lt;/td&gt;
&lt;td&gt;SpiceDB, OpenFGA, Ory Keto&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  7.3 Real-World Systems are Hybrids
&lt;/h3&gt;

&lt;p&gt;In the real world of authorization systems, very few rely purely on a single model.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;RBAC&lt;/th&gt;
&lt;th&gt;ABAC&lt;/th&gt;
&lt;th&gt;ReBAC&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AWS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;IAM Roles / Policies&lt;/td&gt;
&lt;td&gt;IAM Conditions / Tags&lt;/td&gt;
&lt;td&gt;Verified Permissions (Cedar)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Google&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Cloud IAM Roles&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Zanzibar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Typical SaaS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Admin / Member / Viewer&lt;/td&gt;
&lt;td&gt;IP Allowlist, MFA Req&lt;/td&gt;
&lt;td&gt;Folder sharing, Workspaces&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Almost everyone layers 2 or 3 models. So, what is the practical way to stack them?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;RBAC as the baseline&lt;/strong&gt; — Manage broad roles like Admin / Member / Viewer via RBAC. It's simple and universally understood.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add ABAC for contextual control&lt;/strong&gt; — Handle IP restrictions, MFA requirements, and business hour limitations via ABAC.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Introduce ReBAC for resource sharing and hierarchies&lt;/strong&gt; — Use ReBAC for folder permission inheritance and document sharing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You don't need to adopt ReBAC from day one. Starting with RBAC, and bringing in ABAC or ReBAC when you see signs of role explosion or complex sharing requirements, is the most practical path.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Industry Trends for 2025–2026
&lt;/h2&gt;

&lt;p&gt;As of March 2026, the authorization space is moving rapidly. Here are the key trends to watch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI Agent authorization has become the largest theme.&lt;/strong&gt; Cedar was adopted as the authorization language for AI agents in Amazon Bedrock AgentCore Policy. SpiceDB announced its LangChain integration. Oso released risk control features specifically for "Coding Agents." Cerbos is partnering with Tailscale to provide AI agent authorization. Nearly every player is accelerating support for AI agents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Market consolidation is accelerating.&lt;/strong&gt; CrowdStrike acquired SGNL for $740M (January 2026), and FusionAuth acquired Permify (November 2025). Authorization is transitioning from a standalone category to an integrated part of broader security platforms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Standardization has matured.&lt;/strong&gt; The OpenID AuthZEN Authorization API 1.0 was approved as a Final Specification in January 2026. With an industry standard for the communication protocol between PDP and PEP, swapping authorization engines or integrating multiple systems is becoming significantly easier.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you want to achieve&lt;/th&gt;
&lt;th&gt;Model to Choose&lt;/th&gt;
&lt;th&gt;Products to Consider&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Simple role management&lt;/td&gt;
&lt;td&gt;RBAC&lt;/td&gt;
&lt;td&gt;Kubernetes RBAC, AWS IAM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fine-grained control via attributes &amp;amp; context&lt;/td&gt;
&lt;td&gt;ABAC&lt;/td&gt;
&lt;td&gt;OPA (v1.14), AWS IAM Conditions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Document sharing, hierarchical permissions&lt;/td&gt;
&lt;td&gt;ReBAC&lt;/td&gt;
&lt;td&gt;SpiceDB (v1.50), OpenFGA (v1.13, CNCF Incubating)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The whole package (RBAC + ABAC + ReBAC)&lt;/td&gt;
&lt;td&gt;Hybrid&lt;/td&gt;
&lt;td&gt;Cedar v4 (Amazon Verified Permissions)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Asking "which model is the strongest" is meaningless. You choose based on "what you want to control." Start with RBAC, add ABAC when roles multiply, and pull in ReBAC when you face resource sharing and hierarchies. You don't need to build the "everything" solution from the start.&lt;/p&gt;

</description>
      <category>authorization</category>
      <category>security</category>
      <category>architecture</category>
      <category>iam</category>
    </item>
    <item>
      <title>WIMSE (Workload Identity in Multi System Environments) Deep Dive: Standardizing Identity Authentication for Microservices</title>
      <dc:creator>kt</dc:creator>
      <pubDate>Tue, 24 Mar 2026 16:57:16 +0000</pubDate>
      <link>https://dev.to/kanywst/wimse-workload-identity-in-multi-system-environments-deep-dive-standardizing-identity-5a12</link>
      <guid>https://dev.to/kanywst/wimse-workload-identity-in-multi-system-environments-deep-dive-standardizing-identity-5a12</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;Modern systems are wild. A user clicks a single button, and behind the scenes, ten or twenty independent workloads — microservices, containers, functions — fire off in sequence.&lt;/p&gt;

&lt;p&gt;Take an e-commerce site. When someone hits "Place Order," here's what actually happens:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The API gateway picks up the incoming request&lt;/li&gt;
&lt;li&gt;The order service calls the inventory service to check stock&lt;/li&gt;
&lt;li&gt;The inventory service hits the financial system for a risk assessment before payment&lt;/li&gt;
&lt;li&gt;Once risk assessment clears, the payment service kicks in&lt;/li&gt;
&lt;li&gt;After payment goes through, the notification service fires off a confirmation email&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now here's the real question.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"What happens if you keep passing the user's original auth credentials through this entire request chain?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdky3hqgkl6b45l7xqxqn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdky3hqgkl6b45l7xqxqn.png" alt="Dangerous pattern" width="403" height="783"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The problems stack up fast:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;An external token is flowing across the entire internal network&lt;/strong&gt;: If that token gets stolen, every internal service is exposed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You lose track of who actually initiated the request&lt;/strong&gt;: Intermediate services can't tell whether a request genuinely came from a user or if someone is hitting them directly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Any service can impersonate another&lt;/strong&gt;: If the internal network gets compromised, a malicious workload can pretend to be a legitimate request and call other services&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token theft has a blast radius&lt;/strong&gt;: If the external token leaks, it opens up unauthorized access to external resources too&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is exactly the kind of "inter-workload authentication" problem that the IETF is tackling with &lt;strong&gt;WIMSE (Workload Identity in Multi System Environments)&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is WIMSE?
&lt;/h2&gt;

&lt;p&gt;In one sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;WIMSE is a standardized architecture for how workloads — microservices, containers, and the like — authenticate each other and propagate security context.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's defined in IETF Internet-Draft &lt;code&gt;draft-ietf-wimse-arch-07&lt;/code&gt; (March 2026 edition). The authors are security engineers from CyberArk, Zscaler, and Hochschule Bonn-Rhein-Sieg.&lt;/p&gt;

&lt;p&gt;What WIMSE is trying to standardize:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;How workloads authenticate each other&lt;/strong&gt;: X.509 certificates? JWTs? What format?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How security context gets propagated&lt;/strong&gt;: How do you pass user info, authorization info, and audit data to downstream services?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-domain communication&lt;/strong&gt;: When you need to talk to another organization's systems, how do you verify a workload is trustworthy?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Basically, it's an effort to take the scattered, bespoke implementations across different companies and projects, and unify them under one standard.&lt;/p&gt;




&lt;h2&gt;
  
  
  Three Core Concepts of WIMSE
&lt;/h2&gt;

&lt;p&gt;The WIMSE architecture is built on three pillars.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Trust Domain
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;A trust domain is a group of systems that share common security policies and controls.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Think of it as "all the workloads within a given organization's environment." For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All microservices in Company A's production environment = one Trust Domain&lt;/li&gt;
&lt;li&gt;All containers in Company B's dev environment = a different Trust Domain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4iscgdo7q9r2s57smumq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4iscgdo7q9r2s57smumq.png" alt="Trust domain" width="800" height="307"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Trust Domains are typically &lt;strong&gt;identified by a fully qualified domain name (FQDN)&lt;/strong&gt; — like &lt;code&gt;a.example.com&lt;/code&gt; or &lt;code&gt;prod-us-west.mycompany.com&lt;/code&gt;. This makes them globally unique.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Workload Identifier
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;A workload identifier is an ID that uniquely identifies a single workload within a Trust Domain.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It takes the form of a URI and includes the Trust Domain. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spiffe://a.example.com/paymentService
spiffe://a.example.com/orderService/v2
spiffe://prod-us-west.mycompany.com/ml-training-job-001
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These often follow SPIFFE (Secure Production Identity Framework for Everyone), a spec that already has widespread adoption.&lt;/p&gt;

&lt;p&gt;Key points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The same identifier value issued under &lt;strong&gt;different Trust Domains is a completely different workload&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;An identifier can refer to a "logical workload" or a "specific workload instance" (like this particular container boot)&lt;/li&gt;
&lt;li&gt;Identifiers &lt;strong&gt;must remain stable throughout the workload's lifetime&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Workload Identity Credentials
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Workload identity credentials are what a workload uses to prove "I am who I say I am."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are two main formats:&lt;/p&gt;

&lt;h4&gt;
  
  
  X.509 Certificate
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-----BEGIN CERTIFICATE-----
MIIDpzCCApugAwIBAgIBATANBgkqhkiG9w0BAQsFADAz...
...
-----END CERTIFICATE-----
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same certificate format used in TLS. &lt;strong&gt;When you're doing Mutual TLS (mTLS), this is what gets used.&lt;/strong&gt; The client-side workload presents its certificate, and the server-side verifies it.&lt;/p&gt;

&lt;p&gt;Strengths:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Already supported by a huge number of systems&lt;/li&gt;
&lt;li&gt;Authentication completes at the TLS layer&lt;/li&gt;
&lt;li&gt;Strong cryptographic binding to the key&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  JWT / Workload Identity Token
&lt;/h4&gt;

&lt;p&gt;Here's what a JWT SVID (Workload Identity Token) payload looks like — similar to what gets issued by something like SPIRE:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"iss"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://a.example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"spiffe://a.example.com/orderService"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1710432000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"iat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1710428400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"aud"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"spiffe://a.example.com/paymentService"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an application-layer token. It usually travels in HTTP headers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Authorization: Bearer eyJhbGc...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Strengths:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Easy to integrate when HTTP is your baseline&lt;/li&gt;
&lt;li&gt;You can pack arbitrary attributes into the token&lt;/li&gt;
&lt;li&gt;Great fit for microservices and API gateways&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Important: These Are Not Bearer Tokens
&lt;/h2&gt;

&lt;p&gt;WIMSE authentication tokens are &lt;strong&gt;not bearer tokens&lt;/strong&gt;. A bearer token means "whoever holds this token can use it." WIMSE tokens are different — &lt;strong&gt;you need to prove you possess the private key that corresponds to the token's public component&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;With mTLS, for instance, the private key is used to create a signature during the TLS handshake. With JWTs, the WIMSE Workload Proof Token (WPT) mechanism has the workload sign the request message with its private key, proving possession.&lt;/p&gt;

&lt;p&gt;In other words:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stolen token ≠ attacker can use it immediately

Private key is also required → proof of key possession is mandatory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is one of WIMSE's real strengths.&lt;/p&gt;




&lt;h2&gt;
  
  
  WIMSE Scenarios: How It Works in Practice
&lt;/h2&gt;

&lt;p&gt;Let's look at how WIMSE actually functions in a real microservice environment across a few scenarios.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario 1: Basic Workload Identity System
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flqq5xk9gus4mnyv97o8s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flqq5xk9gus4mnyv97o8s.png" alt="Basic Workload Identity System" width="800" height="468"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's what's happening at each step:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(1) Distributing workload identity credentials&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When a workload boots up, it first obtains its identity credential from the &lt;strong&gt;CA / Certificate Authority&lt;/strong&gt;. This isn't a one-time thing — credentials get automatically renewed before they expire (auto rotation).&lt;/p&gt;

&lt;p&gt;Short-lived credentials (say, valid for one hour) are used deliberately. If one gets stolen, the damage window is tiny.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(2) Request from the external client&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A standard HTTPS request arrives from a user or application. The gateway handles it with normal Server Transport Authentication (STA).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(3) Gateway to workload&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When the gateway forwards the request to Workload 1, it uses &lt;strong&gt;mTLS (Mutual TLS)&lt;/strong&gt;. The gateway presents its own identity credential to prove "I'm the gateway," and Workload 1 verifies the gateway in return.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(4) Workload-to-workload communication&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Workload 1 needs something from Workload 3, so it makes a call. Again, Workload 1 uses its own identity credential to connect. Workload 3 verifies that credential — confirms "this is coming from Workload 1" — and only then processes the request.&lt;/p&gt;




&lt;h3&gt;
  
  
  Scenario 2: Security Context Propagation
&lt;/h3&gt;

&lt;p&gt;So far we've only looked at &lt;strong&gt;authentication&lt;/strong&gt; — workloads confirming each other's identities. But in the real world, you also need to pass along:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;User info&lt;/strong&gt;: "Who originally made this request?"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authorization info&lt;/strong&gt;: "What is this user allowed to do?"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit info&lt;/strong&gt;: "When did what happen?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is called &lt;strong&gt;security context&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcrhu2rpk67sjk8f5ilso.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcrhu2rpk67sjk8f5ilso.png" alt="Security Context Propagation" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The important bits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;External token (a) stops at the gateway&lt;/strong&gt;: It never enters the internal network&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An internal context (c) is created instead&lt;/strong&gt;: A short-lived internal token containing user info and other details&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Each workload checks (c)&lt;/strong&gt;: They use it to determine what the user is allowed to do&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A common pattern here is for the gateway to receive an OAuth 2.0 access token and convert it into an internal &lt;strong&gt;Transaction Token&lt;/strong&gt;. I want to do a separate deep dive on Transaction Tokens, but the gist is: think of it as "cryptographic proof of authorization context for this request chain."&lt;/p&gt;




&lt;h3&gt;
  
  
  Scenario 3: Cross-Domain Communication
&lt;/h3&gt;

&lt;p&gt;Scenarios 1 and 2 were both within a single Trust Domain. But in practice, you often need to talk to systems in different organizations or environments. How does that work?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsuj7x52gl0in6fqjtbjt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsuj7x52gl0in6fqjtbjt.png" alt="Cross-Domain Communication" width="800" height="264"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Walking through the flow:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(1)-(2) Normal flow within Company A&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A user request arrives, the gateway forwards it to Workload 1, passing along the context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(3) Workload 1 needs to reach an external service&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Workload 1 decides it needs to access Company B's service. But Company A's internal credentials won't cut it — it needs &lt;strong&gt;a token in a format Company B trusts&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So it asks Company A's token service for an externally-facing token, presenting &lt;strong&gt;its own identity credential (c)&lt;/strong&gt;. This lets the token service verify that Workload 1 is actually Workload 1.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(4)-(5) Cross-domain handshake&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The token service, armed with Company A's auth info, &lt;strong&gt;reaches out to Company B's external token service&lt;/strong&gt;: "Company A's Workload 1 wants to access your external service."&lt;/p&gt;

&lt;p&gt;Company B's token service evaluates whether Company A's Workload 1 can be trusted, based on policy. If it checks out, it issues an external access token (a) that Workload 1 can use within Company B.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(6) Accessing the external service&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Workload 1 uses the token from Company B to access Company B's service. From Company B's service perspective: "This token is in a format we trust — access granted."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key takeaways&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Company A's workload credential (c) never reaches Company B&lt;/li&gt;
&lt;li&gt;Instead, it gets &lt;strong&gt;converted into a token (a) that Company B understands&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Workload 1's identity is preserved while being safely propagated across domains&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;[!TIP]&lt;br&gt;
&lt;strong&gt;Fun fact: Egress Identity Generalization&lt;/strong&gt;&lt;br&gt;
When crossing trust boundaries, exposing granular instance-level IDs (like &lt;code&gt;spiffe://a.example.com/order-service/pod-123&lt;/code&gt;) to the outside would leak internal scaling and deployment details to potential attackers. That's why the token service (or gateway) is recommended to generalize the ID to something like &lt;code&gt;spiffe://a.example.com/order-service&lt;/code&gt; before sending it externally. This technique is also recommended in the RFC (&lt;code&gt;draft-07&lt;/code&gt;, section &lt;code&gt;3.3.8.1&lt;/code&gt;).&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  WIMSE Implementation Formats
&lt;/h2&gt;

&lt;p&gt;The WIMSE architecture defines &lt;em&gt;what&lt;/em&gt; authentication should look like, but &lt;strong&gt;there are multiple ways to actually implement it&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Authentication Method 1: mTLS (Transport Layer)
&lt;/h3&gt;

&lt;p&gt;Both sides present certificates during the TLS handshake.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F43heqe6fmz3xs3pjr8py.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F43heqe6fmz3xs3pjr8py.png" alt="WIMSE Implementation Formats" width="772" height="203"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Strengths:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Everything stays in the TLS layer&lt;/li&gt;
&lt;li&gt;Wide existing support across systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Weaknesses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If an intermediate proxy or load balancer terminates the TLS session, you lose the end-to-end guarantee&lt;/li&gt;
&lt;li&gt;Can get complicated in serverless or multi-tenant environments&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Authentication Method 2: HTTP Signatures (Application Layer)
&lt;/h3&gt;

&lt;p&gt;Sign the HTTP request itself. Defined in RFC 9421.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="nf"&gt;POST&lt;/span&gt; &lt;span class="nn"&gt;/api/order&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;span class="na"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;paymentservice.a.example.com&lt;/span&gt;
&lt;span class="na"&gt;Content-Digest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sha-256=:...&lt;/span&gt;
&lt;span class="na"&gt;Signature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sig1=:...:&lt;/span&gt;
&lt;span class="na"&gt;Signature-Input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sig1=(...; created=1710428400; keyid="spiffe://a.example.com/orderService"; alg="rsa-pss-sha512")&lt;/span&gt;

{"order_id": 12345}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The signature covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTP method (POST)&lt;/li&gt;
&lt;li&gt;Path (/api/order)&lt;/li&gt;
&lt;li&gt;Headers&lt;/li&gt;
&lt;li&gt;Hash of the body&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Strengths:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Signatures survive proxy hops&lt;/li&gt;
&lt;li&gt;Guarantees message integrity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Weaknesses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Higher computational cost&lt;/li&gt;
&lt;li&gt;More complex to implement than mTLS&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Authentication Method 3: WIMSE Workload Proof Token (WPT)
&lt;/h3&gt;

&lt;p&gt;A JWT-based mechanism that signs specific request information with a private key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"alg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"RS256"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"typ"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WPT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"kid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"key-id-123"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"iss"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"spiffe://a.example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"spiffe://a.example.com/orderService"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"aud"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"spiffe://a.example.com/paymentService"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"iat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1710428400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1710428700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cnf"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"txn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"base64(hash(request_context))"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;txn&lt;/code&gt; (transaction) claim contains a hash of the request's context info. This lets the receiver confirm the token was actually created for this specific request.&lt;/p&gt;

&lt;p&gt;Sent via HTTP header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Workload-Proof-Token: eyJhbGc...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Use Cases: Problems WIMSE Actually Solves
&lt;/h2&gt;

&lt;p&gt;Let's get concrete about what WIMSE fixes in the real world.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Case 1: Credential Distribution at Workload Startup
&lt;/h3&gt;

&lt;p&gt;A new container just spun up. It needs to know who it is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Without WIMSE:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Developers manually stick secret keys in config files&lt;/li&gt;
&lt;li&gt;Keys are long-lived (months, years)&lt;/li&gt;
&lt;li&gt;Risk of keys getting baked into the container image&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;With WIMSE:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;At startup, the container uses proof like a Kubernetes Projected Service Account Token&lt;/li&gt;
&lt;li&gt;Requests an identity credential from the CA&lt;/li&gt;
&lt;li&gt;Gets a short-lived certificate (minutes to hours) distributed automatically&lt;/li&gt;
&lt;li&gt;Auto-renews before expiry&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Use Case 2: Service Authentication
&lt;/h3&gt;

&lt;p&gt;When Workload A calls Workload B, B wants to confirm "is this really Workload A?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Without WIMSE:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Workload A's secret key lives in Workload B's config file&lt;/li&gt;
&lt;li&gt;If multiple workloads are calling in, B needs to hold every single one of their secrets&lt;/li&gt;
&lt;li&gt;Management becomes a nightmare&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;With WIMSE:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Workload A presents its short-lived identity credential&lt;/li&gt;
&lt;li&gt;Workload B verifies it against the Trust Domain's CA&lt;/li&gt;
&lt;li&gt;Policy check determines if the call is trusted&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Use Case 3: Rich Audit Logging
&lt;/h3&gt;

&lt;p&gt;When something goes wrong, you want complete traceability: "Which workload did what, from where?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Without WIMSE:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Logs show &lt;em&gt;something&lt;/em&gt; happened, but you can't verify whether a request was actually legitimate&lt;/li&gt;
&lt;li&gt;Hard to distinguish between stolen-credential abuse and genuine access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;With WIMSE:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every request carries "from which workload, to which workload" information&lt;/li&gt;
&lt;li&gt;Full traceability of workload-to-workload communication&lt;/li&gt;
&lt;li&gt;By following the security context propagation, you can trace exactly how far a user's request traveled&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Use Case 4: Delegation and Impersonation
&lt;/h3&gt;

&lt;p&gt;User A sends a request. Workload 1 processes it and needs to ask other workloads to "do something on behalf of User A."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Without WIMSE:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pass User A's token from Workload 1 to other workloads&lt;/li&gt;
&lt;li&gt;Other workloads process it as "User A's token"&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;But you can't trace who actually initiated the request — was it Workload 1 or something else?&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;With WIMSE:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The security context carries a history: "original user is A, currently being processed by Workloads 1 and 2"&lt;/li&gt;
&lt;li&gt;Audit logs give you the full picture: "Under User A's authority, across the Workload 1 → Workload 2 chain, here's exactly what happened"&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Use Case 5: AI Agent-to-Agent Communication
&lt;/h3&gt;

&lt;p&gt;This one was newly added in the latest spec (&lt;code&gt;draft-ietf-wimse-arch-07&lt;/code&gt;), and it's squarely aimed at the current trend.&lt;/p&gt;

&lt;p&gt;Autonomous AI agents are increasingly calling various workloads on behalf of users, and &lt;strong&gt;AI agents are delegating tasks to other AI agents, forming processing chains&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How WIMSE handles it:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI agents are treated as a special case of "delegated workloads"&lt;/li&gt;
&lt;li&gt;"Which AI agent, acting under which user's authority, is permitted to do what" gets cryptographically bound into tokens with scoped permissions&lt;/li&gt;
&lt;li&gt;This prevents the "privilege escalation" scenario where AI-to-AI communication chains accidentally accumulate massive permissions — WIMSE's framework shuts that down completely&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Security Considerations
&lt;/h2&gt;

&lt;p&gt;WIMSE is powerful, but a sloppy implementation will create vulnerabilities instead of eliminating them. Here are the critical things to get right.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Traffic Interception
&lt;/h3&gt;

&lt;p&gt;Without TLS, workload identity credentials flowing across the network can be intercepted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mitigations:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Always use TLS&lt;/strong&gt;: Even for workload-to-workload communication within a Trust Domain, TLS is non-negotiable&lt;/li&gt;
&lt;li&gt;mTLS is even better: Both sides verify each other&lt;/li&gt;
&lt;li&gt;Use short-lived tokens: Even if stolen, the usable window is tiny&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Information Disclosure
&lt;/h3&gt;

&lt;p&gt;If security context or identity credentials contain sensitive information, unauthorized workloads could access it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mitigations:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Include only the minimum necessary information&lt;/li&gt;
&lt;li&gt;Use reference pointers for sensitive data — don't send the actual data&lt;/li&gt;
&lt;li&gt;Never output identity credentials in logs or error responses&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Credential Theft
&lt;/h3&gt;

&lt;p&gt;Even short-lived credentials can be weaponized if stolen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mitigations:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Protect private keys with Hardware Security Modules (HSMs) or TPMs&lt;/li&gt;
&lt;li&gt;Isolate workloads from their private keys (separate memory pages, processes, etc.)&lt;/li&gt;
&lt;li&gt;Keep lifetimes ultra-short (minutes)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Workload Compromise
&lt;/h3&gt;

&lt;p&gt;If malicious code compromises a workload, its identity credential could be abused.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mitigations:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ensure only legitimate workloads can obtain identities&lt;/strong&gt; (Attestation)&lt;/li&gt;
&lt;li&gt;Detect tampering in workloads&lt;/li&gt;
&lt;li&gt;Policy-based access control: Fine-grained restrictions like "this workload can only call this API"&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;WIMSE is a standardized architecture for tackling workload authentication in the microservice era, head-on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The key points:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Trust Domain&lt;/strong&gt;: A group of workloads sharing common security policies. Identified by FQDN.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Workload Identifier&lt;/strong&gt;: A URI that uniquely identifies a single workload within a Trust Domain. Usually follows the SPIFFE ID format.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Workload Identity Credentials&lt;/strong&gt;: The auth material a workload uses to prove its identity. Either X.509 certificates or JWT tokens. Cryptographic binding to a private key is mandatory.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Short-lived with auto-renewal&lt;/strong&gt;: Credentials live for minutes to hours and get automatically rotated before they expire.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Security context propagation&lt;/strong&gt;: User and authorization info gets converted from external formats (OAuth tokens) into internal formats (Transaction Tokens, etc.) and flows through each workload.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cross-domain support&lt;/strong&gt;: Even when communicating with systems in different organizations or environments, tokens get converted into trusted formats for safe interoperability.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The WIMSE spec is still an IETF Internet-Draft, but implementations (SPIFFE, Kubernetes, Istio, and others) are already well underway, with adoption across many cloud-native projects.&lt;/p&gt;

&lt;p&gt;With microservice architectures being the default now, &lt;strong&gt;how you design workload-to-workload authentication&lt;/strong&gt; is a strategic decision. Understanding WIMSE gives you a clearer picture of how to build microservice systems that are both secure and actually operable.&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://datatracker.ietf.org/doc/html/draft-ietf-wimse-arch-07" rel="noopener noreferrer"&gt;IETF Internet-Draft: WIMSE Architecture (draft-ietf-wimse-arch-07)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://spiffe.io/" rel="noopener noreferrer"&gt;SPIFFE Project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE-ID.md" rel="noopener noreferrer"&gt;SPIFFE ID Format Specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/" rel="noopener noreferrer"&gt;Kubernetes Workload Identity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://istio.io/latest/docs/concepts/security/#mutual-tls" rel="noopener noreferrer"&gt;Istio Mutual TLS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://datatracker.ietf.org/doc/rfc9421/" rel="noopener noreferrer"&gt;RFC 9421: HTTP Message Signatures&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>microservices</category>
      <category>oauth</category>
      <category>identity</category>
    </item>
  </channel>
</rss>
