<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE rfc [
<!ENTITY nbsp    "&#160;">
<!ENTITY zwsp   "&#8203;">
<!ENTITY nbhy   "&#8209;">
<!ENTITY wj     "&#8288;">
]>
<rfc xmlns:xi="http://www.w3.org/2001/XInclude" ipr="trust200902" docName="draft-forten-oauth-sd-jwt-access-token-00" category="exp" consensus="true" submissionType="IETF" tocInclude="true" sortRefs="true" symRefs="true" version="3">

  <front>
    <title abbrev="SD for JWT Access Tokens">Selective Disclosure for JWT Access Tokens Without Changing the Token</title>
    <seriesInfo name="Internet-Draft" value="draft-forten-oauth-sd-jwt-access-token-00"/>
    <author initials="S. F." surname="Lee" fullname="Su-hyeon Forten Lee">
      <address>
        <email>nine01223@naver.com</email>
      </address>
    </author>
    <date/>
    <area>Security</area>
    <workgroup>Web Authorization Protocol</workgroup>
    <keyword>access token</keyword>
    <keyword>selective disclosure</keyword>
    <keyword>SD-JWT</keyword>
    <keyword>DPoP</keyword>

    <abstract>
      <t>This document adds selective disclosure to JWT access tokens without changing the form of the Authorization header or how the token is validated. The RFC 9068 token is sent as today, with some of its claims selectively disclosable as defined by SD-JWT (RFC 9901); the Disclosures and the optional Key Binding JWT travel in two new HTTP fields. A recipient that does not implement this profile ignores the fields and processes the token as an ordinary JWT access token. The token itself then carries no selectively disclosable value, and the holder chooses per request which values to reveal.</t>
    </abstract>

    <note removeInRFC="true">
      <name>About This Document</name>
      <t>Discussion of this document takes place on the Web Authorization Protocol Working Group mailing list (<eref target="mailto:oauth@ietf.org"/>), which is archived at <eref target="https://mailarchive.ietf.org/arch/browse/oauth/"/>.</t>
    </note>
  </front>

  <middle>

    <section anchor="introduction">
      <name>Introduction</name>
      <t><xref target="RFC9068" section="2.2.2" sectionFormat="of"/> notes that authorization servers commonly put resource owner attributes in JWT access tokens. A token that carries such attributes by value exposes them wherever the token goes: in token stores and logs, in every resource server it names, and in any place it is forwarded. Encrypting the token <xref target="RFC7516"/> hides the values but also stops any party without the key from validating it.</t>
      <t>Issuing a separate minimal token per resource server <xref target="RFC8707"/>, or an opaque token that a gateway exchanges for an internal JWT, both answer this by multiplying tokens or by adding an exchange. Selective disclosure <xref target="RFC9901"/> instead separates the two inside one token: the token carries only digests, and the holder presents the values it chooses, per request and per resource server, as an agent calling several resource servers on a user's behalf does. The holder, here the client, does receive every value; where the client itself must not see them, encryption remains the only answer. This document applies it to the <xref target="RFC9068"/> token with the token in the Authorization header unchanged, so that existing infrastructure processes it as before. The Disclosures and the Key Binding JWT travel in separate HTTP fields, as the DPoP proof does in <xref target="RFC9449"/>; those fields are visible to intermediaries, and what the profile protects is the token, not the request (<xref target="security"/>).</t>
      <t>This profile covers access tokens presented to a resource server over HTTP. It defines no credential format; <xref target="I-D.ietf-oauth-sd-jwt-vc"/> covers that case.</t>
    </section>

    <section anchor="conventions">
      <name>Conventions and Definitions</name>
      <t>The key words "<bcp14>MUST</bcp14>", "<bcp14>MUST NOT</bcp14>", "<bcp14>REQUIRED</bcp14>", "<bcp14>SHALL</bcp14>", "<bcp14>SHALL NOT</bcp14>", "<bcp14>SHOULD</bcp14>", "<bcp14>SHOULD NOT</bcp14>", "<bcp14>RECOMMENDED</bcp14>", "<bcp14>NOT RECOMMENDED</bcp14>", "<bcp14>MAY</bcp14>", and "<bcp14>OPTIONAL</bcp14>" in this document are to be interpreted as described in BCP&nbsp;14 <xref target="RFC2119"/> <xref target="RFC8174"/> when, and only when, they appear in all capitals, as shown here.</t>
      <t>"Issuer-signed JWT", "Disclosure", "Key Binding JWT" (KB-JWT), "SD-JWT" and "SD-JWT+KB" are as defined in <xref target="RFC9901"/>. "Token" means an Issuer-signed JWT that satisfies <xref target="format"/>.</t>
    </section>

    <section anchor="format">
      <name>Token Format</name>
      <t>A Token is a JWT access token <xref target="RFC9068"/> whose payload <bcp14>MAY</bcp14> contain, at the top level only, the <tt>_sd</tt> and <tt>_sd_alg</tt> claims of <xref target="RFC9901" section="4" sectionFormat="of"/>; the <tt>...</tt> array elements, nested <tt>_sd</tt> claims and the recursive Disclosures of <xref target="RFC9901" section="4.2.6" sectionFormat="of"/> <bcp14>MUST NOT</bcp14> be used. Every Disclosure then names a top-level claim and stands on its own. The header is that of <xref target="RFC9068" section="2.1" sectionFormat="of"/>, including <tt>typ</tt> <tt>at+jwt</tt>. <xref target="RFC9901" section="9.11" sectionFormat="of"/> recommends a distinct <tt>typ</tt> for SD-JWT profiles; this profile keeps the <xref target="RFC9068"/> value because <xref target="RFC9068" section="4" sectionFormat="of"/> requires resource servers to reject any other, and because keeping it is what leaves existing infrastructure unchanged. What <xref target="RFC9901" section="9.11" sectionFormat="of"/> guards against is a JWT being taken for another kind; a recipient unaware of this profile taking a Token for an access token is the intended behavior. A recipient recognizes a selectively disclosable Token by the presence of <tt>_sd</tt> in its payload, which it sees once it has validated the Token as any JWT access token.</t>
      <t>The following claims, with their contents, <bcp14>MUST</bcp14> be in the clear and <bcp14>MUST NOT</bcp14> be selectively disclosable: <tt>iss</tt>, <tt>exp</tt>, <tt>aud</tt>, <tt>sub</tt>, <tt>client_id</tt>, <tt>iat</tt>, <tt>jti</tt>, <tt>cnf</tt>, and <tt>scope</tt> and <tt>nbf</tt> when present. These are the claims that the processing defined by <xref target="RFC9068"/>, <xref target="RFC9449"/> and this profile depends on, and the set satisfies <xref target="RFC9901" section="9.7" sectionFormat="of"/>. The same applies to any claim whose absence would widen what the Token permits, such as <tt>act</tt> <xref target="RFC8693"/> with everything nested in it; a resource server <bcp14>MUST NOT</bcp14> read an undisclosed claim as permission. Any other top-level claim <bcp14>MAY</bcp14> be selectively disclosable. A Token with no selectively disclosable claim is an ordinary <xref target="RFC9068"/> token.</t>
    </section>

    <section anchor="response">
      <name>Token Response</name>
      <t>Whenever a token response <xref target="RFC6749"/> delivers a Token that has selectively disclosable claims, the authorization server <bcp14>MUST</bcp14> include a <tt>disclosures</tt> parameter whose value is a JSON array of the Disclosure strings issued with, and committed to by digest in, that Token. This holds for a refresh as for the initial issuance, and Disclosures are used only with the Token they were returned with. They are returned apart from <tt>access_token</tt> so that its value stays a JWT that recipients unaware of this profile can parse.</t>
      <t>A client that does not implement this profile ignores the parameter, as <xref target="RFC6749" section="5.1" sectionFormat="of"/> provides for parameters it does not understand, and presents the Token alone; the resource server then sees only the cleartext claims. An authorization server therefore issues a selectively disclosable Token only to a client it knows to implement this profile; how it knows is outside this document.</t>
      <t>This associates the Disclosures with the Token on the client side; the cryptographic binding remains the digests in the Token. The client <bcp14>MUST NOT</bcp14> inspect the Token (<xref target="RFC9068" section="6" sectionFormat="of"/>) and need not: each Disclosure carries its claim name and value (<xref target="RFC9901" section="4.2" sectionFormat="of"/>), so the client selects by claim name among those it received with the Token. It does not perform the Holder processing of <xref target="RFC9901" section="7.2" sectionFormat="of"/>; the Token is its own authorization server's, presented opaquely as any access token is.</t>
    </section>

    <section anchor="presentation">
      <name>Presentation</name>

      <section anchor="fields">
        <name>HTTP Fields</name>
        <t>Two Structured Fields <xref target="RFC9651"/> are defined.</t>
        <dl>
          <dt><tt>SD-JWT-Disclosures</tt></dt>
          <dd>A List of Strings, each one Disclosure in its base64url form. Order is significant. The field <bcp14>MAY</bcp14> be split across several field lines, which recipients combine in order as <xref target="RFC9110" section="5.3" sectionFormat="of"/> specifies; a field with no members is omitted rather than sent empty.</dd>
          <dt><tt>SD-JWT-Key-Binding</tt></dt>
          <dd>An Item that is a String, one KB-JWT in compact serialization.</dd>
        </dl>
        <t>The <em>reassembled SD-JWT</em> of a request is the Token, a tilde, and each member of <tt>SD-JWT-Disclosures</tt> in order, each followed by a tilde: the serialization of <xref target="RFC9901" section="4" sectionFormat="of"/> and the input to <tt>sd_hash</tt> (<xref target="RFC9901" section="4.3.1" sectionFormat="of"/>). An absent <tt>SD-JWT-Disclosures</tt> field means zero Disclosures, and the reassembled SD-JWT is then the Token followed by a tilde. Appending the KB-JWT gives the SD-JWT+KB.</t>
      </section>

      <section anchor="send">
        <name>Sending</name>
        <t>The client sends the Token in the Authorization header with the DPoP scheme and a DPoP proof, as <xref target="RFC9449"/> specifies, with nothing attached. In <tt>SD-JWT-Disclosures</tt> it sends any subset of the Disclosures received with that Token (<xref target="response"/>), each at most once. Which Disclosures a resource server needs, and whether it implements this profile at all, is something the client knows about that resource server, as it knows which scope to request; this profile defines no way to ask, and a client <bcp14>SHOULD NOT</bcp14> send Disclosures to a resource server it does not know to implement it. How it knows is outside this document; the resource server's metadata or the client's registration with the authorization server are natural places.</t>
        <t>A KB-JWT, if sent, satisfies <xref target="RFC9901" section="4.3" sectionFormat="of"/>: <tt>sd_hash</tt> is computed over the reassembled SD-JWT, <tt>aud</tt> identifies the resource server by the identifier it expects in the Token's <tt>aud</tt>, and <tt>nonce</tt> is the DPoP nonce the resource server provided (<xref target="RFC9449" section="9" sectionFormat="of"/>). A resource server that requires a KB-JWT therefore provides DPoP nonces.</t>
      </section>

      <section anchor="receive">
        <name>Receiving</name>
        <t>A resource server that implements this profile:</t>
        <ol>
          <li>Validates the Token and the DPoP proof as <xref target="RFC9068"/> and <xref target="RFC9449"/> specify. This step is unchanged from any DPoP-bound JWT access token and <bcp14>MUST</bcp14> succeed first. The DPoP <tt>ath</tt> covers the Token alone (<xref target="RFC9449" section="4.3" sectionFormat="of"/>), not the Disclosures.</li>
          <li>Verifies the reassembled SD-JWT, with the KB-JWT appended if present, as <xref target="RFC9901" section="7.3" sectionFormat="of"/> specifies, and uses the Processed SD-JWT Payload. This holds with zero Disclosures too, where a KB-JWT alone forms the SD-JWT+KB of <xref target="RFC9901" section="4" sectionFormat="of"/>, though it then adds nothing beyond the DPoP proof. A failure yields <tt>invalid_token</tt> (<xref target="RFC9068" section="4" sectionFormat="of"/>).</li>
        </ol>
        <t>Whether a KB-JWT is required is resource server policy and, as <xref target="RFC9901" section="7.3" sectionFormat="of"/> requires, <bcp14>MUST NOT</bcp14> depend on whether one was received; a resource server that requires one rejects a request without it. The key that verifies the KB-JWT is the <tt>jwk</tt> in the header of the DPoP proof, whose thumbprint <xref target="RFC7638"/> <bcp14>MUST</bcp14> equal the Token's <tt>cnf.jkt</tt> (<xref target="RFC9449" section="6.1" sectionFormat="of"/>).</t>
        <t>A resource server that does not implement this profile ignores both fields, as <xref target="RFC9110"/> permits, and sees only the cleartext claims. So does one that relies on token introspection <xref target="RFC7662"/> instead of validating the Token itself, since the introspection response carries no Disclosure and an authorization server <bcp14>MUST NOT</bcp14> put selectively disclosable values in one, so such a resource server gains nothing from this profile.</t>
      </section>
    </section>

    <section anchor="security">
      <name>Security Considerations</name>
      <t>This profile invites Tokens to be stored and logged as less sensitive than before. A bearer Token would turn every such place into a credential leak, which is why Tokens under this profile <bcp14>MUST</bcp14> be DPoP-bound <xref target="RFC9449"/>: only the holder of the key can use one. The KB-JWT does not replace DPoP here, because intermediaries do not verify it. <xref target="RFC8725"/> applies to the Token, the KB-JWT and the DPoP proof.</t>
      <t>This profile protects the Token, not the request. An intermediary that can read the request can read <tt>SD-JWT-Disclosures</tt>; confidentiality from such a party is provided by the transport, as <xref target="RFC9901" section="10.3" sectionFormat="of"/> notes, and not by this profile. A deployment that needs it beyond the transport could carry each Disclosure as a JWE <xref target="RFC7516"/> for the resource server, which decrypts it before reassembly; this is left to future work. What the profile ensures is that the Token itself, when stored, logged, introspected or forwarded, does not contain the selectively disclosable values; those travel only in the Disclosures.</t>
      <t>An intermediary can also remove <tt>SD-JWT-Disclosures</tt> or members of it. Only a KB-JWT protects the set of Disclosures (<xref target="RFC9901" section="9.10" sectionFormat="of"/>); without one, the resource server cannot distinguish a client that disclosed nothing from a field that was removed, so a resource server behind intermediaries <bcp14>SHOULD</bcp14> require a KB-JWT. Disclosures cannot be forged or substituted, since each is committed to by a digest in the Token and <xref target="RFC9901" section="7.1" sectionFormat="of"/> rejects any that is not.</t>
      <t>The two fields carry the values this profile keeps out of the Token. They <bcp14>MUST</bcp14> be handled like the Authorization field: excluded from access logs, traces, request dumps, monitoring and CDN logs, and from any shared cache (<xref target="RFC9901" section="10.2" sectionFormat="of"/>). Infrastructure unaware of this profile will not do so, since logging configurations typically redact fields by name; this is the reason for the rule in <xref target="send"/> that Disclosures go only to resource servers known to implement the profile.</t>
    </section>

    <section anchor="privacy">
      <name>Privacy Considerations</name>
      <t>This profile hides values from the Token, not from the client, which receives every Disclosure and <bcp14>SHOULD</bcp14> store them no longer than the Token (<xref target="RFC9901" section="10.2" sectionFormat="of"/>). It offers none of the unlinkability discussed in <xref target="RFC9901" section="10.1" sectionFormat="of"/>: the Token's <tt>jti</tt>, <tt>sub</tt> and digests are the same in every request. Where a pairwise <tt>sub</tt> is used (<xref target="RFC9068" section="6" sectionFormat="of"/>), it and decoy digests (<xref target="RFC9901" section="4.2.5" sectionFormat="of"/>) limit what a resource server learns beyond what is disclosed to it.</t>
    </section>

    <section anchor="iana">
      <name>IANA Considerations</name>
      <t>This document requests registration of <tt>SD-JWT-Disclosures</tt> and <tt>SD-JWT-Key-Binding</tt> as permanent entries in the "Hypertext Transfer Protocol (HTTP) Field Name Registry" <xref target="RFC9110"/>, with <xref target="fields"/> of this document as the reference, and of <tt>disclosures</tt> in the "OAuth Parameters" registry <xref target="RFC6749"/> for use in token responses, described as "JSON array of the SD-JWT Disclosures issued with the access token in the same response", with <xref target="response"/> as the reference. No JWT claims or header parameters are registered.</t>
    </section>

  </middle>

  <back>

    <references>
      <name>References</name>
      <references>
        <name>Normative References</name>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.2119.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8174.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.6749.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.7638.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9651.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9068.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9110.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9449.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9901.xml"/>
      </references>
      <references>
        <name>Informative References</name>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.7516.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8693.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8707.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.7662.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8725.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml3/reference.I-D.ietf-oauth-sd-jwt-vc.xml"/>
      </references>
    </references>

    <section anchor="example">
      <name>Example</name>
      <t>Token payload with <tt>email</tt> and <tt>name</tt> selectively disclosable (digests and Disclosures below are placeholders):</t>
      <sourcecode type="json"><![CDATA[
{
  "iss": "https://as.example.com",
  "sub": "https://as.example.com/users/9f1c2d",
  "aud": "https://api.example.com",
  "client_id": "agent-7f3a",
  "scope": "files:read",
  "iat": 1790640000,
  "exp": 1790643600,
  "jti": "urn:uuid:3b1c9f2e-7a41-4d0e-9c55-1f0a2b6d8e71",
  "cnf": { "jkt": "0ZcOCORZNYy-DWpqq30jZyJGHTN0d2HglBV3uiguA4I" },
  "_sd": [
    "CrQe7S5kqBAHt-nMYXgc6bdt2SH5aTY1sU_M-PgkjPI",
    "JzYjH4svliH0R3PyEMfeZu6Jt69u5qehZo7F7EPYlSE"
  ],
  "_sd_alg": "sha-256"
}
]]></sourcecode>
      <t>Token response delivering it:</t>
      <sourcecode type="json"><![CDATA[
{
  "access_token": "eyJhbGciOiJFUzI1NiIsInR5cCI6ImF0K2p3dCIsImtpZ...",
  "token_type": "DPoP",
  "expires_in": 3600,
  "disclosures": [
    "WyI2SWo3dE0tYTVpVlBHYm9TNXRtdlZBIiwgImVtYWls...",
    "WyJlbHVWNU9nM2dTTklJOEVZbnN4QV9BIiwgIm5hbWUi..."
  ]
}
]]></sourcecode>
      <t>Request disclosing <tt>email</tt> and withholding <tt>name</tt>:</t>
      <sourcecode type="http-message"><![CDATA[
GET /files HTTP/1.1
Host: api.example.com
Authorization: DPoP eyJhbGciOiJFUzI1NiIsInR5cCI6ImF0K2p3dCIsImtpZ...
DPoP: eyJhbGciOiJFUzI1NiIsInR5cCI6ImRwb3Arand0IiwiandrIjp7Imt0...
SD-JWT-Disclosures: "WyI2SWo3dE0tYTVpVlBHYm9TNXRtdlZBIiwgImVtYWls..."
SD-JWT-Key-Binding: "eyJhbGciOiJFUzI1NiIsInR5cCI6ImtiK2p3dCJ9.eyJu..."
]]></sourcecode>
      <t>A gateway in front of <tt>api.example.com</tt> validates the first two headers as for any DPoP-bound access token. The Token it validates, stores or logs as a token carries the digest of the email address, not the address; the address is in the <tt>SD-JWT-Disclosures</tt> field, which the gateway forwards without processing.</t>
    </section>

    <section anchor="open-issues" removeInRFC="true">
      <name>Open Issues</name>
      <ul>
        <li>Whether the KB-JWT is redundant beside DPoP. An alternative is a claim in the DPoP proof carrying the hash of the reassembled SD-JWT, which binds the disclosed set to the request with one signature and no nonce. Whether <xref target="RFC9449" section="4.2" sectionFormat="of"/> permits additional claims has to be settled first.</li>
        <li>Whether keeping <tt>typ</tt> <tt>at+jwt</tt> is acceptable against <xref target="RFC9901" section="9.11" sectionFormat="of"/>.</li>
        <li>How a resource server states that it implements this profile and which Disclosures it needs (its metadata, or a <tt>WWW-Authenticate</tt> challenge; no existing error code fits "valid Token, required claim not disclosed"), and how a client states to the authorization server that it implements it (see <xref target="send"/>).</li>
      </ul>
    </section>

  </back>
</rfc>
