ECL OAuth Authority Profile 1
Requirement-IDs: compiled Version: 1.0 Status: normative profile of ECL-1 Version 1.0
This profile expresses ECL's transaction-bound authority as an OAuth 2.0 access
token, so that a resource server that already accepts OAuth tokens can enforce
an ECL admit without learning DCMP-2. One token expresses one admit. Its
authorization_details entry (RFC 9396) binds the action class, the operation,
the target, the parameter digest and the use limits of that admit; its act
claim (RFC 8693) keeps the actor and the subject of a delegated action apart; its
lifetime lies inside the authorization's window; and it is sender-constrained
with DPoP (RFC 9449) or mutual TLS (RFC 8705) to a key that the governed AI system
does not hold, so a token copied from anywhere cannot be replayed elsewhere.
The profile adds requirements to a claim that declares it. It removes none of the core requirements, of DCMP-2 or of AUTHZEN-BINDING-1 where that profile is also claimed.
Under this profile an action has two boundaries that ECL1N-0185 asks a claim to
name: the authority issuer's issuance of the token, which releases authority and
happens only for a verified admit (OA1-008), and the resource server's
acceptance of the presented token, which releases the action after the checks
of the presentation table (OA1-005, OA1-011); the effect follows at the
resource server.
Pinned sources
RFCs are immutable; the digests identify the plain-text editions at the RFC
Editor. The machine-readable record of these pins is
conformance/binding-sources.json.
| Source | URL | SHA-256 |
|---|---|---|
| RFC 7515, JSON Web Signature | https://www.rfc-editor.org/rfc/rfc7515.txt | ecbf84674118e62526511c1237e1deaa86aada86d25b247623a2a7e04ae62e40 |
| RFC 7518, JSON Web Algorithms | https://www.rfc-editor.org/rfc/rfc7518.txt | 9a9ae524b09ea700ad3f189bac115df95ba69af84b26ffdbe3cdfb8d2152b1fc |
| RFC 7519, JSON Web Token | https://www.rfc-editor.org/rfc/rfc7519.txt | fecd930e9ccf2276b95c0017c6c4ff5d09352e4bc3c7629946447894e0f97248 |
| RFC 7638, JSON Web Key Thumbprint | https://www.rfc-editor.org/rfc/rfc7638.txt | 98b29ab9652070e6a5fec748d43e221bbedc66b6b22e05903fb5366c04ef3110 |
| RFC 7800, Proof-of-Possession Key Semantics for JWTs | https://www.rfc-editor.org/rfc/rfc7800.txt | 051514a781038027c2ac07c26ebe8533556012b2353cdaec8f3baeac53a7c919 |
| RFC 8037, CFRG Elliptic Curve Diffie-Hellman and Signatures in JOSE | https://www.rfc-editor.org/rfc/rfc8037.txt | f14347f9d8eead78ef6e7a1ea3d795d35d664033095a76a80fcd567904aaedaa |
| RFC 8693, OAuth 2.0 Token Exchange | https://www.rfc-editor.org/rfc/rfc8693.txt | 358d658014738bf4dc56c8aae2094ce716ede23b6d5a80488a7dc8e8d72da609 |
| RFC 8705, OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens | https://www.rfc-editor.org/rfc/rfc8705.txt | 6e45a1ee94c6a6a177a4cb077fe67d74bd70c1218c9362f2db2f153239612414 |
| RFC 8707, Resource Indicators for OAuth 2.0 | https://www.rfc-editor.org/rfc/rfc8707.txt | 3b89844a938b9219571931b664ed4a7b380555a4dda1df4e9445a2f72188e76b |
| RFC 8725, JSON Web Token Best Current Practices | https://www.rfc-editor.org/rfc/rfc8725.txt | 737b7013a2495dcfd5481cf4c57c87e2455d537c4cec1140c9096a71fcef0812 |
| RFC 9068, JWT Profile for OAuth 2.0 Access Tokens | https://www.rfc-editor.org/rfc/rfc9068.txt | 68d3ff3910c0f2340fd7b93b430e00053da845b6eebe095ebef0c8bfda5e14c5 |
| RFC 9396, OAuth 2.0 Rich Authorization Requests | https://www.rfc-editor.org/rfc/rfc9396.txt | d6a8f032d8a585daae1c33a8c7b6e539d199f886ec8cc1c7898436f7f2eed29c |
| RFC 9449, OAuth 2.0 Demonstrating Proof of Possession (DPoP) | https://www.rfc-editor.org/rfc/rfc9449.txt | 3842c58e1f6043389416023b9bb8d765048266024982fbbd90640e05943f4e13 |
Version 1.0 capability boundary
The profile deliberately carries no obligations. An OAuth resource server can perform the presentation checks without a DCMP-2 processor, but that does not supply an interoperability contract for fresh state proofs, critical token conditions or proof retrieval at release. Those modes are not defined here. The issuer refuses all such admits before token issuance, even when an earlier proof was valid. This restriction does not exempt a deployment from core release-boundary, control-state, policy or revocation requirements. Offline record checking does not establish that a deployed revocation service works.
Token content
OA1-001§ — An OAuth access token that expresses ECL authority MUST express
exactly one admit, and MUST carry, as a JWT access token (RFC 9068) or in its
introspection response, exactly one authorization_details entry of the type
https://externalcontrollayer.ai/ns/authorization-details/action/1 whose
members equal the admit's authorization and request as the entry table below
gives.
OA1-002§ — The token's aud MUST be exactly the authorization's audience, as
a single value; a client MUST request the token with a resource indicator (RFC
8707) equal to that audience, and a resource server MUST reject a token whose
aud does not name it, with ecl.error.audience-mismatch.v1.
OA1-003§ — The token's validity MUST lie inside the authorization's window:
its nbf, or its iat when it has no nbf, MUST NOT be earlier than
valid_from, its exp MUST NOT be later than valid_until, and its lifetime
from iat to exp MUST be greater than zero and MUST NOT exceed the declared
maximum token lifetime, 300 seconds unless the claim declares a shorter one;
each of iat, nbf and exp MUST be a NumericDate without a fraction, an
integer number of seconds. A resource server MUST reject, with
ecl.error.expired.v1, a token presented before its nbf (or its iat when it
has no nbf) less the declared clock-skew allowance, or at or after its exp
plus that allowance, and, when the token carries one ECL entry, a token
presented outside that entry's window (from valid_from until before
valid_until), to which no allowance applies. The clock-skew allowance is zero
unless the claim declares one, and at most 60 seconds.
The allowance tolerates a resource server whose clock differs a little from the authority issuer's, and the entry's window keeps every accepted presentation inside the ECL authorization even when the allowance reaches past the token's own validity. RFC 7519 allows a NumericDate with a fraction; this profile does not, so that every verifier compares times exactly, and a token or proof whose time has a fraction cannot be read under OA1-007.
Sender constraint and presentation
OA1-004§ — The token MUST be sender-constrained, with cnf.jkt (RFC 9449) or
the cnf member x5t#S256 (RFC 8705), to a key held by the component that
presents it and not readable by the governed AI system, and a resource server
MUST reject a token presented without valid proof of possession. For DPoP the
presentation MUST carry exactly one proof, a JWS whose header has typ
dpop+jwt, an algorithm from the declared asymmetric set of OA1-007 and a jwk
that holds a public key only; whose signature verifies under that jwk; whose
jwk has the RFC 7638 thumbprint cnf.jkt; whose htm equals the request
method and whose htu equals the request URI with its scheme in lowercase,
without its query and fragment and without other normalization (RFC 9449,
Section 4.3); whose iat, an integer number of seconds, lies no more than the
declared proof window before or after the time of presentation, the window being
60 seconds unless the claim declares another of at most 300 seconds; whose ath
is the base64url SHA-256 of the access token; and whose jti, a string of 1 to
256 characters, the resource server has not accepted within that window, a
repeated jti being refused with ecl.error.replay.v1. For mutual TLS the
client certificate's SHA-256 thumbprint MUST equal x5t#S256.
OA1-005§ — A resource server or enforcement point MUST accept the token only
for a request whose target, operation and parameter digest equal the
identifier, the actions member and the parameters_digest of the token's
ECL entry, refusing any other with ecl.error.scope-mismatch.v1, and only within
the entry's use policy, refusing a further use with ecl.error.replay.v1. Every
instance of a resource server that accepts the token MUST count its uses, and
remember the DPoP jti values it accepted, in one store that all of them
share, so that a use or a proof that one instance accepted counts at every
other. An authorization_details entry of another type MUST NOT widen the
authority that the ECL entry expresses.
Delegation
OA1-006§ — When the decision request names a subject distinct from its actor,
the token MUST carry the subject in sub and the actor in the RFC 8693 act
claim (act.sub), with any earlier actors of the delegation as nested act
claims; a token for such an action that lacks act, or whose act.sub differs
from the request's actor, MUST be rejected, and a token exchange MUST NOT widen
the subject token's authority and MUST preserve the members that ECL1N-0067
lists (ECL1N-0031).
Signatures, issuance and records
OA1-007§ — The token MUST be a JWS (RFC 7515) signed with an algorithm from
the declared asymmetric set, with the header typ at+jwt (RFC 9068) and a
kid that names the signing key; the declared asymmetric set is the set of
asymmetric JWS algorithms that the claim declares, and ES256 and EdDSA with the
curve Ed25519 (RFC 8037) when it declares none. A verifier MUST take the
issuer's keys from its own configuration; MUST reject the algorithm none, a
symmetric algorithm, a header without a kid or with a kid it does not know,
a crit header it does not understand (RFC 7515, Section 4.1.11), and a token
whose header carries jwk, jku, x5u or x5c (RFC 8725, Sections 2.1, 3.1
and 3.10, treats none, algorithm substitution, kid, jku and x5u); and
MUST reject a token whose iss is not a configured issuer or whose signature
does not verify under that issuer's configured keys. A verifier MUST refuse as
malformed a token or a DPoP proof with a segment that is not unpadded base64url
(RFC 7515, Section 2) whose unused bits are zero, MUST read the JSON of the
header and payload segments of each under the strict JSON profile of ECL objects
(IE1-002), and MUST refuse as malformed a segment outside that profile or one
that is not a JSON object, such as a segment that repeats a member name or holds
a number with a fraction or an exponent, an integer beyond the exact
interoperable range, NaN, an unpaired surrogate or a byte order mark.
RFC 7515 and RFC 7519 require a segment to be a valid JSON object and let a
verifier either refuse a repeated member name or keep the last one; this profile
refuses it, and reads the rest of a segment as ECL objects are read, so that two
verifiers cannot read one token or proof differently. Base64url text whose
unused bits are not zero decodes to the same bytes as its canonical form, so a
verifier that accepted it would accept two token strings for one signature, of
which the ath of RFC 9449 and the digest of OA1-008 name only one. The
vectors use the default algorithm set. The profile narrows RFC
9068 in two places. The token's typ is the string at+jwt, compared exactly:
a resource server refuses the application/at+jwt that RFC 9068, Section 4 also
lets it accept, and a typ that differs only in letter case, such as the
at+JWT of the example in RFC 9068, Section 3, although media type names are
case-insensitive (RFC 7515, Section 4.1.9). The default set also leaves out
RS256, which RFC 9068, Section 2.1 asks implementations to support; a claim may
declare RS256, which the vectors do not exercise.
OA1-008§ — An authority issuer MUST issue a token only for a verified admit,
never for deny, hold or challenge (DCMP2-014); when the admit's
authorization.token_reference names the token, its digest MUST be the SHA-256
of the token's compact serialization; and when the ECL authorization is revoked,
the token MUST become unusable within the declared revocation propagation bound,
through introspection or a lifetime no longer than that bound. Version 1.0 of
this profile supports only an admit whose obligations list is empty. An issuer
MUST NOT issue a token for any admit carrying an obligation, including one
claimed to have been discharged, and the presentation verifier MUST reject
such an issuance with ecl.rule.oauth-token-expresses-admit.v1. A token, custom
claim or successful issuance MUST NOT be treated as discharging an obligation
at a later release boundary. Actions requiring obligations use a declared
DCMP-2 enforcement path with release-time proofs; they are outside this
profile's Version 1.0 capability. Grant revocation and policy changes MUST
invalidate or narrow authority within their declared propagation bounds; token
lifetime alone is sufficient only when it is within every applicable bound.
OA1-009§ — Where AUTHZEN-BINDING-1 is also claimed, an AuthZEN permit MUST NOT be a reason to issue a token (AZB1-001): the token is issued only from the ECL admit, and an AuthZEN evaluation that a resource server runs when the token is presented MAY only narrow the presented request.
OA1-010§ — A resource server or enforcement point that retains presentation
records for audit MUST write each one as ecl_oauth_presentation_v1, valid
against https://externalcontrollayer.ai/ns/schema/binding/oauth-presentation/1.
Presentation checks and refusals
OA1-011§ — A resource server MUST refuse a presentation that fails a check of
the presentation table below with the code that the table gives for that check,
and a presentation that fails more than one check with the code of the first of
them in the table's order. A presentation record's result MUST be the
resource server's decision: accepted true only for a presentation that passes
every check, and otherwise accepted false with that code in error.
| Order | Check | Requirement | Code |
|---|---|---|---|
| 1 | The token is a compact JWS whose segments the verifier reads under OA1-007, and it passes the header, algorithm, key, signature and issuer checks of OA1-007 | OA1-007 | ecl.error.integrity-invalid.v1 |
| 2 | The token's aud names the resource server |
OA1-002 | ecl.error.audience-mismatch.v1 |
| 3 | The time of presentation lies within the token's validity, with the clock-skew allowance, and within its ECL entry's window | OA1-003 | ecl.error.expired.v1 |
| 4 | The proof of possession passes every check of OA1-004 other than the check that its jti was not accepted before: a DPoP proof that carries a jti string of 1 to 256 characters and binds the request and the token, or the bound client certificate |
OA1-004 | ecl.error.integrity-invalid.v1 |
| 5 | The DPoP proof's jti was not accepted before within the proof window |
OA1-004 | ecl.error.replay.v1 |
| 6 | The presented target, operation and parameter digest equal those of the token's one ECL entry | OA1-005 | ecl.error.scope-mismatch.v1 |
| 7 | The use lies within the entry's use policy | OA1-005 | ecl.error.replay.v1 |
The checks of the issuer's side (OA1-001, OA1-006 and OA1-008) need the ECL request and response, which a resource server need not hold, so they are not in the table; the profile's vectors check them on the record as the issuer's obligations.
Entry table
| ECL entry member | Value |
|---|---|
type |
https://externalcontrollayer.ai/ns/authorization-details/action/1 |
locations |
[authorization.audience] |
actions |
[request.action.operation] |
identifier |
request.action.target.uri |
authorization_id |
authorization.authorization_id |
request_id |
request.message_id, which the response carries as request_id |
action_id |
request.action.action_id |
action_class |
request.action.action_class |
scope_digest |
authorization.scope_digest (DCMP2-044) |
parameters_digest |
request.action.parameters_digest |
use_policy |
authorization.use_policy |
max_uses |
authorization.max_uses, when present |
valid_from, valid_until |
The authorization's window |
decision |
When the token is issued after the response: uri the response's message_id and digest its digest |
Claim table (informative)
A JWT access token carries every claim that RFC 9068, Section 2.2 requires,
among them iss, client_id and jti, each a string (OA1-001). The table
gives the value of each claim for an ECL admit.
| JWT claim | Value |
|---|---|
iss |
The authority issuer, the principal that holds the token-signing key |
sub |
request.subject.id, or request.actor.id when the request names no subject |
act |
{"sub": request.actor.id} when the subject differs from the actor |
aud |
authorization.audience |
iat, nbf |
Integer seconds, not earlier than authorization.valid_from |
exp |
Integer seconds, not later than authorization.valid_until |
jti |
Unique per token |
client_id |
The enforcement point or gateway that presents the token |
cnf |
{"jkt": ...} or {"x5t#S256": ...} |
authorization_details |
[ECL entry] |
The scope_digest already binds the request identifier and the complete action,
so a resource server that receives the request and action can check the binding
again; one that receives only the target and parameters checks the members of
the entry.
Presentation record (informative)
An ecl_oauth_presentation_v1 record holds one presentation: the issuer and
the issuer_keys that the verifier configures; the resource_server that
received the presentation; the declared maximum token lifetime, DPoP proof
window and clock-skew allowance (max_token_lifetime_seconds,
dpop_proof_window_seconds and clock_skew_seconds, each its default when
absent); the DCMP-2 request and response the token expresses; the token;
the presentation with the HTTP method and URI, the target, operation and
parameter digest presented, the time, the number of uses with this one, the DPoP
proof or the client certificate thumbprint, and the DPoP jti values that the
resource server accepted within the proof window before this presentation
(seen_dpop_jti); and the result, which names the code of OA1-011 for a
refusal. The rules registered for this profile, whose names begin
ecl.rule.oauth-, check a record after its schema: the issuer's side always,
the checks of the presentation table when the record accepts the presentation,
and the code of the first check that fails when it refuses it.
Security considerations
A bearer token in an agent's context is a credential that the agent can leak or
misuse, which is the gap that ECL1N-0033 and the direct-access control gap (G2)
describe. Binding the token to a proof-of-possession key that the governed AI
system cannot read makes a copied token useless, and binding it to one admit and
one parameter digest makes a stolen proof and token usable only for the action
already admitted, once, before exp. The verifier's own key configuration
closes the attacks that take a key or an algorithm from the token's own header,
such as the none and algorithm-substitution attacks of RFC 8725, Section 2.1,
and the kid, jku and x5u misuse of its Section 3.10, and reading every
segment under the strict JSON profile leaves no room for two verifiers to read
one token differently. Replay protection holds only as far as the use counts and
the remembered jti values reach, which is why every instance of a resource
server shares them (OA1-005). Token exchange across trust domains is where
authority is most often widened by interpretation; ECL1N-0031 requires a denial
when the intersection with the parent authority cannot be shown.
Conformance
| Kind | What the citation establishes |
|---|---|
| Vectors | The oauth-authority suite refuses a token issued for a non-admit response, with a lifetime beyond the window or of zero, missing the act claim of a delegated action or the jti claim, expressing other parameters, whose entry holds the max_uses true where the admit grants one use or a decision of null, or not sender-constrained, among them a token bound to an x5t#S256 of null and presented with no client certificate; a resource server that accepts a token signed with none, with a key its own header carries or with Ed448, a token whose header gives alg as a list or names no kid, a token whose signature segment is not canonical base64url, a token whose claims hold a NumericDate with a fraction or an integer beyond the exact range, a token for another audience, an expired token, a token presented within the clock-skew allowance but after its entry's window, a proof from another key, a proof that carries the bound key but whose signature does not verify, a proof that carries no jti or an empty one, a replayed proof, another target, or a second use; and a refusal with another code than the first failing check gives, among them a refusal as a replay of a proof whose jti has more than 256 characters. It accepts a presentation within the declared clock-skew allowance and a refusal with the code of the first failing check |
| Assessor procedure | Key custody for the proof-of-possession key, revocation propagation, resource-indicator use and token exchange policy |
No live authorization server is used: the tokens of the oauth-authority
suite are signed with test keys that each presentation record names as the
issuer's keys.