<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE rfc [
  <!ENTITY nbsp "&#160;">
]>
<rfc xmlns:xi="http://www.w3.org/2001/XInclude"
     category="std"
     docName="draft-bandyopadhayaya-oauth-push-device-00"
     ipr="trust200902"
     obsoletes=""
     updates=""
     submissionType="IETF"
     xml:lang="en"
     version="3">

  <front>
    <title abbrev="OAuth Push Device Discovery">Discovery and Device Lifecycle for OAuth Push-Based Authentication</title>
    <seriesInfo name="Internet-Draft" value="draft-bandyopadhayaya-oauth-push-device-00"/>

    <author fullname="Jyotirmoy Bandyopadhayaya" initials="J." surname="Bandyopadhayaya">
      <organization>LoginRadius</organization>
      <address>
        <email>jyotirmoy.bandyopadhayaya@loginradius.com</email>
      </address>
    </author>

    <date year="2026" month="July" day="29"/>

    <area>Security</area>
    <workgroup>Web Authorization Protocol</workgroup>

    <keyword>OAuth</keyword>
    <keyword>push authentication</keyword>
    <keyword>CIBA</keyword>
    <keyword>device registration</keyword>
    <keyword>attestation</keyword>

    <abstract>
      <t>
        This document defines a discovery, registration, attestation, and
        device-lifecycle layer for out-of-band OAuth 2.0 authenticator
        devices, allowing a single spec-compliant authenticator application
        to register with, and receive push-based wake signals from, any
        conforming Authorization Server, without requiring the
        Authorization Server operator to build and distribute its own
        dedicated mobile application. This document is intentionally
        agnostic to which back-channel authentication protocol ultimately
        consumes it; OpenID Connect Client-Initiated Backchannel
        Authentication (CIBA) Core is its first binding, defined in a
        separate companion document, but nothing in this document depends
        on CIBA or on OpenID Connect.
      </t>
    </abstract>

  </front>

  <middle>

    <section anchor="intro" numbered="true" toc="default">
      <name>Introduction</name>
      <t>
        Push-based multi-factor authentication (MFA) today is implemented
        as a closed, vertically integrated stack per Authorization Server
        (AS) vendor: a proprietary API, a proprietary push backend, and a
        proprietary mobile application. This produces three compounding
        costs for application and AS developers:
      </t>
      <ol type="1">
        <li>
          <t><strong>Vendor lock-in</strong> — the authenticator
          application only speaks to the AS that shipped it.</t>
        </li>
        <li>
          <t><strong>Fragmentation</strong> — no shared registration
          ceremony exists, unlike <xref target="RFC6238"/>
          (TOTP), where any application can generate codes for any AS from
          a shared secret.</t>
        </li>
        <li>
          <t><strong>Developer cost</strong> — offering push MFA
          requires building and maintaining a full mobile application, not
          just a backend integration.</t>
        </li>
      </ol>
      <t>
        This document defines a registration and device-lifecycle layer
        that removes these costs by letting any spec-compliant
        authenticator application serve push MFA for any spec-compliant
        AS.
      </t>

      <section anchor="terminology" numbered="true" toc="default">
        <name>Terminology</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>
        <dl newline="false">
          <dt>AS</dt><dd>Authorization Server.</dd>
          <dt>RP</dt><dd>Relying Party.</dd>
          <dt>Authenticator</dt><dd>The generic, spec-compliant push-MFA
            client application defined by this document.</dd>
          <dt>Relay</dt><dd>The authenticator vendor's shared platform-push
            (APNs/FCM) integration point.</dd>
          <dt>Consuming protocol</dt><dd>A back-channel authentication
            protocol (e.g. OIDC CIBA Core) that wakes a device registered
            under this document via a companion binding specification.</dd>
        </dl>
      </section>
    </section>

    <section anchor="relationship" numbered="true" toc="default">
      <name>Relationship to Existing Work</name>
      <t>
        <xref target="RFC9126"/> (Pushed Authorization Requests, PAR)
        covers moving authorization request parameters to a back-channel
        POST before redirect. It does not address push delivery or
        out-of-band devices. This document does not depend on PAR or its
        <tt>pushed_authorization_request_endpoint</tt>; <xref
        target="registration"/> instead borrows PAR's design pattern
        (pushing sensitive parameters over a back-channel POST and
        receiving a short-lived opaque reference in return) as its own
        self-contained mechanism.
      </t>
      <t>
        <xref target="RFC8414"/> (OAuth 2.0 Authorization Server Metadata)
        establishes the pattern this document follows for its own
        discovery document: a dedicated well-known metadata document with
        additive-only evolution and no explicit version field (<xref
        target="versioning"/>).
      </t>
      <t>
        OIDC CIBA Core <xref target="OIDC.CIBA"/> defines
        <tt>/bc-authorize</tt>, <tt>auth_req_id</tt>, and the poll/ping/push
        delivery modes. This document does not reference or depend on
        CIBA; a device registered per this specification is usable by any
        back-channel authentication protocol that defines a binding to it.
        A companion document defines the CIBA binding: an
        <tt>interaction_type</tt> request parameter on
        <tt>/bc-authorize</tt> (<xref target="interaction-type"/>) and a
        <tt>device_revoked</tt> error extending CIBA's error surface (<xref
        target="revocation"/>). This split follows the precedent of
        WebAuthn <xref target="WebAuthn2"/> (a browser-facing API defined
        by the W3C) and CTAP (an authenticator-facing protocol defined by
        the FIDO Alliance) as companion specifications from different
        bodies serving different interfaces to the same underlying model.
      </t>
      <t>
        This document defines the registration and lifecycle layer; it
        takes no position on, and places no requirements on, the
        authentication protocol that ultimately wakes a registered device.
      </t>
    </section>

    <section anchor="discovery" numbered="true" toc="default">
      <name>Discovery Document</name>
      <t>
        A conforming AS publishes a discovery document at a dedicated
        well-known URI <xref target="RFC8615"/>, following the <xref
        target="RFC8414"/> pattern rather than extending
        <tt>/.well-known/openid-configuration</tt>. This keeps the
        push-auth metadata document independently versionable and
        cacheable, at the cost of a second discovery round trip for
        clients that need both documents. A CIBA-binding AS additionally
        publishes CIBA's own discovery fields per the companion binding
        document; the two documents are independent but <bcp14>MAY</bcp14>
        be operated by the same AS simultaneously.
      </t>
      <sourcecode type="http">
GET /.well-known/oauth-push-notification HTTP/1.1
Host: oauth.example.com
      </sourcecode>
      <sourcecode type="json"><![CDATA[
{
  "registration_endpoint":
    "https://oauth.example.com/push/register",
  "delivery_modes_supported": ["push", "ping", "poll"],
  "interaction_type_supported":
    ["boolean", "number_choose", "input_manual"],
  "minimum_interaction_type": "number_choose",
  "registration_token_ttl": 300,
  "signing_alg_values_supported": ["ES256"],
  "push_relay_endpoint":
    "https://relay.authenticator-vendor.com/notify",
  "fallback_order": ["push", "poll"],
  "client_attestation_required": true,
  "trusted_authenticator_registries": [
    "https://mds.push-auth-wg.org/v1",
    "https://mds-backup.push-auth-wg.org/v1"
  ],
  "minimum_attestation_level": "certified",
  "revocation_endpoint":
    "https://oauth.example.com/push/revoke",
  "key_rotation_endpoint":
    "https://oauth.example.com/push/rotate-key",
  "device_management_endpoint":
    "https://oauth.example.com/push/devices",
  "max_devices_per_user": 5,
  "push_wait_timeout_seconds": 10,
  "error_response_format": "application/problem+json"
}
      ]]></sourcecode>
      <t>
        Fields are flattened, not nested, to match established discovery
        document convention and to simplify JSON Schema validation. The
        document <bcp14>SHOULD</bcp14> be published against a formal JSON
        Schema (2020-12 <xref target="JSON.SCHEMA"/>); field registration
        follows an IANA registry established by this document (<xref
        target="iana-metadata"/>), so new fields, including
        protocol-binding-specific ones defined by companion documents,
        can be added without a version bump.
      </t>

      <section anchor="versioning" numbered="true" toc="default">
        <name>Versioning</name>
        <t>
          This document defines no explicit <tt>version</tt> field.
          Following <xref target="RFC8414"/> precedent, evolution is
          purely additive: new capabilities are advertised via new
          <tt>_supported</tt> arrays or new optional fields, and
          conformant clients <bcp14>MUST</bcp14> ignore members they do
          not recognize. A version field would solve a breaking-change
          problem this specification does not yet have; one can be
          introduced later without disruption if that changes.
        </t>
      </section>

      <section anchor="error-format" numbered="true" toc="default">
        <name>Error Response Format</name>
        <t>
          All endpoints defined in this document (<tt>registration_endpoint</tt>,
          <tt>revocation_endpoint</tt>, <tt>key_rotation_endpoint</tt>,
          <tt>device_management_endpoint</tt>) return errors as
          <tt>application/problem+json</tt> per <xref target="RFC9457"/>,
          not the bare OAuth <tt>error</tt>/<tt>error_description</tt>
          shape from <xref target="RFC6749" section="5.2"/>: these
          endpoints need extension members that shape does not
          accommodate cleanly.
        </t>
        <sourcecode type="json"><![CDATA[
{
  "type":
   "https://push-auth-wg.org/errors/registration_token_already_used",
  "title": "Registration token already used",
  "status": 400,
  "detail":
    "This registration token has already been redeemed.",
  "device_id": null
}
        ]]></sourcecode>
        <table anchor="error-registry">
          <name>Error Registry</name>
          <thead>
            <tr>
              <th>Type slug</th>
              <th>Status</th>
              <th>Meaning</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>registration_token_already_used</td>
              <td>400</td>
              <td>Token already redeemed (register)</td>
            </tr>
            <tr>
              <td>registration_token_expired</td>
              <td>400</td>
              <td>Token used past its TTL (register)</td>
            </tr>
            <tr>
              <td>attestation_missing</td>
              <td>400</td>
              <td>Attestation required but absent (register)</td>
            </tr>
            <tr>
              <td>attestation_invalid</td>
              <td>403</td>
              <td>Attestation failed verification (register)</td>
            </tr>
            <tr>
              <td>attestation_untrusted</td>
              <td>403</td>
              <td>Issuer not trusted/below minimum level (register)</td>
            </tr>
            <tr>
              <td>interaction_type_unsupported</td>
              <td>400</td>
              <td>Device below the AS's minimum ceremony (register)</td>
            </tr>
            <tr>
              <td>nonce_replayed</td>
              <td>400</td>
              <td>Replay-Nonce reused/missing (revoke, rotate)</td>
            </tr>
            <tr>
              <td>rate_limited</td>
              <td>429</td>
              <td>Too many requests (register, revoke, rotate)</td>
            </tr>
          </tbody>
        </table>
        <t>
          A consuming protocol's binding document <bcp14>MAY</bcp14>
          define additional error codes for its own wake surface (e.g.
          the CIBA binding's <tt>device_revoked</tt>, returned from
          <tt>/bc-authorize</tt>) without modifying this registry.
        </t>
        <t>
          <tt>registration_endpoint</tt> <bcp14>MUST</bcp14> return
          <tt>429</tt> with a <tt>Retry-After</tt> header on excessive
          attempts, keyed by source IP and/or registration token; this is
          the highest-value pre-authentication enumeration target.
          <tt>revocation_endpoint</tt> and <tt>key_rotation_endpoint</tt>
          <bcp14>SHOULD</bcp14> apply the same, keyed by <tt>device_id</tt>.
        </t>
      </section>

      <section anchor="interaction-type" numbered="true" toc="default">
        <name>Interaction Type (User Approval Ceremony)</name>
        <t>
          <tt>interaction_type</tt> describes the ceremony the user
          performs on the authenticator when a request lands, a distinct
          concern from <tt>delivery_modes_supported</tt>, which is
          transport (push/ping/poll), not interaction. Three values are
          defined:
        </t>
        <dl newline="true">
          <dt>boolean</dt>
          <dd>
            Plain approve/deny. The legacy default; vulnerable to
            MFA-fatigue ("prompt bombing") because the user can dismiss an
            unwanted prompt with a single tap under pressure.
          </dd>
          <dt>number_choose</dt>
          <dd>
            The consuming protocol's front end displays a number; the
            user must pick the matching number from a small set (the real
            value plus decoys) shown in the authenticator application.
            This is the industry-standard mitigation for MFA fatigue,
            formalized in CISA guidance <xref target="CISA.NUMBER"/>.
          </dd>
          <dt>input_manual</dt>
          <dd>
            The user transcribes a code shown on one device into the
            other. The correct fallback shape when a richer UI cannot be
            shown (feature phones, accessibility, or the poll-only
            degraded path in <xref target="transport"/>).
          </dd>
        </dl>
        <t>
          Rich transaction detail (e.g. "Approve $500 to X") and
          biometric-gated local confirmation are explicitly not additional
          <tt>interaction_type</tt> values; they are payload-content and
          local-authorization concerns respectively, and compose with any
          of the three values above rather than replacing them.
        </t>
        <t>
          This document defines the ceremony vocabulary and the
          discovery-level capability/floor fields
          (<tt>interaction_type_supported</tt>,
          <tt>minimum_interaction_type</tt>). It does not define how a
          specific back-channel authentication protocol requests a
          ceremony per request, or carries the ceremony's content to the
          user; that is a binding concern, defined by the companion CIBA
          binding document for CIBA specifically.
        </t>
        <t>
          <strong>Downgrade protection:</strong>
          <tt>minimum_interaction_type</tt> lets the AS set a floor: a
          consuming protocol's binding <bcp14>MUST NOT</bcp14> allow a
          caller to request an <tt>interaction_type</tt> weaker than the
          AS's floor. Without this, a caller could request
          <tt>boolean</tt> on every request regardless of AS risk policy,
          silently bypassing anti-fatigue protection the AS intends to
          enforce.
        </t>
        <t>
          <strong>Unsupported type:</strong> if an authenticator's
          registration-time capability list (<xref target="registration"/>)
          does not include the AS's <tt>minimum_interaction_type</tt>, the
          AS <bcp14>MUST</bcp14> reject registration with
          <tt>interaction_type_unsupported</tt> rather than accepting the
          device and silently degrading to <tt>boolean</tt> at wake time.
          A silent degrade would recreate the exact fatigue exposure
          <tt>number_choose</tt> exists to close.
        </t>
      </section>
    </section>

    <section anchor="registration" numbered="true" toc="default">
      <name>Registration Flow</name>
      <ol type="1">
        <li>
          <t>
            The user scans a QR code encoding a URL with the registration
            token in the fragment, not the userinfo component:
            <tt>https://oauth.example.com/push/enroll#{REGISTRATION_TOKEN}</tt>.
            Fragments are never transmitted to servers, proxies, or logged
            in access logs, unlike the deprecated
            <tt>user:pass&#64;host</tt> userinfo form (<xref
            target="RFC3986" section="3.2.1"/>), which risks leaking the
            token via browser history, referrer headers, and QR-scanner
            applications that open the link in a WebView before the
            authenticator ever sees it. Authenticator vendors
            <bcp14>MAY</bcp14> alternatively register a custom URL scheme
            for direct OS-level handling.
          </t>
        </li>
        <li>
          <t>
            <tt>REGISTRATION_TOKEN</tt> is itself a signed JSON Web Token
            (JWT) <xref target="RFC7519"/> containing:
          </t>
          <dl newline="false">
            <dt>iss</dt><dd>The AS.</dd>
            <dt>client_id</dt><dd>The OAuth client_id already registered
              at the AS (via <xref target="RFC7591"/> dynamic
              registration or static onboarding) whose authentication
              requests the device is being enrolled to receive.</dd>
            <dt>sub</dt><dd>The user being enrolled.</dd>
            <dt>exp</dt><dd>Matches <tt>registration_token_ttl</tt>.</dd>
            <dt>jti</dt><dd>Unique token identifier, used for single-use
              enforcement.</dd>
          </dl>
        </li>
        <li>
          <t>
            The authenticator resolves the discovery document at the host
            encoded in the token's <tt>iss</tt>.
          </t>
        </li>
        <li>
          <t>
            The authenticator POSTs to <tt>registration_endpoint</tt> with
            the registration token, its push address (an APNs or FCM
            token, delivered via the relay), a public key, the
            <tt>interaction_type</tt> values it supports (<xref
            target="interaction-type"/>), and attestation evidence per
            <tt>client_attestation_required</tt> (<xref
            target="trust"/>). The AS <bcp14>MUST</bcp14> reject
            registration with <tt>interaction_type_unsupported</tt> if the
            device's supported set does not include
            <tt>minimum_interaction_type</tt>.
          </t>
          <t>
            This POST is modeled on the design pattern of <xref
            target="RFC9126"/>, not on PAR itself: sensitive registration
            parameters are pushed over a back-channel POST and the AS
            returns a short-lived opaque reference plus the durable device
            credential:
          </t>
          <sourcecode type="json"><![CDATA[
{
  "device_credential": "{opaque bearer credential,
                          scoped push_device}",
  "registration_ref": "urn:push-reg:6a2f...",
  "expires_in": 300
}
          ]]></sourcecode>
          <t>
            The AS <bcp14>MUST</bcp14> reject a <tt>jti</tt> it has
            already consumed (<tt>registration_token_already_used</tt>)
            and <bcp14>MUST</bcp14> reject a token presented past
            <tt>exp</tt> (<tt>registration_token_expired</tt>). The AS
            <bcp14>MUST</bcp14> purge the consumed-<tt>jti</tt> record
            only after <tt>exp</tt> has passed, not before, so an
            expired-but-unconsumed token cannot be replayed after its
            record is dropped.
          </t>
        </li>
        <li>
          <t>
            <tt>device_credential</tt> is scoped <tt>push_device</tt>,
            restricted to exactly: resolution by the consuming
            authentication protocol (a device-address lookup, not a
            bearer-scope check), and self-service calls to
            <tt>revocation_endpoint</tt> / <tt>key_rotation_endpoint</tt>
            for the same device (<tt>push:revoke:self</tt>,
            <tt>push:rotate:self</tt>). AS admin console and RP-triggered
            revocation use distinct scopes (<tt>push:revoke:admin</tt>,
            <tt>push:revoke:rp</tt> respectively; see <xref
            target="revocation"/>).
          </t>
        </li>
        <li>
          <t>
            Once registered, the device is discoverable by any
            back-channel authentication protocol for which the AS has a
            binding; see the relevant companion document for how that
            protocol locates and wakes the device.
          </t>
        </li>
      </ol>
    </section>

    <section anchor="lifecycle" numbered="true" toc="default">
      <name>Device Lifecycle</name>

      <section anchor="revocation" numbered="true" toc="default">
        <name>Revocation</name>
        <t>
          This endpoint follows the general revocation model of <xref
          target="RFC7009"/> (an authenticated POST that invalidates a
          credential), extended with the device-specific cascade and
          scope requirements below.
        </t>
        <sourcecode type="http"><![CDATA[
POST /push/revoke HTTP/1.1
Host: oauth.example.com
Authorization: Bearer {device_credential_or_client_assertion}
Content-Type: application/x-www-form-urlencoded

device_id={id}&reason={reason}
        ]]></sourcecode>
        <ul>
          <li><t>Idempotent: revoking an already-revoked device returns
            <tt>200</tt>.</t></li>
          <li><t>Must cascade: any pending authentication request bound to
            the device <bcp14>MUST</bcp14> be invalidated immediately. A
            consuming protocol's binding document defines how this
            surfaces to that protocol's callers (e.g. CIBA's
            <tt>device_revoked</tt> error); this document only requires
            that the cascade happen.</t></li>
          <li>
            <t>The AS <bcp14>SHOULD</bcp14> push a wake signal so the
            application can clear local key material without waiting for
            its next authentication attempt. This reuses the same opaque
            wake-and-pull mechanism as ordinary authentication requests
            (<xref target="transport"/>): there is no separate revocation
            payload shape.</t>
            <sourcecode type="json"><![CDATA[
{"wake_reason": "device_revoked", "ref_id": "{device_id}"}
            ]]></sourcecode>
            <t>The application performs its normal authenticated pull to
            retrieve the reason and wipe local key material.</t>
          </li>
          <li><t>Distinct callers (application self-revocation, AS admin
            console, RP-triggered offboarding) <bcp14>MUST</bcp14> use
            distinct token scopes: <tt>push:revoke:self</tt>,
            <tt>push:revoke:admin</tt>, <tt>push:revoke:rp</tt>
            respectively.</t></li>
          <li><t>Requests <bcp14>MUST</bcp14> include a
            <tt>Replay-Nonce</tt> (issued by the AS on the prior response,
            per the pattern of <xref target="RFC8555" section="6.4"/>) as
            a JWS-protected <tt>nonce</tt> claim over the request body.
            The AS validates single use and rejects reused or missing
            nonces with <tt>nonce_replayed</tt>.</t></li>
        </ul>
      </section>

      <section anchor="rotation" numbered="true" toc="default">
        <name>Key Rotation</name>
        <sourcecode type="http"><![CDATA[
POST /push/rotate-key HTTP/1.1
Host: oauth.example.com
Authorization: Bearer {current_device_credential}
Content-Type: application/json

{
  "device_id": "{id}",
  "new_public_key": "{pem}",
  "proof_of_possession":
    "{signature over challenge, signed by the OLD key}"
}
        ]]></sourcecode>
        <t>
          Rotation <bcp14>MUST</bcp14> be signed by the outgoing key.
          Without proof of possession, rotation is a forgeable takeover
          path for anyone holding a stolen <tt>device_id</tt> and bearer
          token.
        </t>
        <ul>
          <li><t><strong>Grace window:</strong> the old public key remains
            valid for verification by the consuming authentication
            protocol until either (a) the new key completes one
            successful signed authentication, or (b)
            <tt>grace_period_seconds</tt> elapses (AS-configured,
            <bcp14>RECOMMENDED</bcp14> default 300), whichever occurs
            first. Rotation confirmation is asynchronous: the AS
            <bcp14>MUST NOT</bcp14> invalidate the old key synchronously
            in the rotate-key response, since an in-flight authentication
            request may already be signed under it. When the rotation
            reason is <tt>compromised</tt> rather than <tt>routine</tt>,
            the AS <bcp14>SHOULD</bcp14> reduce or skip the grace
            window.</t></li>
          <li><t><strong>Replay protection:</strong> same
            <tt>Replay-Nonce</tt> requirement as <xref
            target="revocation"/>.</t></li>
          <li><t><strong>Credential binding:</strong>
            <tt>device_credential</tt> itself is bound using DPoP <xref
            target="RFC9449"/> to the key established at registration: a
            <tt>jkt</tt> thumbprint is embedded in the credential at
            issuance, and rotate/revoke/device-management calls
            <bcp14>MUST</bcp14> present a DPoP proof for that key. This
            extends the same binding <xref target="transport"/> already
            requires for the pull step to the credential itself, so
            credential theft alone, without the private key, is
            insufficient to rotate or revoke a device.</t></li>
        </ul>
      </section>

      <section anchor="device-mgmt" numbered="true" toc="default">
        <name>Device Management</name>
        <sourcecode type="http"><![CDATA[
GET /push/devices HTTP/1.1
Host: oauth.example.com
Authorization: Bearer {user_session_token}
        ]]></sourcecode>
        <t>
          Returns the list of registered devices (platform, registration
          time, last used, attestation status) backing a user-facing
          "manage your devices" view.
        </t>
        <ul>
          <li><t>The AS enforces <tt>max_devices_per_user</tt> (discovery
            document, <bcp14>RECOMMENDED</bcp14> default 5,
            AS-configurable) at registration time.</t></li>
          <li>
            <t><strong>Device replacement</strong> is a distinct, atomic
            ceremony from plain add-new-device: pass
            <tt>replaces_device_id</tt> on the registration POST (<xref
            target="registration"/>).</t>
            <sourcecode type="json"><![CDATA[
{
  "registration_token": "...",
  "replaces_device_id": "{old_id}"
}
            ]]></sourcecode>
            <t>The old device is revoked only upon successful completion of
            the new device's registration, avoiding a lockout if
            registration fails partway through. Plain registration
            (without <tt>replaces_device_id</tt>) is subject to the
            <tt>max_devices_per_user</tt> cap.</t>
          </li>
          <li><t><strong>Primary-device selection:</strong> the
            <bcp14>RECOMMENDED</bcp14> default is to wake only the
            most-recently-used registered device, not fan out to all,
            matching common industry practice rather than a
            multi-authenticator fan-out model, since push MFA lacks a
            synchronous prompt moment to let the user disambiguate which
            device just alerted. A device <bcp14>MAY</bcp14> be marked
            <tt>is_primary</tt> via <tt>device_management_endpoint</tt> to
            override recency-based selection. An AS or RP
            <bcp14>MAY</bcp14> instead opt into a fan-out-to-all mode as a
            documented alternative. A stated default matters here because
            this document's value proposition depends on one generic
            authenticator application behaving predictably across
            different AS implementations.</t></li>
        </ul>
      </section>

      <section anchor="recovery" numbered="true" toc="default">
        <name>Account Recovery</name>
        <t>
          A hard lockout (device lost, no other device registered) is out
          of scope for the device protocol itself; recovery mechanisms
          embedded in the credential protocol tend to become bypass
          vectors. It <bcp14>MUST</bcp14> have a defined path at the
          AS/tenant level:
        </t>
        <ul>
          <li><t><strong>Mandatory baseline:</strong> admin-console-forced
            re-registration. A tenant or AS admin <bcp14>MAY</bcp14>
            revoke all devices for a user (<tt>push:revoke:admin</tt>) and
            issue a fresh registration token out of band (e.g. via a
            verified-identity support flow), re-entering the flow at
            <xref target="registration"/>. This requires no new protocol
            mechanism; it is existing revoke plus existing registration,
            admin-initiated.</t></li>
          <li><t><strong>Optional enhancement:</strong> the AS
            <bcp14>MAY</bcp14> support single-use backup codes, issued at
            registration time, redeemable by the consuming authentication
            protocol in place of a device-signed assertion, for
            self-service recovery when no device is reachable. Backup
            codes reintroduce a shared-secret bearer credential and reduce
            the phishing-resistance value of device attestation; RPs
            <bcp14>SHOULD</bcp14> gate this by risk tier rather than
            enabling it by default.</t></li>
        </ul>
      </section>
    </section>

    <section anchor="transport" numbered="true" toc="default">
      <name>Push Transport</name>
      <t>Three delivery approaches were evaluated:</t>
      <table anchor="transport-table">
        <name>Delivery Approaches</name>
        <thead>
          <tr><th>Approach</th><th>Verdict</th></tr>
        </thead>
        <tbody>
          <tr>
            <td>Poll (user-initiated)</td>
            <td>Fallback only; no ambient nudge</td>
          </tr>
          <tr>
            <td>AS to APNs/FCM directly</td>
            <td>Rejected; recreates per-AS platform lock-in</td>
          </tr>
          <tr>
            <td>Push via relay</td>
            <td>Recommended; only the authenticator vendor needs platform
              push credentials</td>
          </tr>
        </tbody>
      </table>
      <t>Payload security requirements:</t>
      <ul>
        <li>
          <t>The push payload <bcp14>MUST</bcp14> be an opaque wake
          signal only:</t>
          <sourcecode type="json"><![CDATA[
{"wake_reason": "auth_request" | "device_revoked",
 "ref_id": "..."}
          ]]></sourcecode>
          <t>No scope, binding message, or user context, since platform push
          infrastructure can inspect payloads in transit. A single wake
          shape covers both ordinary authentication requests and
          device-revocation signals; there is no separate revocation
          payload type.</t>
        </li>
        <li><t>On wake, the application <bcp14>MUST</bcp14> perform an
          authenticated pull (mTLS or a DPoP-bound token) to retrieve
          request details.</t></li>
        <li><t>The pull response <bcp14>MUST</bcp14> be encrypted to the
          device's registered public key (JWE), mirroring the Web Push
          encryption model of <xref target="RFC8291"/>, which mandates
          payload encryption to the subscriber's public key precisely
          because push infrastructure is an untrusted intermediary. This
          converts the relay from a trusted courier into a zero-trust hop
          that only ever sees ciphertext plus a routing reference
          (<tt>ref_id</tt> is deliberately not secret; it is routing
          metadata only).</t></li>
        <li><t>The authenticator's report of which delivery mode it
          actually used (push vs. poll fallback) <bcp14>MUST</bcp14>
          itself be signed by the device key and logged by the AS. The AS
          <bcp14>SHOULD</bcp14> alert or step-up-challenge on repeated
          push-to-poll fallback from the same device. This does not
          prevent an attacker from blocking push at the network layer,
          but makes a forced downgrade observable and rate-limitable
          instead of silent.</t></li>
        <li><t><strong>Push-wait timeout:</strong> APNs and FCM are both
          best-effort platforms with no contractual delivery guarantee.
          This document does not attempt to specify a relay uptime
          commitment; that is a business matter between the AS and the
          relay operator, out of scope here, matching <xref
          target="RFC8030"/> and <xref target="RFC9126"/>, which likewise
          specify only protocol timeouts, never operator SLAs. What this
          document does specify: the client <bcp14>MUST NOT</bcp14>
          switch to active polling before
          <tt>push_wait_timeout_seconds</tt> (discovery document,
          <bcp14>RECOMMENDED</bcp14> default 10) has elapsed since the
          wake was expected, and <bcp14>MUST</bcp14> switch to polling at
          the consuming protocol's own poll interval once it has. This
          bounds worst-case wake latency to
          <tt>push_wait_timeout_seconds</tt> plus one poll interval
          without the two timers racing.</t></li>
      </ul>
      <t>
        A persistent socket (WebSocket or MQTT) in place of platform push
        is explicitly rejected: it does not survive OS-level application
        suspension and defeats the purpose of platform push.
      </t>
    </section>

    <section anchor="trust" numbered="true" toc="default">
      <name>Trust Model</name>
      <t>
        A three-tier attestation ladder, modeled on WebAuthn/FIDO2 <xref
        target="WebAuthn2"/>:
      </t>
      <ol type="1">
        <li><t><strong>Device integrity</strong> (required): App Attest /
          Play Integrity API. Proves the running binary is unmodified and
          on a genuine device.</t></li>
        <li><t><strong>Authenticator attestation:</strong> a
          WebAuthn-style attestation object, signed by the authenticator
          vendor's build-time root key, proving which vendor produced
          this instance.</t></li>
        <li><t><strong>Vendor registry:</strong> the AS checks the
          attestation certificate against
          <tt>trusted_authenticator_registries</tt>, one or more neutral,
          certified metadata services modeled directly on the FIDO
          Alliance's Metadata Service (MDS) <xref target="FIDO.MDS3"/>.
          Default-deny unless listed.</t></li>
      </ol>
      <t>
        Registry entries are distributed as a signed JWT BLOB
        (<tt>MetadataBLOBPayload</tt>), adopting the FIDO MDS3 format: a
        single JWT signed by the registry operator's root key, containing
        per-authenticator entries (attestation root certificates, status
        reports), served over HTTPS with the signature providing
        integrity independent of transport. The AS <bcp14>MUST</bcp14>
        pin the registry's root signing certificate (distributed out of
        band, as with FIDO MDS's root) and the registry operator
        <bcp14>MUST</bcp14> publish a documented key-rotation ceremony
        (overlap period plus published fingerprint) before retiring a
        root key.
      </t>
      <t>
        <tt>trusted_authenticator_registries</tt> accepts a list rather
        than a single URL so an AS can configure fallback registries. A
        registry operator <bcp14>MUST</bcp14> publish a successor pointer
        before ceasing operation, so ASes and vendors have a migration
        path rather than a silent single point of failure.
      </t>
      <t>
        Governance model: multi-registry federation. No single body
        operates <tt>trusted_authenticator_registries</tt>; each AS or
        tenant owner configures its own list of trusted registries,
        choosing which neutral, certified metadata services it
        recognizes. This is distinct from per-AS authenticator
        allowlisting, which remains explicitly rejected, since it
        recreates vendor lock-in one layer up: the AS is choosing which
        registry <em>operators</em> it trusts, not which individual
        authenticator vendors. A protocol-level meta-registry (a curated
        "list of known registries") was considered and rejected: the
        closest real precedent for a federation-of-registries aggregator
        shows that a non-binding aggregator tends to accrete de facto
        gatekeeping authority once relying parties begin treating
        inclusion as a legitimacy signal, recreating the single-curator
        capture risk the federation model exists to avoid. Registry
        discovery is therefore left entirely out of band (specification
        text, vendor documentation, industry reputation).
      </t>
      <t>
        Per-AS bespoke allowlisting of individual authenticator vendors is
        explicitly out of scope and rejected; it recreates fragmentation
        one layer up.
      </t>
      <t>
        AS signing-key rotation (the AS's own protocol-signing keys, as
        distinct from device keys above) is out of scope for this
        document: it composes with existing <tt>jwks_uri</tt> and
        <tt>kid</tt>-tagged multi-key JWKS rotation per <xref
        target="RFC7517"/> and requires no additional device-side
        handling.
      </t>
    </section>

    <section anchor="security" numbered="true" toc="default">
      <name>Security Considerations</name>
      <t>
        This section consolidates security properties defined throughout
        this document.
      </t>
      <t>
        <strong>Registration token exposure.</strong> The registration
        token is carried in a URL fragment rather than the userinfo
        component (<xref target="registration"/>), since fragments are
        never sent to servers, proxies, or recorded in access logs;
        userinfo-form URLs are a known credential-leak vector and are
        deprecated by <xref target="RFC3986" section="3.2.1"/>.
      </t>
      <t>
        <strong>Replay protection.</strong> Registration tokens are
        single-use, enforced via <tt>jti</tt> consumption (<xref
        target="registration"/>). Revocation and key-rotation requests
        require a <tt>Replay-Nonce</tt> (<xref target="revocation"/>,
        <xref target="rotation"/>) modeled on <xref target="RFC8555"
        section="6.4"/>.
      </t>
      <t>
        <strong>Credential binding.</strong> <tt>device_credential</tt> is
        DPoP-bound (<xref target="RFC9449"/>) to the key established at
        registration (<xref target="rotation"/>), so credential theft
        alone, without the corresponding private key, is insufficient to
        rotate or revoke a device.
      </t>
      <t>
        <strong>Key-rotation takeover.</strong> Rotation requires proof of
        possession signed by the outgoing key (<xref target="rotation"/>);
        without this, rotation would be a forgeable takeover path for
        anyone holding a stolen <tt>device_id</tt> and bearer token.
      </t>
      <t>
        <strong>Relay as an untrusted intermediary.</strong> Pull
        responses are mandatorily JWE-encrypted to the device's public key
        (<xref target="transport"/>), converting the relay into a
        zero-trust hop that only ever observes ciphertext and a
        non-secret routing reference.
      </t>
      <t>
        <strong>Downgrade attacks.</strong> A client's report of which
        delivery mode it actually used is signed and logged (<xref
        target="transport"/>), so an attacker forcing a fallback from push
        to poll at the network layer produces an observable,
        rate-limitable signal rather than a silent downgrade. Similarly,
        <tt>minimum_interaction_type</tt> (<xref
        target="interaction-type"/>) prevents an RP from silently
        requesting a weaker approval ceremony than the AS's risk policy
        requires, which is the mechanism that specifically defends
        against MFA-fatigue/prompt-bombing attacks.
      </t>
      <t>
        <strong>Attestation and registry trust.</strong> The three-tier
        attestation ladder and signed-BLOB registry distribution (<xref
        target="trust"/>) bound the impact of a compromised or malicious
        authenticator vendor; registry root-key pinning and a mandatory
        successor-pointer requirement bound the impact of a registry
        operator's key compromise or cessation of operation.
      </t>
      <t>
        <strong>Backup codes.</strong> The optional account-recovery
        backup-code mechanism (<xref target="recovery"/>) reintroduces a
        shared-secret bearer credential and reduces the
        phishing-resistance value that device attestation otherwise
        provides; implementers are advised to gate its availability by
        risk tier rather than enabling it unconditionally.
      </t>
    </section>

    <section anchor="iana" numbered="true" toc="default">
      <name>IANA Considerations</name>

      <section anchor="iana-wku" numbered="true" toc="default">
        <name>Well-Known URI Registration</name>
        <t>
          This document requests IANA register the following well-known
          URI in the "Well-Known URIs" registry defined by <xref
          target="RFC8615"/>:
        </t>
        <table anchor="wku-table">
          <name>oauth-push-notification Well-Known URI</name>
          <thead>
            <tr><th>Field</th><th>Value</th></tr>
          </thead>
          <tbody>
            <tr><td>URI suffix</td><td>oauth-push-notification</td></tr>
            <tr><td>Change controller</td><td>IETF</td></tr>
            <tr><td>Specification document</td><td>This document</td></tr>
            <tr><td>Status</td><td>permanent</td></tr>
          </tbody>
        </table>
      </section>

      <section anchor="iana-metadata" numbered="true" toc="default">
        <name>OAuth Push-Auth Discovery Metadata Registry</name>
        <t>
          This document requests IANA establish a new registry, "OAuth
          Push-Auth Discovery Metadata", under the existing OAuth
          parameters registry group, using the same registration
          procedure (Specification Required, per <xref
          target="RFC8126"/>) as the "OAuth Authorization Server Metadata"
          registry established by <xref target="RFC8414"/>. The initial
          contents are the field names defined in <xref
          target="discovery"/>: <tt>registration_endpoint</tt>,
          <tt>delivery_modes_supported</tt>,
          <tt>interaction_type_supported</tt>,
          <tt>minimum_interaction_type</tt>, <tt>registration_token_ttl</tt>,
          <tt>signing_alg_values_supported</tt>, <tt>push_relay_endpoint</tt>,
          <tt>fallback_order</tt>, <tt>client_attestation_required</tt>,
          <tt>trusted_authenticator_registries</tt>,
          <tt>minimum_attestation_level</tt>, <tt>revocation_endpoint</tt>,
          <tt>key_rotation_endpoint</tt>, <tt>device_management_endpoint</tt>,
          <tt>max_devices_per_user</tt>, <tt>push_wait_timeout_seconds</tt>,
          and <tt>error_response_format</tt>.
        </t>
        <t>
          Error <tt>type</tt> values defined in <xref
          target="error-format"/> are identified by URI as required by
          <xref target="RFC9457"/> and do not require a central IANA
          registry.
        </t>
      </section>
    </section>

    <section anchor="open-issues" numbered="true" toc="default">
      <name>Open Issues</name>
      <t>
        This section is to be removed before publication as an RFC.
      </t>
      <ul>
        <li><t><strong>Relay SLA</strong> business terms remain outside
          this document's scope by design (<xref target="transport"/>);
          the protocol-level bound is <tt>push_wait_timeout_seconds</tt>.
        </t></li>
        <li><t><strong>Registry-discovery convenience.</strong> Whether a
          non-normative, unofficial community page listing known
          registries is worth maintaining outside this document, given
          the rejection of a protocol-level meta-registry (<xref
          target="trust"/>).</t></li>
      </ul>
    </section>

  </middle>

  <back>
      <references>
        <name>Normative References</name>
        <reference anchor="RFC2119">
          <front>
            <title>Key words for use in RFCs to Indicate Requirement Levels</title>
            <author initials="S." surname="Bradner" fullname="S. Bradner"/>
            <date year="1997" month="March"/>
          </front>
          <seriesInfo name="BCP" value="14"/>
          <seriesInfo name="RFC" value="2119"/>
        </reference>
        <reference anchor="RFC8174">
          <front>
            <title>Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words</title>
            <author initials="B." surname="Leiba" fullname="B. Leiba"/>
            <date year="2017" month="May"/>
          </front>
          <seriesInfo name="BCP" value="14"/>
          <seriesInfo name="RFC" value="8174"/>
        </reference>
        <reference anchor="RFC9457">
          <front>
            <title>Problem Details for HTTP APIs</title>
            <author initials="M." surname="Nottingham" fullname="M. Nottingham"/>
            <author initials="E." surname="Wilde" fullname="E. Wilde"/>
            <author initials="S." surname="Dalal" fullname="S. Dalal"/>
            <date year="2023" month="July"/>
          </front>
          <seriesInfo name="RFC" value="9457"/>
        </reference>
        <reference anchor="RFC9449">
          <front>
            <title>OAuth 2.0 Demonstrating Proof of Possession (DPoP)</title>
            <author initials="D." surname="Fett" fullname="D. Fett"/>
            <author initials="B." surname="Campbell" fullname="B. Campbell"/>
            <author initials="J." surname="Bradley" fullname="J. Bradley"/>
            <author initials="T." surname="Lodderstedt" fullname="T. Lodderstedt"/>
            <author initials="M." surname="Jones" fullname="M. Jones"/>
            <author initials="D." surname="Waite" fullname="D. Waite"/>
            <date year="2023" month="September"/>
          </front>
          <seriesInfo name="RFC" value="9449"/>
        </reference>
        <reference anchor="RFC7519">
          <front>
            <title>JSON Web Token (JWT)</title>
            <author initials="M." surname="Jones" fullname="M. Jones"/>
            <author initials="J." surname="Bradley" fullname="J. Bradley"/>
            <author initials="N." surname="Sakimura" fullname="N. Sakimura"/>
            <date year="2015" month="May"/>
          </front>
          <seriesInfo name="RFC" value="7519"/>
        </reference>
        <reference anchor="RFC8615">
          <front>
            <title>Well-Known Uniform Resource Identifiers (URIs)</title>
            <author initials="M." surname="Nottingham" fullname="M. Nottingham"/>
            <date year="2019" month="May"/>
          </front>
          <seriesInfo name="RFC" value="8615"/>
        </reference>
        <reference anchor="RFC8126">
          <front>
            <title>Guidelines for Writing an IANA Considerations Section in RFCs</title>
            <author initials="M." surname="Cotton" fullname="M. Cotton"/>
            <author initials="B." surname="Leiba" fullname="B. Leiba"/>
            <author initials="T." surname="Narten" fullname="T. Narten"/>
            <date year="2017" month="June"/>
          </front>
          <seriesInfo name="BCP" value="26"/>
          <seriesInfo name="RFC" value="8126"/>
        </reference>
      </references>

      <references>
        <name>Informative References</name>
        <reference anchor="RFC6238">
          <front>
            <title>TOTP: Time-Based One-Time Password Algorithm</title>
            <author initials="D." surname="M'Raihi" fullname="D. M'Raihi"/>
            <author initials="S." surname="Machani" fullname="S. Machani"/>
            <author initials="M." surname="Pei" fullname="M. Pei"/>
            <author initials="J." surname="Rydell" fullname="J. Rydell"/>
            <date year="2011" month="May"/>
          </front>
          <seriesInfo name="RFC" value="6238"/>
        </reference>
        <reference anchor="RFC6749">
          <front>
            <title>The OAuth 2.0 Authorization Framework</title>
            <author initials="D." surname="Hardt" fullname="D. Hardt" role="editor"/>
            <date year="2012" month="October"/>
          </front>
          <seriesInfo name="RFC" value="6749"/>
        </reference>
        <reference anchor="RFC7009">
          <front>
            <title>OAuth 2.0 Token Revocation</title>
            <author initials="T." surname="Lodderstedt" fullname="T. Lodderstedt" role="editor"/>
            <author initials="S." surname="Dronia" fullname="S. Dronia"/>
            <author initials="M." surname="Scurtescu" fullname="M. Scurtescu"/>
            <date year="2013" month="August"/>
          </front>
          <seriesInfo name="RFC" value="7009"/>
        </reference>
        <reference anchor="RFC7517">
          <front>
            <title>JSON Web Key (JWK)</title>
            <author initials="M." surname="Jones" fullname="M. Jones"/>
            <date year="2015" month="May"/>
          </front>
          <seriesInfo name="RFC" value="7517"/>
        </reference>
        <reference anchor="RFC7591">
          <front>
            <title>OAuth 2.0 Dynamic Client Registration Protocol</title>
            <author initials="J." surname="Richer" fullname="J. Richer" role="editor"/>
            <author initials="M." surname="Jones" fullname="M. Jones"/>
            <author initials="J." surname="Bradley" fullname="J. Bradley"/>
            <author initials="M." surname="Machulak" fullname="M. Machulak"/>
            <author initials="P." surname="Hunt" fullname="P. Hunt"/>
            <date year="2015" month="July"/>
          </front>
          <seriesInfo name="RFC" value="7591"/>
        </reference>
        <reference anchor="RFC8030">
          <front>
            <title>Generic Event Delivery Using HTTP Push</title>
            <author initials="M." surname="Thomson" fullname="M. Thomson"/>
            <author initials="E." surname="Damaggio" fullname="E. Damaggio"/>
            <author initials="B." surname="Raymor" fullname="B. Raymor" role="editor"/>
            <date year="2016" month="December"/>
          </front>
          <seriesInfo name="RFC" value="8030"/>
        </reference>
        <reference anchor="RFC8291">
          <front>
            <title>Message Encryption for Web Push</title>
            <author initials="M." surname="Thomson" fullname="M. Thomson"/>
            <date year="2017" month="November"/>
          </front>
          <seriesInfo name="RFC" value="8291"/>
        </reference>
        <reference anchor="RFC8414">
          <front>
            <title>OAuth 2.0 Authorization Server Metadata</title>
            <author initials="M." surname="Jones" fullname="M. Jones"/>
            <author initials="N." surname="Sakimura" fullname="N. Sakimura"/>
            <author initials="J." surname="Bradley" fullname="J. Bradley"/>
            <date year="2018" month="June"/>
          </front>
          <seriesInfo name="RFC" value="8414"/>
        </reference>
        <reference anchor="RFC8555">
          <front>
            <title>Automatic Certificate Management Environment (ACME)</title>
            <author initials="R." surname="Barnes" fullname="R. Barnes"/>
            <author initials="J." surname="Hoffman-Andrews" fullname="J. Hoffman-Andrews"/>
            <author initials="D." surname="McCarney" fullname="D. McCarney"/>
            <author initials="J." surname="Kasten" fullname="J. Kasten"/>
            <date year="2019" month="March"/>
          </front>
          <seriesInfo name="RFC" value="8555"/>
        </reference>
        <reference anchor="RFC9126">
          <front>
            <title>OAuth 2.0 Pushed Authorization Requests</title>
            <author initials="T." surname="Lodderstedt" fullname="T. Lodderstedt"/>
            <author initials="B." surname="Campbell" fullname="B. Campbell"/>
            <author initials="N." surname="Sakimura" fullname="N. Sakimura"/>
            <author initials="D." surname="Tonge" fullname="D. Tonge"/>
            <author initials="F." surname="Skokan" fullname="F. Skokan"/>
            <date year="2021" month="September"/>
          </front>
          <seriesInfo name="RFC" value="9126"/>
        </reference>
        <reference anchor="RFC3986">
          <front>
            <title>Uniform Resource Identifier (URI): Generic Syntax</title>
            <author initials="T." surname="Berners-Lee" fullname="T. Berners-Lee"/>
            <author initials="R." surname="Fielding" fullname="R. Fielding"/>
            <author initials="L." surname="Masinter" fullname="L. Masinter"/>
            <date year="2005" month="January"/>
          </front>
          <seriesInfo name="STD" value="66"/>
          <seriesInfo name="RFC" value="3986"/>
        </reference>
        <reference anchor="OIDC.CIBA" target="https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html">
          <front>
            <title>OpenID Connect Client-Initiated Backchannel Authentication Flow - Core 1.0</title>
            <author initials="G." surname="Fernandez" fullname="G. Fernandez"/>
            <author initials="F." surname="Walter" fullname="F. Walter"/>
            <author initials="A." surname="Nennker" fullname="A. Nennker"/>
            <author initials="D." surname="Tonge" fullname="D. Tonge"/>
            <author initials="B." surname="Campbell" fullname="B. Campbell"/>
            <date year="2021"/>
          </front>
        </reference>
        <reference anchor="WebAuthn2" target="https://www.w3.org/TR/webauthn-2/">
          <front>
            <title>Web Authentication: An API for accessing Public Key Credentials Level 2</title>
            <author><organization>W3C</organization></author>
            <date year="2021" month="April"/>
          </front>
        </reference>
        <reference anchor="FIDO.MDS3" target="https://fidoalliance.org/metadata/">
          <front>
            <title>FIDO Metadata Service (MDS3)</title>
            <author><organization>FIDO Alliance</organization></author>
            <date year="2021"/>
          </front>
        </reference>
        <reference anchor="CISA.NUMBER" target="https://www.cisa.gov/sites/default/files/publications/fact-sheet-implement-number-matching-in-mfa-applications-508c.pdf">
          <front>
            <title>Fact Sheet: Implement Number Matching in MFA Applications</title>
            <author><organization>Cybersecurity and Infrastructure Security Agency</organization></author>
            <date year="2023"/>
          </front>
        </reference>
        <reference anchor="JSON.SCHEMA" target="https://json-schema.org/specification">
          <front>
            <title>JSON Schema</title>
            <author><organization>JSON Schema Organization</organization></author>
            <date year="2020"/>
          </front>
        </reference>
      </references>

    <section anchor="acks" numbered="false" toc="default">
      <name>Acknowledgments</name>
      <t>
        The design of this document draws on prior art from the OAuth,
        OpenID Connect, WebAuthn/FIDO2, ACME, and Web Push communities,
        each cited at the relevant point in the text.
      </t>
    </section>

  </back>
</rfc>
