Table of Contents

How AAuth Brings Human Approval Into AI Agent Authorization

TL;DR: AAuth is an authentication and authorization protocol for AI agents inspired by OAuth and OIDC. It authenticates and authorizes agents without the need for a human; it lets agents act on behalf of humans with a separate set of credentials (delegation), and optionally supports  human approval before granting access (human in the loop).

Víctor Jiménez
Víctor Jiménez

Technical Writer at RootNode

Summarize:

Read
0%
A team of engineers reviews an AI agent authorization flow on a conference room display.

Table of Contents

Read
0%

As adoption for agentic workloads grows, organizations question how much they should keep humans in the loop (HITL).

And, as OAuth and OIDC aren’t designed with AI agents in mind, teams tend to be overpermissive, which forces humans to keep a tight look on what the agents are doing. This shifts the human approval conversation to fear and lack of trust, causing either agents to roam free or humans to get fatigued from overseeing each agent’s actions.

A recent draft protocol, called AAuth, proposes a solution for AI Agents Authentication and Authorization with a focus on agent identity, delegated access, and optional human approval. The proposal is driven by Dick Hardt, who helped launch the OpenID Foundation and led the design of OAuth 2.0 and JWTs.

We found it interesting how AAuth makes it easy to follow identity best practices, while reusing the building blocks of existing solutions, like JWTs, scopes, or auth tokens. It’s too soon to know if AAuth will become the next big thing, but it certainly is a good example to learn from and a project worth keeping track of.

This article examines  how AAuth works, followed by a walkthrough of one three-party authorization flow.

What Is AAuth?

AAuth is an authentication and authorization protocol for AI agents inspired by OAuth and OIDC.

Although it’s currently in draft state, it’s driven by one of the minds behind OAuth and OIDC, it feels well thought out, and there are plenty of resources and SDKs available. Also, it uses concepts we are already familiar with, like JWTs, scopes, or auth tokens; so it feels more like a natural evolution than a revolution.

Some of AAuth’s notable design elements include: 

Credential provisioning: Agents use public-private key pairs and signed requests to prove possession of an identity associated with an agent token, similar to private key JWTs in OAuth and OIDC. Public keys are hosted at discoverable endpoints so that all actors can verify their identity.

HITL by design: Actors can request human approval, and the agent won’t get the credentials until the approval is granted.

Delegated access: AAuth resources are aware both of who the agent is and which user they are acting on behalf of. This transparency allows operators to set different access policies for agents than for users, and provides visibility on what the agents are doing.

Combined tokens: Auth tokens on AAuth combine both the access tokens (similar to OAuth) and the identity claims (similar to OIDC) on a single JWT.

But most importantly, AAuth increases organizations’ trust in agents, so their teams can focus on implementing human approval best practices.

We are cautious about AAuth, while similar to OAuth and OIDC, it introduces significant changes to identity flows that everyone has to implement. Will AAuth be widely adopted as is, or will its ideas be adapted and implemented as extensions to OAuth and OIDC? In any case, we find AAuth an interesting pointer to what the future for agentic workloads may look like.

When Should AI Agent Actions Require Human Approval?

Sometimes AI agents aren’t prepared to make a proper decision.

For example, Discord automated moderation services failed recently, mistaking grid images like chessboards for offensive content, resulting in the ban of 8,000 accounts. A similar episode took place days later on Instagram and Facebook.

Humans in AI workloads provide a safety net in these scenarios by adding nuance and context to decision-making. However, they also add latency to the system, defeating the purpose of such automations.

Human approval is also a balance of signal versus noise. If approving is repetitive, people stop paying attention, and the guardrail stops being a safety net.

The role of humans when designing automation is to identify where agents may struggle, anticipate positive and negative flows, and assess risk. Humans should be taken into account when:

      • An error will have severe consequences.

      • Actions are hard to undo.

      • An agent needs to go out of scope to provide the proper response.

    With this in mind, humans should be kept in the loop in workloads like granting privileged access, applying changes in production environments, or working with data in bulk.

    You may also need to keep humans in the loop for regulatory compliance (EU AI Act) or ethical decisions. For example, in sectors like healthcare and financial services, where errors can have serious consequences: a wrong diagnosis can be fatal, and leaking financial information cannot be undone.

    How Agents and Humans Benefit Mutually?

    Automating workloads with agentic AI is a collaborative learning process. Done properly, it comes along with a culture where human reviewers share notes to improve their playbooks, the scripts they follow when evaluating a request. 

    For humans, this exchange not only keeps their individual criteria coherent within the team, but also surfaces edge cases that can be added to the playbooks.

    For AI agents, these updated playbooks can be used as guidelines to reduce error rates.

    As the system matures, organizations can reduce human intervention. Only then can the paradigm shift from humans controlling agents to humans architecting agentic workloads.

    How does AAuth Work?

    The three-party AAuth flow used in this walkthrough involves an agent, a resource and a person server. 

    Let’s take a deep dive into how AAuth handles human approval by running a mock agent and spying on its messages with an AAuth-protected resource.

    As we’ll use their JavaScript reference implementation, you’ll be able to follow this example as long as you have a fairly recent installation of Node.js and npm. We also left some links at the end to other interactive resources, where you’ll be able to play around with AAuth without setting up anything on your machine.

    Setting Up the Agent

    First, we’ll set up our agent. An agent would typically be an LLM client connecting with an MCP server, and the protected resources would be each of the MCP server’s tools. However, AAuth could also be implemented between an MCP server and an API, where the MCP server would take the role of the agent, and the API would be the resource.

    For our example, we’ll use AAuth’s CLI, which is a mock agent whose only purpose is to call the AAuth-protected whoami.aauth.dev resource and display the messages sent between the agent and the resource.

    As authentication in AAuth is done with cryptographic keys, we’ll need to:

        1. Generate a set of keys that uniquely identify our client.

        1. Publish our agent’s public key in JWK.

      Generating the keys: AAuth’s CLI has a bootstrap command to generate these keys:

          • By default, it uses hello.coop as the person server.

          • We’ll be using GitHub Pages to host the agent metadata, so this URL will also be used as its identifier. 

        $ npx @aauth/bootstrap create "https://username.github.io"
        
        {
        
          "agentProvider": "https://username.github.io",
        
          "agentId": "aauth:local@username.github.io",
        
          "personServer": "https://person.hello.coop",
        
          "keys": [
        
            {
        
              "kid": "2026-07-22_b74",
        
              "keystore": "software",
        
              "publicJwk": {
        
                "crv": "Ed25519",
        
                "x": "0qatUiXN7shzIh-U8a6JE5UcA-WcOB4UouJc5QB3mMA",
        
                "kty": "OKP",
        
                "kid": "2026-07-22_b74",
        
                "alg": "EdDSA",
        
                "use": "sig",
        
                "aauth": {
        
                  "device": "macluzom3",
        
                  "created": "2026-07-22"
        
                }
        
              }
        
            }
        
          ]
        
        }

        This command:

            1. Generated the key pair.

            1. Stored it in our system’s secure keystore (e.g., macOS Keychain).

            1. Displayed the public key information under the publicJwk field.

          Publishing the public key: Next, we’ll upload this information to our GitHub page, although we could upload to any public server under our control.

              • As an extra step, we had to turn off Jekyll templating so GitHub Pages published our files correctly. You can do this by dropping an empty .nojekyll file in the root of the repository.

            We’ll need to publish two files.

            The .well-known/aauth-agent.json file contains our agent’s metadata and the link to its public key. We built it using the agentProvider field from the CLI’s output:

            {
            
              "issuer": "https://username.github.io",
            
              "jwks_uri": "https://username.github.io/.well-known/jwks.json",
            
              "client_name": "TestAAuthAgent",
            
              "logo_uri": "https://avatars.githubusercontent.com/u/USER_ID?v=4"
            
            }

            The .well-known/jwks.json file contains the public key. We built it by adding the contents of the publicJwk field from the cli’s output inside an array named keys:

            {
            
              "keys": [
            
                {
            
                  "crv": "Ed25519",
            
                  "x": "0qatUiXN7shzIh-U8a6JE5UcA-WcOB4UouJc5QB3mMA",
            
                  "kty": "OKP",
            
                  "kid": "2026-07-22_b74",
            
                  "alg": "EdDSA",
            
                  "use": "sig",
            
                  "aauth": {
            
                    "device": "macluzom3",
            
                    "created": "2026-07-22"
            
                  }
            
                }
            
              ]
            
            }

            After deploying these files, you can check they are publicly available with curl:

            $ curl -s -o /dev/null -w '%{http_code}' "https://username.github.io/.well-known/aauth-agent.json?cb=$(date +%s)"
            
            200
            
            $ curl -s -o /dev/null -w '%{http_code}' "https://username.github.io/.well-known/jwks.json?cb=$(date +%s)"
            
            200

            A 200 response signals that the files are reachable.

            Accessing a Resource

            Now that our mock agent is set up, we’ll trigger a call to the whoami.aauth.dev resource server. This is a simple API that returns ID claims.

            $ npx @aauth/fetch "https://whoami.aauth.dev?scope=email+profile" --explain --prompt-consent

            Note:

                • We know in advance that the scopes ?scope=email+profile in the resource will trigger a human authorization.

                • The --explain flag will output the HTTP messages between our agent and the resource in JSON format.

                • Use the --prompt-consent flag to force a human approval if you re-run this command before the auth token has expired.

              Let’s explore the output step by step:

              Step 1: First, the agent’s request:

              {
              
                "step": "agent_token_request",
              
                "summary": "agent → resource · agent-token → 401 + resource-token",
              
                "description": "Call the resource with your agent token.",
              
                "request": {
              
                  "method": "GET",
              
                  "url": "https://whoami.aauth.dev?scope=email+profile",
              
                  "headers": {
              
                    "aauth-capabilities": "interaction",
              
                    "signature": "sig=:Rflfh8lOghBnmjjIDUkPF4pgtaEMDRG/rVWr1sDPT019ECK/erqBD/bwANBmO53xVRjxfiEr42dG0I9G8WJiDA==:",
              
                    "signature-input": "sig=(\"@method\" \"@authority\" \"@path\" \"signature-key\");created=1784712523",
              
                    "signature-key": "sig=jwt;jwt=\"eyJhbGciOiJFZERTQSIsInR5cCI6ImFhLWFnZW50K2p3dCIsImtpZCI6IjIwMjYtMDctMjJfYjc0In0.eyJpc3MiOiJodHRwczovL3VzZXJuYW1lLmdpdGh1Yi5pbyIsImR3ayI6ImFhdXRoLWFnZW50Lmpzb24iLCJzdWIiOiJhYXV0aDpsb2NhbEB1c2VybmFtZS5naXRodWIuaW8iLCJqdGkiOiIyMTM0YmJiOC1iZjgxLTQ5YzYtOWU2MC1jYWI3MTIyYTI1MmQiLCJjbmYiOnsiandrIjp7ImNydiI6IkVkMjU1MTkiLCJ4IjoiTXBjSzB2d2NhMHE0NjZpcDFGR0dQNGR6Y04yRzlYRERyUllLTW5menl5USIsImt0eSI6Ik9LUCJ9fSwiaWF0IjoxNzg0NzEyNTIzLCJleHAiOjE3ODQ3MTYxMjMsInBzIjoiaHR0cHM6Ly9wZXJzb24uaGVsbG8uY29vcCJ9.6r9J8StlgpkDO8A6ihe-knq-DX5tipKIIndw-wVwRBZ645PazA05Z3owoqGd3PhmFTuH0d4vc64JM8QVD0SiBw\""
              
                  }
              
                }
              
              }

              The agent sends a GET HTTP request to the URL, sending a JWT in the signature-key with the following payload:

              {
              
                "iss": "https://username.github.io",
              
                "dwk": "aauth-agent.json",
              
                "sub": "aauth:local@username.github.io",
              
                "jti": "2134bbb8-bf81-49c6-9e60-cab7122a252d",
              
                "cnf": {
              
                  "jwk": {
              
                    "crv": "Ed25519",
              
                    "x": "MpcK0vwca0q466ip1FGGP4dzcN2G9XDDrRYKMnfzyyQ",
              
                    "kty": "OKP"
              
                  }
              
                },
              
                "iat": 1784712523,
              
                "exp": 1784716123,
              
                "ps": "https://person.hello.coop"
              
              }

              This is the agent token. It’s a rather standard JWT, similar to the ones used in OIDC, containing the agent’s credentials. Notice how it adds an extra ps field, pointing to our person server.

              Step 2: The resource responds with a 401 error:

              {
              
                "step": "agent_token_request",
              
                "description": "The resource requires a person-issued auth token — this begins the three-party flow (agent ↔ person server ↔ resource). The `AAuth-Requirement` header carries a resource token: the agent presents it to the person server to get authorized.",
              
                "response": {
              
                  "status": 401,
              
                  "headers": {
              
                    "aauth-requirement": "requirement=auth-token; resource-token=\"eyJhbGciOiJFZERTQSIsInR5cCI6ImFhLXJlc291cmNlK2p3dCIsImtpZCI6IklIaTBIY291M3NuTHQ5azdSeWhYSWtVdWt6LUtWT0tma2lNQVZ2OEFuVzQifQ.eyJpc3MiOiJodHRwczovL3dob2FtaS5hYXV0aC5kZXYiLCJkd2siOiJhYXV0aC1yZXNvdXJjZS5qc29uIiwiYXVkIjoiaHR0cHM6Ly9wZXJzb24uaGVsbG8uY29vcCIsImp0aSI6Ikw2S1Q1WVEyd1ZsSXhwRTNfeHRTYXciLCJhZ2VudCI6ImFhdXRoOmxvY2FsQHVzZXJuYW1lLmdpdGh1Yi5pbyIsImFnZW50X2prdCI6ImxtU2xxNkZiSC1OamRMaVNvM1N4cDFPdlJ2WmF1a202ekYyYllFSWtUVUEiLCJzY29wZSI6Indob2FtaSBlbWFpbCBwcm9maWxlIiwiaWF0IjoxNzg0NzEyNTIzLCJleHAiOjE3ODQ3MTI4MjN9.bWWiRSoDxoK4ZAHyfAZAUkKGDp1S2dtw1CsD_NbP_9n_BJAR_tQHaQVs8WEv6YkF4_03pQJxP1EcjPTC8eTTDA\"",
              
                    "content-type": "application/json"
              
                  },
              
                  "body": {
              
                    "error": "auth_token_required"
              
                  }
              
                }
              
              }

              It indicates that an auth token is needed to access the resource, and provides a resource token to be exchanged for an auth token.

              Decoding the resource token, we can see on the aud field that this token is intended for the person server declared on the agent’s token.

              {
              
                "iss": "https://whoami.aauth.dev",
              
                "dwk": "aauth-resource.json",
              
                "aud": "https://person.hello.coop",
              
                "jti": "L6KT5YQ2wVlIxpE3_xtSaw",
              
                "agent": "aauth:local@username.github.io",
              
                "agent_jkt": "lmSlq6FbH-NjdLiSo3Sxp1OvRvZaukm6zF2bYEIkTUA",
              
                "scope": "whoami email profile",
              
                "iat": 1784712523,
              
                "exp": 1784712823
              
              }

              Obtaining an Auth Token

              The agent then contacts the person server and requests an auth token: 

              Step 3: This is the agent’s request:

              {
              
                "step": "ps_token_request",
              
                "summary": "agent → person server · resource-token → 202 pending + approval code",
              
                "description": "POST the resource token to the person server `token_endpoint` to mint an auth token. `Prefer: wait=45` long-polls — the server may hold the connection up to 45s before returning.",
              
                "request": {
              
                  "url": "https://person.hello.coop/aauth/token",
              
                  "headers": {
              
                    "aauth-capabilities": "interaction",
              
                    "content-type": "application/json",
              
                    "prefer": "wait=45",
              
                    "signature": "sig=:caoDxiq2ORqsnxuykKoFnKb1qhSdvOzmW/xfmLo8SD4xu43XNde4ZX+sm1AImAlzn3UqUsUIGoTOutGrptsSAQ==:",
              
                    "signature-input": "sig=(\"@method\" \"@authority\" \"@path\" \"content-type\" \"signature-key\");created=1784712523",
              
                    "signature-key": "sig=jwt;jwt=\"eyJhbGciOiJFZERTQSIsInR5cCI6ImFhLWFnZW50K2p3dCIsImtpZCI6IjIwMjYtMDctMjJfYjc0In0.eyJpc3MiOiJodHRwczovL3VzZXJuYW1lLmdpdGh1Yi5pbyIsImR3ayI6ImFhdXRoLWFnZW50Lmpzb24iLCJzdWIiOiJhYXV0aDpsb2NhbEB1c2VybmFtZS5naXRodWIuaW8iLCJqdGkiOiIyMTM0YmJiOC1iZjgxLTQ5YzYtOWU2MC1jYWI3MTIyYTI1MmQiLCJjbmYiOnsiandrIjp7ImNydiI6IkVkMjU1MTkiLCJ4IjoiTXBjSzB2d2NhMHE0NjZpcDFGR0dQNGR6Y04yRzlYRERyUllLTW5menl5USIsImt0eSI6Ik9LUCJ9fSwiaWF0IjoxNzg0NzEyNTIzLCJleHAiOjE3ODQ3MTYxMjMsInBzIjoiaHR0cHM6Ly9wZXJzb24uaGVsbG8uY29vcCJ9.6r9J8StlgpkDO8A6ihe-knq-DX5tipKIIndw-wVwRBZ645PazA05Z3owoqGd3PhmFTuH0d4vc64JM8QVD0SiBw\""
              
                  },
              
                  "body": {
              
                    "resource_token": "eyJhbGciOiJFZERTQSIsInR5cCI6ImFhLXJlc291cmNlK2p3dCIsImtpZCI6IklIaTBIY291M3NuTHQ5azdSeWhYSWtVdWt6LUtWT0tma2lNQVZ2OEFuVzQifQ.eyJpc3MiOiJodHRwczovL3dob2FtaS5hYXV0aC5kZXYiLCJkd2siOiJhYXV0aC1yZXNvdXJjZS5qc29uIiwiYXVkIjoiaHR0cHM6Ly9wZXJzb24uaGVsbG8uY29vcCIsImp0aSI6Ikw2S1Q1WVEyd1ZsSXhwRTNfeHRTYXciLCJhZ2VudCI6ImFhdXRoOmxvY2FsQHVzZXJuYW1lLmdpdGh1Yi5pbyIsImFnZW50X2prdCI6ImxtU2xxNkZiSC1OamRMaVNvM1N4cDFPdlJ2WmF1a202ekYyYllFSWtUVUEiLCJzY29wZSI6Indob2FtaSBlbWFpbCBwcm9maWxlIiwiaWF0IjoxNzg0NzEyNTIzLCJleHAiOjE3ODQ3MTI4MjN9.bWWiRSoDxoK4ZAHyfAZAUkKGDp1S2dtw1CsD_NbP_9n_BJAR_tQHaQVs8WEv6YkF4_03pQJxP1EcjPTC8eTTDA",
              
                    "capabilities": [
              
                      "interaction"
              
                    ],
              
                    "prompt": "consent"
              
                  }
              
                }
              
              }

              Note that:

                  • With the “prefer“: “wait=45” HTTP header, the agent indicates that it is willing to poll for the result, and the server may hold the connection for up to 45 seconds.

                  • The agent, once again, identifies with an agent token in the signature-key field.

                  • The body contains a JSON with the JWT resource_token the agent received from the resource.

                Step 4: The person server considers that this request needs human approval, so it provides a URL where a human user can provide it:

                {
                
                  "step": "ps_token_request",
                
                  "description": "User interaction required before the auth token is issued (`AAuth-Requirement: requirement=interaction`) — the person must approve in a browser; the agent polls the pending `location` until they do.",
                
                  "response": {
                
                    "status": 202,
                
                    "headers": {
                
                      "aauth-requirement": "requirement=interaction; url=\"https://person.hello.coop/auth\"; code=\"9Z3N-K7GZ\"",
                
                      "content-type": "application/json; charset=utf-8",
                
                      "location": "https://person.hello.coop/aauth/pending/9Z3N-K7GZ"
                
                    },
                
                    "body": {
                
                      "status": "pending",
                
                      "location": "https://person.hello.coop/aauth/pending/9Z3N-K7GZ",
                
                      "requirement": "interaction",
                
                      "code": "9Z3N-K7GZ",
                
                      "timeout": 600
                
                    }
                
                  }
                
                }

                Step 5: If you are following this example, you’ll see how the mock agent stops at this moment and instructs you to navigate to the address in the aauth-requirement field. The agent will also generate a QR code for your convenience:

                qr code

                Step 6: At the same time, the agent will start polling the URL in the location field: 

                {
                
                  "step": "consent_poll",
                
                  "summary": "agent → person server · poll → 200 + auth-token (person approved)",
                
                  "description": "Poll the pending URL — checking whether the person has acted. `Prefer: wait=45` long-polls so the response returns immediately on consent rather than burning round-trips.",
                
                  "request": {
                
                    "url": "https://person.hello.coop/aauth/pending/9Z3N-K7GZ",
                
                    "headers": {
                
                      "aauth-capabilities": "interaction",
                
                      "prefer": "wait=45",
                
                      "signature": "sig=:OGV3a9sIFGD4yBcAcqwoup19tdbMyYZRPssSHa+Lwtw8K6Ynnp+jO8wgyRwIRZK0zjmhwWZ+o1TamNT5jaSaAg==:",
                
                      "signature-input": "sig=(\"@method\" \"@authority\" \"@path\" \"signature-key\");created=1784712524",
                
                      "signature-key": "sig=jwt;jwt=\"eyJhbGciOiJFZERTQSIsInR5cCI6ImFhLWFnZW50K2p3dCIsImtpZCI6IjIwMjYtMDctMjJfYjc0In0.eyJpc3MiOiJodHRwczovL3VzZXJuYW1lLmdpdGh1Yi5pbyIsImR3ayI6ImFhdXRoLWFnZW50Lmpzb24iLCJzdWIiOiJhYXV0aDpsb2NhbEB1c2VybmFtZS5naXRodWIuaW8iLCJqdGkiOiIyMTM0YmJiOC1iZjgxLTQ5YzYtOWU2MC1jYWI3MTIyYTI1MmQiLCJjbmYiOnsiandrIjp7ImNydiI6IkVkMjU1MTkiLCJ4IjoiTXBjSzB2d2NhMHE0NjZpcDFGR0dQNGR6Y04yRzlYRERyUllLTW5menl5USIsImt0eSI6Ik9LUCJ9fSwiaWF0IjoxNzg0NzEyNTIzLCJleHAiOjE3ODQ3MTYxMjMsInBzIjoiaHR0cHM6Ly9wZXJzb24uaGVsbG8uY29vcCJ9.6r9J8StlgpkDO8A6ihe-knq-DX5tipKIIndw-wVwRBZ645PazA05Z3owoqGd3PhmFTuH0d4vc64JM8QVD0SiBw\""
                
                    }
                
                  }
                
                }

                Step 7: Following the approval URL, you’ll find a regular SSO process. You may choose any option to register a new account, or use an existing hello.coop one:

                After the authentication process, you’ll be presented with a confirmation screen:

                confirmation screen

                Step 8: Once you hit approve, you’ll see how the agent receives an auth token:

                {
                
                  "step": "consent_poll",
                
                  "description": "The person approved — the body carries the freshly issued auth token, bound (`cnf`) to the same ephemeral key the agent has been signing with.",
                
                  "response": {
                
                    "status": 200,
                
                    "headers": {
                
                      "content-type": "application/json; charset=utf-8"
                
                    },
                
                    "body": {
                
                      "auth_token": "eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMDctMDZUMjI6NTg6NTMuODk4Wl9lYmYtZDIyLUVkRFNBIiwidHlwIjoiYWEtYXV0aCtqd3QifQ.eyJpc3MiOiJodHRwczovL3BlcnNvbi5oZWxsby5jb29wIiwiZHdrIjoiYWF1dGgtcGVyc29uLmpzb24iLCJqdGkiOiJTMkxxaExPZU94dVpiODRnR25la1UiLCJzdWIiOiJzdWJfenJKOEVqOGU5M1c4dGV0am1TSHdGcFdrX0NCZSIsImF1ZCI6Imh0dHBzOi8vd2hvYW1pLmFhdXRoLmRldiIsImFnZW50IjoiYWF1dGg6bG9jYWxAdXNlcm5hbWUuZ2l0aHViLmlvIiwiYWN0Ijp7ImFnZW50IjoiYWF1dGg6bG9jYWxAdXNlcm5hbWUuZ2l0aHViLmlvIn0sInNjb3BlIjoid2hvYW1pIiwidGVuYW50IjoidGVuX2NETHdmcWNXNDA2Tnk4d1lKaERXUjdYVF9GOXEiLCJlbWFpbCI6InRlc3RAYWVtYml0LmlvIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsIm5hbWUiOiJBZW1iaXQuaW8iLCJwaWN0dXJlIjoiaHR0cHM6Ly9waWN0dXJlcy5oZWxsby5jb29wL3IvMDBkOTk0YzQtYWU3Mi00NTVkLWIzNzMtNWNlZTRkOGRlNmZkLnBuZyIsImNuZiI6eyJqd2siOnsia3R5IjoiT0tQIiwiY3J2IjoiRWQyNTUxOSIsIngiOiJNcGNLMHZ3Y2EwcTQ2NmlwMUZHR1A0ZHpjTjJHOVhERHJSWUtNbmZ6eXlRIn19LCJpYXQiOjE3ODQ3MTI1NTIsImV4cCI6MTc4NDcxNjE1Mn0.nanFjpoMQbwi28gZMb0Hq5DvDyABV2uvm7pLoLoWvzwLvD6ozZmp5i6B3QcFI-F7qCoKao1GjSvT9Qpb36jGBw",
                
                      "expires_in": 3600
                
                    }
                
                  }
                
                }

                Expanding the auth_token, we can see how this token also contains ID claims for both the agent and the user:

                {
                
                  "iss": "https://person.hello.coop",
                
                  "dwk": "aauth-person.json",
                
                  "jti": "S2LqhLOeOxuZb84gGnekU",
                
                  "sub": "sub_zrJ8Ej8e93W8tetjmSHwFpWk_CBe",
                
                  "aud": "https://whoami.aauth.dev",
                
                  "agent": "aauth:local@username.github.io",
                
                  "act": {
                
                    "agent": "aauth:local@username.github.io"
                
                  },
                
                  "scope": "whoami",
                
                  "tenant": "ten_cDLwfqcW406Ny8wYJhDWR7XT_F9q",
                
                  "email": "test@aembit.io",
                
                  "email_verified": true,
                
                  "name": "Aembit.io",
                
                  "picture": "https://pictures.hello.coop/r/00d994c4-ae72-455d-b373-5cee4d8de6fd.png",
                
                  "cnf": {
                
                    "jwk": {
                
                      "kty": "OKP",
                
                      "crv": "Ed25519",
                
                      "x": "MpcK0vwca0q466ip1FGGP4dzcN2G9XDDrRYKMnfzyyQ"
                
                    }
                
                  },
                
                  "iat": 1784712552,
                
                  "exp": 1784716152
                
                }

                We can see how:

                    • This token is issued and signed by iss, our person server.

                    • This token authorizes the actors in act, our agent.

                    • For the service in “aud“: “https://whoami.aauth.dev“.

                  Using the auth_token to Access the Resource

                  With the token in hand, the agent tries to access the resource again:

                  Step 9: The agent’s request:

                  {
                  
                    "step": "auth_token_request",
                  
                    "summary": "agent → resource · auth-token → 200 + person claims",
                  
                    "description": "Call the resource — `Signature-Key` now carries the person-issued auth token, not the agent token.",
                  
                    "request": {
                  
                      "url": "https://whoami.aauth.dev?scope=email+profile",
                  
                      "headers": {
                  
                        "signature": "sig=:leGywBK74jsB8nMhij7AgsQ0N9l0f8zpxmgF4bxvgesuEOLCcukN5Jif1WDMGnO7+12hqagK8maSVWfgPf4eDA==:",
                  
                        "signature-input": "sig=(\"@method\" \"@authority\" \"@path\" \"signature-key\");created=1784712552",
                  
                        "signature-key": "sig=jwt;jwt=\"eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjYtMDctMDZUMjI6NTg6NTMuODk4Wl9lYmYtZDIyLUVkRFNBIiwidHlwIjoiYWEtYXV0aCtqd3QifQ.eyJpc3MiOiJodHRwczovL3BlcnNvbi5oZWxsby5jb29wIiwiZHdrIjoiYWF1dGgtcGVyc29uLmpzb24iLCJqdGkiOiJTMkxxaExPZU94dVpiODRnR25la1UiLCJzdWIiOiJzdWJfenJKOEVqOGU5M1c4dGV0am1TSHdGcFdrX0NCZSIsImF1ZCI6Imh0dHBzOi8vd2hvYW1pLmFhdXRoLmRldiIsImFnZW50IjoiYWF1dGg6bG9jYWxAdXNlcm5hbWUuZ2l0aHViLmlvIiwiYWN0Ijp7ImFnZW50IjoiYWF1dGg6bG9jYWxAdXNlcm5hbWUuZ2l0aHViLmlvIn0sInNjb3BlIjoid2hvYW1pIiwidGVuYW50IjoidGVuX2NETHdmcWNXNDA2Tnk4d1lKaERXUjdYVF9GOXEiLCJlbWFpbCI6InRlc3RAYWVtYml0LmlvIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsIm5hbWUiOiJBZW1iaXQuaW8iLCJwaWN0dXJlIjoiaHR0cHM6Ly9waWN0dXJlcy5oZWxsby5jb29wL3IvMDBkOTk0YzQtYWU3Mi00NTVkLWIzNzMtNWNlZTRkOGRlNmZkLnBuZyIsImNuZiI6eyJqd2siOnsia3R5IjoiT0tQIiwiY3J2IjoiRWQyNTUxOSIsIngiOiJNcGNLMHZ3Y2EwcTQ2NmlwMUZHR1A0ZHpjTjJHOVhERHJSWUtNbmZ6eXlRIn19LCJpYXQiOjE3ODQ3MTI1NTIsImV4cCI6MTc4NDcxNjE1Mn0.nanFjpoMQbwi28gZMb0Hq5DvDyABV2uvm7pLoLoWvzwLvD6ozZmp5i6B3QcFI-F7qCoKao1GjSvT9Qpb36jGBw\""
                  
                      }
                  
                    }
                  
                  }

                  It may look like it’s repeating the first request, but note how this time around the signature-key header contains the newly acquired token (signed by the person server), instead of the agent token.

                  Step 10: The resource accepts this token and returns a status 200:

                  {
                  
                    "step": "auth_token_request",
                  
                    "description": "The resource verified the person-issued auth token — it now knows who is calling (`agent`) and on whose behalf (`sub`, plus claims the person server vouched for).",
                  
                    "response": {
                  
                      "status": 200,
                  
                      "headers": {
                  
                        "content-type": "application/json"
                  
                      },
                  
                      "body": {
                  
                        "sub": "sub_zrJ8Ej8e93W8tetjmSHwFpWk_CBe",
                  
                        "agent": "aauth:local@username.github.io",
                  
                        "tenant": "ten_cDLwfqcW406Ny8wYJhDWR7XT_F9q",
                  
                        "email": "test@aembit.io",
                  
                        "email_verified": true,
                  
                        "name": "Aembit.io",
                  
                        "picture": "https://pictures.hello.coop/r/00d994c4-ae72-455d-b373-5cee4d8de6fd.png"
                  
                      }
                  
                    }
                  
                  }

                  The whoami.aauth.dev resource’s purpose is to let us play around with the AAuth protocol, so it just echoes back our ID claims and doesn’t perform any other action. However, in a real-world example, this could be an API performing a sensitive operation, like banning a user account.

                  What’s the Role of IAM on AAuth?

                  The AAuth protocol includes a federated scenario that decouples authorization (what is the user allowed to do) and authentication (who the user is).

                  In this scenario, the person server stays in charge of performing authentication, but federates authorization to a centralized access server. These access servers are IAM tools that implement access policies across organizations.

                  Interest in AAuth is growing as organizations look for more effective ways to manage agent identity, delegated access and human approval. The protocol also builds on familiar security standards and formats, including JWTs and HTTP Message Signatures, which could make it easier for IAM and authorization systems to support AAuth as the draft develops. 

                  Where to Learn More About AAuth?

                  AAuth is a rather mature project with a strong chance of becoming the new de facto standard for authentication and authorization in agentic workloads. There are plenty of resources to get started; we recommend the following:

                      • This WebAgent demo lets you play with the above scenario without setting up anything on your computer.

                      • You can also try the walkthrough demo, which loads your coding agent with a set of skills to guide you on setting up the above scenario.

                    Although AAuth sounds promising, its adoption is low at the time of writing this article. For example, there is no support for AAuth resources in popular platforms like Claude or ChatGPT. There are also few resources and IAM tools that currently support AAuth or HITL.

                    However, if you want to take the next step, you can check this playlist from the AAuth developers; it contains several demos where Dick Hardt provides more context on the philosophy behind AAuth.

                    Conclusion

                    Deciding when to require human approval is a matter of risk assessment. You should approach it as a training process where humans help the system mature so it requires fewer approvals over time.

                    Although implementing HITL in your current agentic workloads requires coding your own agents, AI evolves fast, and things may be completely different in a few months.

                    Proposals such as AAuth aim to bring native support for human approval and centralized access control to agentic workloads, including mechanisms that withhold authorization until approval is granted.

                    The open question is whether the broader ecosystem will adopt them.

                    Related Reading

                    Víctor Jiménez
                    Víctor Jiménez

                    Víctor Jiménez is an engineer and technical writer who specializes in turning complex engineering concepts into clear, practical explanations. He began his career as a full-stack software engineer, while also working as a MySQL database administrator and certified instructor. After moving into technical marketing, Víctor focused increasingly on educational content for technical audiences. His recent work covers cloud infrastructure, containers and cybersecurity. Outside of work, he experiments with 3D printing, builds LEGO sets and hosts Dungeons & Dragons campaigns.

                    You might also like

                    Aembit’s new Credential Provider automates Claude API Workload Identity Federation, retiring static keys for short-lived tokens.
                    The MCP authorization spec sets a new standard for securing non-human AI agents – with lessons for anyone building autonomous, scalable systems.
                    An exercise ended with frontier models inside a platform’s production systems, exposing a hard truth about what agents can do with credentials that systems trust.