From f4c32b8b6fcd4745909a64814ce6b77a25aa99ab Mon Sep 17 00:00:00 2001 From: "fudai.yf" Date: Fri, 13 Mar 2026 16:51:45 +0800 Subject: [PATCH 1/6] doc(README): update agent operation authorization IETF Draft link --- README.md | 4 ++-- README.zh-CN.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c7402a0..3c31b44 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Open Agent Auth is an enterprise-grade authorization framework that provides cryptographic identity binding, fine-grained authorization, request-level isolation, and semantic audit trails for AI agents operating on behalf of users. **It builds a collaborative ecosystem where humans, agents, and resource providers work as equal partners with mutual trust and accountability.** -The framework is implemented based on [IETF Draft: Agent Operation Authorization (draft-liu-agent-operation-authorization-00)](https://github.com/maxpassion/IETF-Agent-Operation-Authorization-draft/blob/main/draft-liu-agent-operation-authorization-00.xml), extending upon this standard by leveraging industry-standard protocols (OAuth 2.0, OpenID Connect, WIMSE, W3C VC) and featuring Model Context Protocol (MCP) integration to ensure every agent-executed operation is traceable to explicit user consent. +The framework is implemented based on [IETF Draft: Agent Operation Authorization (draft-liu-agent-operation-authorization-01)](https://datatracker.ietf.org/doc/draft-liu-agent-operation-authorization/), extending upon this standard by leveraging industry-standard protocols (OAuth 2.0, OpenID Connect, WIMSE, W3C VC) and featuring Model Context Protocol (MCP) integration to ensure every agent-executed operation is traceable to explicit user consent. ### Project Status @@ -326,7 +326,7 @@ For detailed security architecture, see [Security Documentation](docs/architectu ### Standards -- [Agent Operation Authorization Draft](https://github.com/maxpassion/IETF-Agent-Operation-Authorization-draft/blob/main/draft-liu-agent-operation-authorization-00.xml) +- [Agent Operation Authorization Draft](https://datatracker.ietf.org/doc/draft-liu-agent-operation-authorization/) --- diff --git a/README.zh-CN.md b/README.zh-CN.md index 233040a..e46e869 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -24,7 +24,7 @@ Open Agent Auth 是一款企业级授权框架,为代表用户执行操作的 AI 智能体提供密码学身份绑定、细粒度授权、请求级隔离与语义审计追踪能力。**该框架构建了一个协作生态系统,人类、智能体与资源提供方以平等伙伴的身份,在相互信任与问责机制下共同协作**。 -本框架基于 [IETF 草案:智能体操作授权(draft-liu-agent-operation-authorization-00)](https://github.com/maxpassion/IETF-Agent-Operation-Authorization-draft/blob/main/draft-liu-agent-operation-authorization-00.xml) 标准实现并对其进行扩展,融合 OAuth 2.0、OpenID Connect、WIMSE、W3C VC 等行业标准协议,并集成模型上下文协议(MCP),确保智能体执行的每一项操作均可追溯至明确的用户授权。 +本框架基于 [IETF 草案:智能体操作授权(draft-liu-agent-operation-authorization-01)](https://datatracker.ietf.org/doc/draft-liu-agent-operation-authorization/) 标准实现并对其进行扩展,融合 OAuth 2.0、OpenID Connect、WIMSE、W3C VC 等行业标准协议,并集成模型上下文协议(MCP),确保智能体执行的每一项操作均可追溯至明确的用户授权。 ### 项目状态 @@ -326,7 +326,7 @@ Open Agent Auth 在所有层级实施全面的安全措施: ### 标准规范 -- [智能体操作授权草案](https://github.com/maxpassion/IETF-Agent-Operation-Authorization-draft/blob/main/draft-liu-agent-operation-authorization-00.xml) +- [智能体操作授权草案](https://datatracker.ietf.org/doc/draft-liu-agent-operation-authorization/) --- From e9d3d9ea3f9e7a018620e68eebaacff1f4220c4c Mon Sep 17 00:00:00 2001 From: "fudai.yf" Date: Fri, 13 Mar 2026 16:53:54 +0800 Subject: [PATCH 2/6] feat(aoa): optimize policy operation render rules in the agent operation authorization consent page --- .../QwenLlmOperationTextRenderer.java | 35 +++++++++---------- .../QwenLlmOperationTextRendererTest.java | 6 ++-- .../templates/oauth2/aoa_consent.html | 2 +- 3 files changed, 20 insertions(+), 23 deletions(-) diff --git a/open-agent-auth-samples/sample-authorization-server/src/main/java/com/alibaba/openagentauth/sample/authz/renderer/QwenLlmOperationTextRenderer.java b/open-agent-auth-samples/sample-authorization-server/src/main/java/com/alibaba/openagentauth/sample/authz/renderer/QwenLlmOperationTextRenderer.java index 1fb9fe3..3b445d3 100644 --- a/open-agent-auth-samples/sample-authorization-server/src/main/java/com/alibaba/openagentauth/sample/authz/renderer/QwenLlmOperationTextRenderer.java +++ b/open-agent-auth-samples/sample-authorization-server/src/main/java/com/alibaba/openagentauth/sample/authz/renderer/QwenLlmOperationTextRenderer.java @@ -62,29 +62,26 @@ public class QwenLlmOperationTextRenderer implements OperationTextRenderer { private static final Logger logger = LoggerFactory.getLogger(QwenLlmOperationTextRenderer.class); private static final String SYSTEM_INSTRUCTION = """ - You are an authorization consent page assistant. Translate a machine-readable Rego policy \ - into a concise, scannable explanation for a non-technical user. + You are an authorization consent page assistant. Convert a Rego policy into a brief, \ + plain-language explanation a non-technical user can understand in under 5 seconds. - Output MUST use this exact structure: + Output a short paragraph (1–3 sentences). No headings, labels, bullet points, or \ + markdown — just plain sentences. - What this authorizes: [ONE sentence. State the action, the target resource, and any scope \ - in a single concise sentence. Do NOT repeat or paraphrase the same information twice.] + Sentence 1: State what the agent will be allowed to do and on which resource. \ + Use a verb-first style. Example: "Search and purchase books priced under $50." - Key constraints: [Bullet each REAL constraint from the policy, prefixed with "- ". \ - Only list limits that ARE explicitly present (e.g., spending caps, time windows, category \ - restrictions, rate limits, geographic bounds). NEVER list the absence of a constraint \ - (e.g., do NOT write "No spending cap is set"). If no constraints exist beyond what is \ - already stated above, omit this section entirely.] - - What this means for you: [ONE sentence. State what the agent can do after approval. \ - Only mention token expiration if an explicit expiration time is provided in the input.] + Sentence 2–3 (optional): Only if the policy contains explicit constraints such as \ + spending caps, time windows, category restrictions, rate limits, or geographic bounds, \ + state them naturally. Omit if no constraints exist. Rules: - 1. Be extremely concise. The user should grasp the meaning within 5 seconds of reading. - 2. No greetings, no markdown, no extra commentary. - 3. Use plain language. Avoid jargon like "Rego", "OPA", "policy", "predicate". - 4. Never repeat information across sections. Each section adds NEW information only. - 5. Prefer concrete terms: "search for books" over "perform search operations". + 1. Plain language only — no jargon (Rego, OPA, policy, predicate, input). + 2. Concrete nouns and verbs — "buy books" not "perform purchase operations". + 3. Never state the absence of a constraint ("No spending cap" is forbidden). + 4. Never add greetings, markdown formatting, or commentary. + 5. If a token expiration time is provided in the input, mention it naturally \ + (e.g., "This authorization expires at 23:59."). 6. Respond in English. """; @@ -171,7 +168,7 @@ private String buildPrompt(OperationTextRenderContext context) { prompt.append("\nAuthorization valid until: ").append(context.getTokenExpiration()).append("\n"); } - prompt.append("\nPlease provide the three-part explanation (What this authorizes / Key constraints / What this means for you)."); + prompt.append("\nProvide the ACTION line and, only if applicable, the LIMITS section."); return prompt.toString(); } diff --git a/open-agent-auth-samples/sample-authorization-server/src/test/java/com/alibaba/openagentauth/sample/authz/renderer/QwenLlmOperationTextRendererTest.java b/open-agent-auth-samples/sample-authorization-server/src/test/java/com/alibaba/openagentauth/sample/authz/renderer/QwenLlmOperationTextRendererTest.java index ceae763..6206106 100644 --- a/open-agent-auth-samples/sample-authorization-server/src/test/java/com/alibaba/openagentauth/sample/authz/renderer/QwenLlmOperationTextRendererTest.java +++ b/open-agent-auth-samples/sample-authorization-server/src/test/java/com/alibaba/openagentauth/sample/authz/renderer/QwenLlmOperationTextRendererTest.java @@ -86,14 +86,14 @@ void shouldRenderOperationTextSuccessfully() { .thenAnswer(invocation -> { AssistantContentSimpleConsumers consumers = invocation.getArgument(2); // Simulate text callback via mock AssistantContent - simulateTextResponse(consumers, "What this authorizes: The agent can search for books."); + simulateTextResponse(consumers, "Search for programming books."); return null; }); OperationTextRenderResult result = renderer.render(context); assertThat(result).isNotNull(); - assertThat(result.getRenderedText()).isEqualTo("What this authorizes: The agent can search for books."); + assertThat(result.getRenderedText()).isEqualTo("Search for programming books."); assertThat(result.getSemanticExpansionLevel()).isEqualTo(SemanticExpansionLevel.MEDIUM); } } @@ -112,7 +112,7 @@ void shouldReturnHighExpansionWhenOriginalPromptAbsent() { anyString(), any(TransportOptions.class), any(AssistantContentSimpleConsumers.class))) .thenAnswer(invocation -> { AssistantContentSimpleConsumers consumers = invocation.getArgument(2); - simulateTextResponse(consumers, "What this authorizes: Agent can search."); + simulateTextResponse(consumers, "Search for items."); return null; }); diff --git a/open-agent-auth-spring-boot-starter/src/main/resources/templates/oauth2/aoa_consent.html b/open-agent-auth-spring-boot-starter/src/main/resources/templates/oauth2/aoa_consent.html index 99c0675..6997703 100644 --- a/open-agent-auth-spring-boot-starter/src/main/resources/templates/oauth2/aoa_consent.html +++ b/open-agent-auth-spring-boot-starter/src/main/resources/templates/oauth2/aoa_consent.html @@ -746,7 +746,7 @@

Operation Authorization Details

- Purchase items under $50 during the Nov 11 promotion (valid until 23:59) + The agent can search and purchase books priced under $50. This authorization expires at 23:59.
Interpretation: medium From 1921afb7618a11ed6aecd79023cbf8f0b544e902 Mon Sep 17 00:00:00 2001 From: "fudai.yf" Date: Fri, 13 Mar 2026 21:27:46 +0800 Subject: [PATCH 3/6] feat(aoa): remove token expiration from operation policy text --- .../authz/renderer/QwenLlmOperationTextRenderer.java | 10 +++------- .../src/main/resources/application.yml | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/open-agent-auth-samples/sample-authorization-server/src/main/java/com/alibaba/openagentauth/sample/authz/renderer/QwenLlmOperationTextRenderer.java b/open-agent-auth-samples/sample-authorization-server/src/main/java/com/alibaba/openagentauth/sample/authz/renderer/QwenLlmOperationTextRenderer.java index 3b445d3..079da7e 100644 --- a/open-agent-auth-samples/sample-authorization-server/src/main/java/com/alibaba/openagentauth/sample/authz/renderer/QwenLlmOperationTextRenderer.java +++ b/open-agent-auth-samples/sample-authorization-server/src/main/java/com/alibaba/openagentauth/sample/authz/renderer/QwenLlmOperationTextRenderer.java @@ -80,8 +80,8 @@ Output a short paragraph (1–3 sentences). No headings, labels, bullet points, 2. Concrete nouns and verbs — "buy books" not "perform purchase operations". 3. Never state the absence of a constraint ("No spending cap" is forbidden). 4. Never add greetings, markdown formatting, or commentary. - 5. If a token expiration time is provided in the input, mention it naturally \ - (e.g., "This authorization expires at 23:59."). + 5. Only describe what the Rego policy itself permits or restricts. Do not infer \ + or mention information not present in the policy (such as token expiration). 6. Respond in English. """; @@ -164,11 +164,7 @@ private String buildPrompt(OperationTextRenderContext context) { } } - if (context.getTokenExpiration() != null) { - prompt.append("\nAuthorization valid until: ").append(context.getTokenExpiration()).append("\n"); - } - - prompt.append("\nProvide the ACTION line and, only if applicable, the LIMITS section."); + prompt.append("\nDescribe what this policy permits in plain language."); return prompt.toString(); } diff --git a/open-agent-auth-samples/sample-authorization-server/src/main/resources/application.yml b/open-agent-auth-samples/sample-authorization-server/src/main/resources/application.yml index b6fef51..5043ebc 100644 --- a/open-agent-auth-samples/sample-authorization-server/src/main/resources/application.yml +++ b/open-agent-auth-samples/sample-authorization-server/src/main/resources/application.yml @@ -91,7 +91,7 @@ sample: llm-renderer: enabled: true # Qwen model to use. Consistent with sample-agent's default model. - model: qwen3-coder-flash + model: qwen3-coder-plus # Timeout in seconds for LLM calls timeout: 120 From 07d3adb789b28dd51dfd7160feb60f19779e178d Mon Sep 17 00:00:00 2001 From: "fudai.yf" Date: Fri, 13 Mar 2026 21:40:33 +0800 Subject: [PATCH 4/6] feat(ui): add loading disable button after authorization operation --- .../templates/oauth2/aoa_consent.html | 66 ++++++++++++++++- .../templates/oauth2/oidc_consent.html | 71 ++++++++++++++++++- 2 files changed, 134 insertions(+), 3 deletions(-) diff --git a/open-agent-auth-spring-boot-starter/src/main/resources/templates/oauth2/aoa_consent.html b/open-agent-auth-spring-boot-starter/src/main/resources/templates/oauth2/aoa_consent.html index 6997703..3273721 100644 --- a/open-agent-auth-spring-boot-starter/src/main/resources/templates/oauth2/aoa_consent.html +++ b/open-agent-auth-spring-boot-starter/src/main/resources/templates/oauth2/aoa_consent.html @@ -607,6 +607,44 @@ transform: translateY(0); } + .btn:disabled { + opacity: 0.6; + cursor: not-allowed; + transform: none; + box-shadow: none; + pointer-events: none; + } + + .btn .btn-spinner { + display: none; + width: 16px; + height: 16px; + border: 2px solid transparent; + border-top-color: currentColor; + border-radius: 50%; + animation: spin 0.6s linear infinite; + } + + .btn.is-loading .btn-spinner { + display: inline-block; + } + + .btn.is-loading .btn-label { + display: none; + } + + .btn.is-loading .btn-loading-text { + display: inline; + } + + .btn .btn-loading-text { + display: none; + } + + @keyframes spin { + to { transform: rotate(360deg); } + } + .footer { text-align: center; margin-top: var(--space-4); @@ -908,10 +946,14 @@

Operation Authorization Details

@@ -925,6 +967,26 @@

Operation Authorization Details

const content = header.nextElementSibling; content.classList.toggle('show'); } + + document.querySelectorAll('form').forEach(function(form) { + form.addEventListener('submit', function(event) { + var clickedButton = event.submitter || document.activeElement; + if (clickedButton && clickedButton.name && clickedButton.value) { + var hiddenInput = document.createElement('input'); + hiddenInput.type = 'hidden'; + hiddenInput.name = clickedButton.name; + hiddenInput.value = clickedButton.value; + form.appendChild(hiddenInput); + } + var buttons = form.querySelectorAll('button[type="submit"]'); + buttons.forEach(function(button) { + button.disabled = true; + if (button === clickedButton) { + button.classList.add('is-loading'); + } + }); + }); + }); diff --git a/open-agent-auth-spring-boot-starter/src/main/resources/templates/oauth2/oidc_consent.html b/open-agent-auth-spring-boot-starter/src/main/resources/templates/oauth2/oidc_consent.html index b87aed2..d51621e 100644 --- a/open-agent-auth-spring-boot-starter/src/main/resources/templates/oauth2/oidc_consent.html +++ b/open-agent-auth-spring-boot-starter/src/main/resources/templates/oauth2/oidc_consent.html @@ -168,6 +168,44 @@ .btn-deny:active { background: #EBE7E1; } + + .btn:disabled { + opacity: 0.6; + cursor: not-allowed; + transform: none; + box-shadow: none; + pointer-events: none; + } + + .btn .btn-spinner { + display: none; + width: 16px; + height: 16px; + border: 2px solid transparent; + border-top-color: currentColor; + border-radius: 50%; + animation: spin 0.6s linear infinite; + } + + .btn.is-loading .btn-spinner { + display: inline-block; + } + + .btn.is-loading .btn-label { + display: none; + } + + .btn.is-loading .btn-loading-text { + display: inline; + } + + .btn .btn-loading-text { + display: none; + } + + @keyframes spin { + to { transform: rotate(360deg); } + } @@ -196,10 +234,41 @@

Authorize Access

- + Deny
+ + \ No newline at end of file From b686b29bf118ca92762e15153f7542e7fcb83da3 Mon Sep 17 00:00:00 2001 From: "fudai.yf" Date: Fri, 13 Mar 2026 21:49:53 +0800 Subject: [PATCH 5/6] test: fix unit test --- .../authz/renderer/QwenLlmOperationTextRendererTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/open-agent-auth-samples/sample-authorization-server/src/test/java/com/alibaba/openagentauth/sample/authz/renderer/QwenLlmOperationTextRendererTest.java b/open-agent-auth-samples/sample-authorization-server/src/test/java/com/alibaba/openagentauth/sample/authz/renderer/QwenLlmOperationTextRendererTest.java index 6206106..61beea1 100644 --- a/open-agent-auth-samples/sample-authorization-server/src/test/java/com/alibaba/openagentauth/sample/authz/renderer/QwenLlmOperationTextRendererTest.java +++ b/open-agent-auth-samples/sample-authorization-server/src/test/java/com/alibaba/openagentauth/sample/authz/renderer/QwenLlmOperationTextRendererTest.java @@ -364,8 +364,8 @@ void shouldIncludeRequestContextInPrompt() { } @Test - @DisplayName("Should include token expiration in prompt when available") - void shouldIncludeTokenExpirationInPrompt() { + @DisplayName("Should not include token expiration in prompt (policy-only rendering)") + void shouldNotIncludeTokenExpirationInPrompt() { QwenLlmOperationTextRenderer renderer = new QwenLlmOperationTextRenderer(TEST_MODEL, TEST_TIMEOUT); java.time.Instant expiration = java.time.Instant.parse("2026-12-31T23:59:59Z"); @@ -379,7 +379,7 @@ void shouldIncludeTokenExpirationInPrompt() { anyString(), any(TransportOptions.class), any(AssistantContentSimpleConsumers.class))) .thenAnswer(invocation -> { String prompt = invocation.getArgument(0); - assertThat(prompt).contains("Authorization valid until:"); + assertThat(prompt).doesNotContain("Authorization valid until:"); AssistantContentSimpleConsumers consumers = invocation.getArgument(2); simulateTextResponse(consumers, "Rendered"); From e5861ba3c22ed7d9bd862b999effc421978bac14 Mon Sep 17 00:00:00 2001 From: "fudai.yf" Date: Fri, 13 Mar 2026 22:19:04 +0800 Subject: [PATCH 6/6] feat(config): make PAR request expiry configurable --- .../src/main/resources/application.yml | 1 + .../capabilities/OAuth2ServerProperties.java | 34 +++++++++++++++++++ .../AuthorizationServerAutoConfiguration.java | 7 ++-- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/open-agent-auth-samples/sample-authorization-server/src/main/resources/application.yml b/open-agent-auth-samples/sample-authorization-server/src/main/resources/application.yml index 5043ebc..2c6afdc 100644 --- a/open-agent-auth-samples/sample-authorization-server/src/main/resources/application.yml +++ b/open-agent-auth-samples/sample-authorization-server/src/main/resources/application.yml @@ -36,6 +36,7 @@ open-agent-auth: refresh-token-expiry: 2592000 id-token-expiry: 3600 authorization-code-expiry: 600 + par-request-expiry: 600 auto-register-clients: enabled: true clients: diff --git a/open-agent-auth-spring-boot-starter/src/main/java/com/alibaba/openagentauth/spring/autoconfigure/properties/capabilities/OAuth2ServerProperties.java b/open-agent-auth-spring-boot-starter/src/main/java/com/alibaba/openagentauth/spring/autoconfigure/properties/capabilities/OAuth2ServerProperties.java index ce5566d..1b11d27 100644 --- a/open-agent-auth-spring-boot-starter/src/main/java/com/alibaba/openagentauth/spring/autoconfigure/properties/capabilities/OAuth2ServerProperties.java +++ b/open-agent-auth-spring-boot-starter/src/main/java/com/alibaba/openagentauth/spring/autoconfigure/properties/capabilities/OAuth2ServerProperties.java @@ -468,6 +468,22 @@ public static class OAuth2TokenProperties { */ private int authorizationCodeExpiry = 600; + /** + * Pushed Authorization Request (PAR) expiry in seconds. + *

+ * The lifetime of PAR request URIs issued by the authorization server. + * PAR requests must be used within this time window. In flows involving + * user authentication redirects (e.g., to an external IDP), this value + * should be large enough to accommodate the entire authentication flow. + *

+ *

+ * Default value: {@code 600} (10 minutes) + *

+ * + * @see RFC 9126 - OAuth 2.0 Pushed Authorization Requests + */ + private int parRequestExpiry = 600; + /** * Gets the access token expiry in seconds. * @@ -539,6 +555,24 @@ public int getAuthorizationCodeExpiry() { public void setAuthorizationCodeExpiry(int authorizationCodeExpiry) { this.authorizationCodeExpiry = authorizationCodeExpiry; } + + /** + * Gets the PAR request expiry in seconds. + * + * @return the PAR request expiry in seconds + */ + public int getParRequestExpiry() { + return parRequestExpiry; + } + + /** + * Sets the PAR request expiry in seconds. + * + * @param parRequestExpiry the PAR request expiry in seconds to set + */ + public void setParRequestExpiry(int parRequestExpiry) { + this.parRequestExpiry = parRequestExpiry; + } } /** diff --git a/open-agent-auth-spring-boot-starter/src/main/java/com/alibaba/openagentauth/spring/autoconfigure/role/AuthorizationServerAutoConfiguration.java b/open-agent-auth-spring-boot-starter/src/main/java/com/alibaba/openagentauth/spring/autoconfigure/role/AuthorizationServerAutoConfiguration.java index 7310754..8468b0c 100644 --- a/open-agent-auth-spring-boot-starter/src/main/java/com/alibaba/openagentauth/spring/autoconfigure/role/AuthorizationServerAutoConfiguration.java +++ b/open-agent-auth-spring-boot-starter/src/main/java/com/alibaba/openagentauth/spring/autoconfigure/role/AuthorizationServerAutoConfiguration.java @@ -233,9 +233,10 @@ public OAuth2ParRequestValidator parRequestValidator() { @Bean @ConditionalOnMissingBean - public OAuth2ParServer parServer(OAuth2ParRequestStore parRequestStore, OAuth2ParRequestValidator parRequestValidator) { - logger.info("Creating OAuth2ParServer bean"); - return new DefaultOAuth2ParServer(parRequestStore, parRequestValidator); + public OAuth2ParServer parServer(OAuth2ParRequestStore parRequestStore, OAuth2ParRequestValidator parRequestValidator, OpenAgentAuthProperties properties) { + int parRequestExpiry = properties.getCapabilities().getOAuth2Server().getToken().getParRequestExpiry(); + logger.info("Creating OAuth2ParServer bean with PAR request expiry: {} seconds", parRequestExpiry); + return new DefaultOAuth2ParServer(parRequestStore, parRequestValidator, parRequestExpiry); } @Bean