Internet-Draft HVP October 2026
Goto Expires 29 April 2027 [Page]
Workgroup:
TBD
Internet-Draft:
draft-goto-handle-verification-latest
Published:
Intended Status:
Standards Track
Expires:
Author:
S. Goto
Google

Handle Verification Protocol

Abstract

This document defines the Handle Verification Protocol (HVP), the HTTP-level protocol by which a browser obtains a signed Handle Verification Token (HVT) from an issuer attesting that the user controls a handle, and presents it to a verifier. HVP uses the same three-party model as the Email Verification Protocol ([EVP]), in which the browser intermediates between the verifier and the issuer and hides the verifier's identity from the issuer.

This document defines handles, handle types, handle normalization and resolution, issuers, the HVT request, the HVT, its key binding (HVT+KB), and the verification of the HVT+KB, which are the same for every handle type. It also defines one handle type: domain handles, such as @alice.example, which are used by the AT Protocol, and which are resolved through a DID document in a way that is backwards compatible with the AT Protocol. The browser API, which is defined separately (for example, by the W3C IdP Registration API ([IDP-REGISTRATION])), defines which handle types are supported, how the user selects a handle, and how the HVT+KB is delivered to the verifier.

Discussion Venues

Note: This section is to be removed before publishing as an RFC.

Source for this draft and an issue tracker can be found at https://github.com/samuelgoto/handle-verification.

Status of This Memo

This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.

Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.

Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."

This Internet-Draft will expire on 29 April 2027.

▲

Table of Contents

1. Introduction

Social networks built on open protocols let any server host a user's account, and let users bring their own domain name as their identity. The AT Protocol ([ATPROTO]) identifies every user with a handle that is a DNS name, such as @alice.example, which resolves to a stable decentralized identifier (DID). ActivityPub ([ACTIVITYPUB]) implementations identify users with handles of the form @alice@social.example, and the W3C Social Web Incubator Community Group is considering adding domain handles, such as @alice.example, as an alternative ([HANDLES-TF]). The result is an open federation: a website that wants to accept these users cannot know their providers in advance.

Email shows that an open federation can still be verified. A verifier accepts an email address at any mail domain without a prior relationship with the domain, and the Email Verification Protocol ([EVP]) lets the verifier check such an address with a signed token. HVP applies that model to handles, in a way that does not depend on which ecosystem the user's account belongs to.

1.1. The Problem

A website that wants to let users sign in with a handle today has to integrate with each ecosystem separately. For the AT Protocol, it uses AT Protocol OAuth ([ATPROTO-OAUTH]): it presents a text field for the user to type their handle, resolves the handle to a DID and PDS, discovers the PDS's authorization server, and redirects the user to it. ActivityPub has no common sign-in protocol, so websites typically use the OAuth API of each server implementation. In both cases:

  • the user has to know and type their handle, which experience with OpenID 2.0 suggests many users do not;
  • the website is identified to the user's authorization server, which learns every site the user signs in to;
  • the browser, which often already knows the user's handles, has no role in the flow, unlike in [EVP] and the Email Verification API ([EMAIL-VERIFICATION-API]), which let the browser integrate verification into autofill and form submission, into a JavaScript API, or into FedCM ([FEDCM]); and
  • each authorization server and each website implements the protocol independently, and nothing between them checks the result. An authorization server learns whether it interoperates with a website only by testing against that website, so interoperability across many authorization servers and many websites rests on conformance testing and certification rather than on the protocol itself.

1.2. Solution

HVP separates the problem into three parts:

  1. Syntax (Section 4): what a handle is, and how it is written and normalized. Handles come in several handle types, which are syntactically distinct.
  2. Resolution (Section 5): how a handle is resolved to a unique user identifier, checked against the handle, and turned into the issuer that the user has authorized to vouch for it. Each handle type defines its own resolution.
  3. Issuance and verification: how a browser obtains a Handle Verification Token (HVT) from the authorized issuer, and how a verifier verifies it. This part reuses the token request, key binding, and token format of EVP, and is the same for every handle type.

This document defines one handle type, domain handles (Section 6), which are resolved to a DID in a way that is backwards compatible with the AT Protocol (Section 6.4.1). Nothing in issuance or verification depends on the handle type, or on the ecosystem that the account belongs to.

HVP keeps the privacy properties of EVP: the browser intermediates between the verifier and the issuer, so the issuer does not learn which verifier the user is signing in to.

1.3. Scope

This document defines the protocol, which is the same for every handle type, and one handle type, domain handles, which consist of a DNS name alone. Other handle types are defined by other documents, which plug into the protocol: [URL-HANDLES] defines URL handles, that is, users identified by an https URL, such as a Solid WebID, an ActivityPub actor, or an IndieAuth personal website, and [USERNAME-HANDLES] builds on it to define username handles, which combine a user name and a domain, such as @alice@social.example, by mapping them to the URL of their account with WebFinger. More handle types can be added in the same way, without changing issuance or verification. Email addresses are out of scope: they are verified with [EVP].

HVP provides verified identification, for example to sign a user in: it tells the verifier that the user controls a handle, and which unique user identifier that handle resolves to. It does not provide authorization: an HVT grants no access to the user's account, and cannot be used to read or publish content or to act on the user's behalf. A verifier that needs such access continues to use the ecosystem's authorization protocol, such as AT Protocol OAuth ([ATPROTO-OAUTH]), and can use HVP and that protocol together, for example by signing the user in with HVP and requesting authorization later, only when it needs it. See Section 14.4.

1.4. Relationship to EVP

HVP reuses the mechanics of [EVP] (the token request, key binding, and token format) with the substitutions described in this document. A browser API can offer both, using EVP for email addresses and HVP for handles.

1.5. Relationship to the Browser API

This document specifies the IETF protocol aspects of handle verification: the HTTP-level interactions between the browser, the issuer, and the verifier, and the lookups needed to resolve a handle. How the browser learns about the user's handles, how the user selects one, and how the browser communicates with the verifier are defined separately. The W3C IdP Registration API ([IDP-REGISTRATION]), an extension of FedCM ([FEDCM]), is one such API. See Appendix B.

2. Conventions and Definitions

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.

This document uses the following terms:

Handle:
A human-readable identifier of a user, such as @alice.example, of one of several handle types. A handle is not necessarily permanent: a user can change it. See Section 4.
Handle type:
A syntactically distinct form of handle, with its own syntax, normalization, and resolution, such as domain handles. See Section 4.
Domain handle:
A handle that consists of a DNS name, such as alice.example, displayed as @alice.example. See Section 6.
Unique user identifier:
An identifier of the user's account that resolution produces, and that the HVT carries in its sub claim. Each handle type defines it: for a domain handle, it is the DID that the handle resolves to. Unlike a handle, which can change hands and point to different accounts over time, it identifies one account.
DID:
A decentralized identifier ([DID-CORE]) that identifies a user's account more permanently than its handle, such as did:plc:bftbdgcbe4zclvpkboe4hleq or did:web:alice.example. A handle resolves to a DID.
Controlled identifier document:
A JSON document that describes the subject of an identifier, which is its id, and lists the subject's other identifiers, verification methods, and services ([CID-1.0]). See Section 5.1.
DID document:
The document that a DID resolves to. It is a controlled identifier document whose id is the DID. It lists the handle that the account claims, the account's services, and, for HVP, its authorized issuer.
Authorized issuer:
The issuer identifier that resolution yields for a handle, typically the one that a controlled identifier document names with its HandleVerification service (Section 5.2): for a domain handle, its DID document. Only the authorized issuer can issue an HVT that a verifier accepts for that handle. A handle has at most one authorized issuer.
Issuer:
A service that verifies the user controls a handle and issues HVTs for it, typically the server that hosts the user's account, such as an AT Protocol personal data server (PDS) or an ActivityPub server. An issuer is identified by an issuer identifier (Section 7.1).
Handle Verification Token (HVT):
A JWT ([RFC7519]) issued by an issuer that contains a handle, the unique user identifier it resolves to, and the browser's public key. See Section 8.
HVT+KB:
An HVT combined with a Key Binding JWT that binds it to a verifier and a session. See Section 9.
Verifier:
The party, typically a website, to which the browser presents an HVT+KB, and which verifies it to learn that the user controls a handle. It corresponds to the relying party (RP) of [EVP] and of federated sign-in protocols.

3. Protocol Flow

The protocol involves three parties: the verifier, the browser, and the issuer. The following diagram shows the protocol flow:

Step                       Verifier     Browser              Issuer
                               |            |                    |
3.1 Handle Discovery           |      [obtain handle from user]  |
                               |            |                    |
3.2 Session Binding            |--- nonce ->|                    |
                               |            |                    |
3.3 Handle Resolution          |   [resolve handle: subject      |
                               |    identifier, issuer]          |
                               |            |                    |
3.4 HVT Request                |            |-- POST /issuance ->|
                               |            |   (handle, sub)    |
                               |            |                    |
3.5 HVT Issuance               |            |<------- HVT -------|
                               |            |                    |
3.6 KB Creation                |        [create KB-JWT]          |
                               |            |                    |
3.7 HVT+KB Presentation        |<-- HVT+KB -|                    |
                               |            |                    |
3.8 HVT+KB Verification [resolve handle;    |                    |
                         verify HVT+KB]     |                    |

The flow is the same as the flow of [EVP], with two differences: the browser and the verifier resolve the handle (Section 5) where EVP looks up a DNS delegation record, and the token is an HVT rather than an EVT.

3.1. Handle Discovery

How the browser assembles candidate handles, from its own store, from accounts that an issuer pushes or the browser pulls, or from the user typing one, is defined by the browser API ([IDP-REGISTRATION]) and is out of scope for this document. When the browser starts from text that the user typed, it parses it as described in Section 4.2.

3.2. Session Binding

As in [EVP]: the verifier generates a cryptographically random nonce with at least 128 bits of entropy and binds it to a session it has with the browser. How the verifier provides the nonce to the browser is defined by the browser API.

3.3. Handle Resolution

The browser resolves the handle as described in Section 5. Resolution produces the unique user identifier and the authorized issuer. The browser fetches the issuer's metadata (Section 7.2). From this point onward, the handle and the unique user identifier are never altered: they are sent byte-for-byte in the HVT request, and returned byte-for-byte in the handle and sub claims.

3.4. HVT Request

The browser sends an HVT request to the issuer's issuance_endpoint. The request is the token request of [EVP], with the substitutions listed in Section 7.3.

3.5. HVT Issuance

The issuer verifies the request, checks that the browser's cookies represent a signed-in user who controls the handle, and returns an HVT (Section 8) as the value of issuance_token in an application/json response, as in [EVP].

3.6. KB Creation

The browser verifies the HVT (Section 8.3), additionally confirming that the handle and sub claims are byte-for-byte identical to the handle and unique user identifier it requested and that the cnf.jwk claim matches the public key it generated. It then creates a KB-JWT and combines it with the HVT as described in Section 9.

3.7. HVT+KB Presentation

The browser provides the HVT+KB to the verifier. This mechanism is defined by the browser API.

3.8. HVT+KB Verification

The verifier verifies the HVT+KB as described in Section 10. The verifier resolves the handle itself, and does not rely on the browser's resolution. If verification succeeds, the verifier has verified that the user controls the handle in the handle claim, and has learned the user's unique user identifier in the sub claim.

4. Handles

A handle is a human-readable identifier of a user. Handles come in several handle types, whose syntaxes are mutually exclusive, so that any string is a handle of at most one type. Each handle type defines:

Everything else, that is, the issuers, the HVT request, the HVT, its key binding, and the verification of the HVT+KB, is the same for every handle type. This document defines one handle type, domain handles (Section 6).

Note: Other documents define other handle types: URL handles ([URL-HANDLES]), such as https://social.example/users/alice, and username handles ([USERNAME-HANDLES]), such as @alice@social.example.

A browser, an issuer, or a verifier supports some set of handle types. A handle of a type that it does not support is not a handle for it.

4.1. Normalization

Each handle type defines the normalized form of its handles. Every handle in this protocol (in the HVT request, in the handle claim, and in comparisons) is in normalized form, and every comparison of handles is a byte-for-byte comparison of normalized handles.

4.2. Parsing User Input

To parse a handle that a user typed, parse it as a handle of each supported handle type. Because their syntaxes are mutually exclusive, at most one succeeds, and its result is the handle. If none succeeds, the input is not a handle.

Table 1
Input Handle
@Alice.Example, alice.example alice.example (domain handle, Section 6.3)
https://Social.Example/users/alice https://social.example/users/alice (URL handle, [URL-HANDLES])
@Alice@Social.Example alice@social.example (username handle, [USERNAME-HANDLES])
alice@mail.example not a handle

An email address, such as alice@mail.example, is not a handle of any type: a browser API can verify it with [EVP].

5. Handle Resolution

Resolution takes a normalized handle and produces:

Each handle type defines its resolution: domain handles are resolved through a DID (Section 6.4). Resolution checks that the handle and the unique user identifier refer to each other, so that a handle cannot claim an account that does not agree to it, and finds the issuer that the user has explicitly authorized.

Both the browser and the verifier perform resolution. The browser does it to find an issuer to ask for an HVT; the verifier does it to check that the HVT was issued by an issuer that is authorized for the handle (Section 10).

The following rules apply to every resolution step, of every handle type:

5.1. Controlled Identifier Documents

Handle types can resolve a handle to a controlled identifier document ([CID-1.0]), which describes the account: a DID document for a domain handle (Section 6.4), or a document fetched from a URL for other handle types. A controlled identifier document names the account's authorized issuer with the HandleVerification service (Section 5.2), and can claim the handle back with its alsoKnownAs member.

Resolving parties read a controlled identifier document as JSON ([RFC8259]), without JSON-LD processing ([JSON-LD11]), as follows:

  • The document is a JSON object whose id member is a string. Otherwise, it is not a controlled identifier document.
  • Members are read by their literal names: id, alsoKnownAs, service, and, within each service, id, type, and serviceEndpoint. A JSON-LD @context, if present, is ignored, so a document that spells these members differently, for example in expanded JSON-LD form, has none of them.
  • Relative URLs in the id of a service, such as #handle_verification, are resolved against the id of the document.

A handle type that resolves a handle to a controlled identifier document defines how the document is obtained, and how its id is checked: for example, a DID document's id is the DID, and a document fetched from a URL has that URL as its id ([CID-1.0]).

5.2. The HandleVerification Service

A controlled identifier document names the issuer authorized to vouch for its handles with a service ([CID-1.0]) whose type is HandleVerification, whose serviceEndpoint is an issuer identifier (Section 7.1), and whose id is #handle_verification, that is, the document's id followed by #handle_verification:

{ "id": "#handle_verification", "type": "HandleVerification",
  "serviceEndpoint": "https://issuer.example" }

HandleVerification is a service type of [CID-1.0], in the same way that, for example, the AT Protocol identifies the account's PDS with a service whose id is #atproto_pds ([ATPROTO-DID]). Service ids are unique within a controlled identifier document ([CID-1.0]), and in did:plc services are stored as a map keyed by id ([DID-PLC]), so a document names at most one issuer.

If the document has no service whose id is #handle_verification, or that service's type is not HandleVerification, or its serviceEndpoint is not an issuer identifier, that is, not an https origin with no path, query, or fragment, the handle has no authorized issuer, and HVP cannot be used with it.

Because a handle has a single authorized issuer, a change of issuer takes effect at once: as soon as the document names a new issuer, HVTs from the previous one no longer verify. This is the same as when a user moves their account to another server. A user who moves their account to another server, and whose issuer is that server, updates their HandleVerification service as part of the move.

Authorization is always explicit: resolution does not infer an issuer from the account's other services or properties, such as its AT Protocol PDS (the #atproto_pds service), the PDS's authorization server, or the server that hosts the document. An issuer has to deploy HVP anyway, and requiring the document to name it means that a server does not become able to vouch for a handle in a new way, to verifiers it may never have dealt with, without the account's owner opting in. See Section 14.7.

Like other services of controlled identifier documents, the HandleVerification service names a party to interact with, and does not involve the document's verification methods: the issuer signs HVTs with the keys of its own metadata (Section 7.2).

Open Issue: Whether the service type should be identified by a full IRI, for example a w3id.org URL, with a published JSON-LD context, so that JSON-LD consumers of DID documents and controlled identifier documents can process it, and its registration in the W3C DID Extensions registry, which also covers the properties of controlled identifier documents.

5.3. Caching

A resolving party MAY cache the results of resolution, subject to DNS time-to-live values and HTTP caching rules. A verifier SHOULD NOT cache resolution results for longer than a few minutes when verifying an HVT+KB, because a user who moves their handle to another issuer, or who loses control of it, expects the change to take effect promptly.

6. Domain Handles

A domain handle is a DNS name, such as alice.example. Domain handles are the handles of the AT Protocol ([ATPROTO-HANDLE]), and the domain-based handles proposed for ActivityPub ([HANDLES-TF]). In DID documents, a domain handle is written as an at URI ([ATPROTO-URI]), at:// followed by the handle, such as at://alice.example, as in the AT Protocol. This document uses that form for domain handles of every ecosystem.

A domain handle is displayed with a leading @, as in @alice.example, and is carried in the protocol without it.

6.1. Syntax

A domain handle MUST satisfy the following rules, which are a synthesis of the hostname rules of [RFC1035], [RFC3696] and [RFC3986]:

  • The handle contains only ASCII characters, and is at most 253 characters long.
  • The handle consists of at least two labels separated by ASCII periods (.). It has no leading or trailing period.
  • Each label is between 1 and 63 characters long, and contains only ASCII letters, digits, and hyphens (-). A label does not start or end with a hyphen.
  • The last label (the top-level domain) does not start with a digit. As a consequence, IP addresses are not handles.

Internationalized domain names are written in their A-label form ([RFC5890]), for example xn--ls8h.example.

In ABNF ([RFC5234]), before normalization:

handle    = 1*( label "." ) tld-label
label     = alnum [ *61( alnum / "-" ) alnum ]
tld-label = ALPHA [ *61( alnum / "-" ) alnum ]
alnum     = ALPHA / DIGIT

with the additional constraint that the whole handle is at most 253 characters long. A reference regular expression is:

^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+
[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$

(The regular expression is shown on two lines for display; it is a single expression.)

The following top-level domains are syntactically valid in a handle, but a handle whose domain ends in one of them MUST fail resolution: .alt, .arpa, .example, .internal, .invalid, .local, .localhost, and .onion. The .test top-level domain MAY be used in development, and MUST fail resolution in production deployments. (The examples in this document use .example and so would fail resolution.)

Open Issue: How internationalized handles are displayed, and how homoglyph attacks are mitigated, in coordination with the internationalization work of [HANDLES-TF].

6.2. Normalization

Domain handles are case-insensitive. The normalized form of a domain handle is the handle converted to ASCII lowercase.

6.3. Parsing User Input

To parse a domain handle that a user typed:

  1. Strip leading and trailing ASCII whitespace.
  2. If the input starts with at://, remove it, or else if it starts with @, remove it.
  3. If the input then ends with a single /, remove it.
  4. Normalize the rest (Section 6.2), and check that it satisfies Section 6.1.

If any step fails, the input is not a domain handle.

Table 2
Input Domain handle
@Alice.Example, alice.example, at://alice.example alice.example
@192.0.2.1, @com not a domain handle

6.4. Resolution

A domain handle resolves to a DID ([DID-CORE]), which is its unique user identifier, and whose DID document claims the handle back and names the authorized issuer. This generalizes the handle resolution of the AT Protocol ([ATPROTO-HANDLE], [ATPROTO-DID]): it uses ecosystem-neutral names for the records that map a handle to its DID, also accepts the names of the AT Protocol (Section 6.4.1), and adds the extraction of the authorized issuer.

Given a normalized domain handle handle:

  1. Handle to DID. Perform the following lookups. A DID MUST use the plc method ([DID-PLC]) or the web method ([DID-WEB]); otherwise fail. If both lookups yield a DID, and the DIDs differ, fail. If either lookup yields a DID, it is the handle's DID. If neither yields anything, the resolving party MUST fall back to the lookups of existing AT Protocol handles (Section 6.4.1), but only if both lookups established that the handle names nothing: that is, if the DNS query returned no TXT record whose value starts with a recognized key, or no record at all, and the well-known request returned a 404 (Not Found) or 410 (Gone) response, or a document with no recognized member. If either lookup failed in any other way, for example because of a DNS server failure, a timeout, or a server error, fail. A resolving party MAY perform the lookups of Section 6.4.1 in parallel with these, to avoid adding a round trip, and use their results only when it falls back. A handle near the 253-character limit cannot use the DNS lookups, because the prefix would push the query name over the limit; the well-known lookups work for such handles.

    • DNS TXT record. Query TXT records at the name _handle. handle. A record whose value starts with did= names a DID, in the remainder of the value. Records whose value starts with anything else are ignored. This method aligns with [RFC1464].
    • Well-known document. GET https:// handle /.well-known/handle. A 2xx response with the application/json media type is a JSON object whose did member, if present, names a DID. Other members are ignored.
  2. DID to DID document. Resolve the DID to its DID document ([DID-CORE]), a controlled identifier document that is read as described in Section 5.1, and whose id member MUST be identical to the DID:

    • For did:plc, GET the DID from a PLC directory, by default https://plc.directory/ followed by the DID.
    • For did:web, GET https:// domain /.well-known/did.json, where domain is the method-specific identifier ([DID-WEB]), such as alice.example for did:web:alice.example. A did:web DID with a path is not supported, as in the AT Protocol ([ATPROTO-DID]).
  3. Back-link. Find the first element of the DID document's alsoKnownAs array that is at:// followed by a string that satisfies Section 6.1. After normalization, that string MUST be identical to handle. Otherwise fail. This check ensures that a DID document cannot be claimed by a handle that it did not agree to.

  4. Issuer. The authorized issuer is the serviceEndpoint of the DID document's HandleVerification service (Section 5.2). If there is none, fail.

If any step fails, resolution fails. The unique user identifier is the DID.

For example, the handle @samuelgoto.bsky.social resolves as follows at the time of writing, through the AT Protocol names (Section 6.4.1), since it publishes none of the new ones:

GET https://samuelgoto.bsky.social/.well-known/atproto-did
  -> did:plc:bftbdgcbe4zclvpkboe4hleq
GET https://plc.directory/did:plc:bftbdgcbe4zclvpkboe4hleq
  -> alsoKnownAs: ["at://samuelgoto.bsky.social"]

so its unique user identifier is the DID did:plc:bftbdgcbe4zclvpkboe4hleq. Its DID document has no HandleVerification service yet, so it has no authorized issuer. See Appendix A.

Because records and members with other keys are ignored, other kinds of records can be added later without affecting resolving parties that do not support them. For example, [USERNAME-HANDLES] defines the me= key and the me member, which let a domain handle name a username handle or a URL handle ([URL-HANDLES]) instead of a DID.

Open Issue: Other kinds of records could be added later with keys of their own: for example, domain= for another domain handle, resolved recursively and failing when a loop is detected, or email= for an email address verified with [EVP]. A handle could also be allowed to name several identifiers, any one of which proves it; this would require a rule for which unique user identifier an HVT for the handle carries, because each resolves to a different one, and a verifier identifies accounts by it.

Open Issue: The SWICG Handles Task Force proposes /.well-known/activitypub-actor, a JSON document that refers to an ActivityPub actor, to resolve domain handles in ActivityPub ([HANDLES-TF]). This document proposes /.well-known/handle and _handle, which refer to a DID instead, so that the same names serve every ecosystem. The two need to be reconciled.

Open Issue: Whether a server that the DID document already names, such as the PDS named by the #atproto_pds service, should be an authorized issuer by default, which would let HVP work with existing accounts without changing their DID documents, at the cost of the explicit opt-in.

6.4.1. Backwards Compatibility with the AT Protocol

The AT Protocol maps a handle to a DID with a TXT record at _atproto. followed by the handle, and with the well-known URL /.well-known/atproto-did ([ATPROTO-HANDLE]). These names are specific to the AT Protocol. This document defines ecosystem-neutral names, _handle and /.well-known/handle, which take precedence, and falls back to the AT Protocol names when a handle does not publish the new ones, so that every existing AT Protocol handle keeps working.

When step 1 of resolution (Section 6.4) falls back, the resolving party performs the following lookups, each of which yields the handle's DID:

  • AT Protocol DNS TXT record. Query TXT records at the name _atproto. handle. Ignore records whose value does not start with did=. The DID is the remainder of the value.
  • AT Protocol well-known URL. GET https:// handle /.well-known/atproto-did. A 2xx response with the text/plain media type contains the DID, with leading and trailing whitespace removed.

If both lookups yield a DID, and the DIDs differ, resolution fails, in the same way that an AT Protocol handle with conflicting _atproto records fails today. If neither yields a DID, resolution fails.

As a consequence:

  • A handle that is published with the AT Protocol names only, as every AT Protocol handle is today, resolves for every resolving party, without any change.
  • A handle that is published with the new names resolves to what they name, whatever the AT Protocol names say. This lets the owner of a handle point it elsewhere, for example to a different DID, without first removing its AT Protocol records.
  • Operators of handle domains MAY publish both. AT Protocol software that does not yet support the new names reads only the AT Protocol names, so operators that want the handle to resolve the same way in both SHOULD keep them consistent.

Open Issue: Whether, and when, the AT Protocol adopts the new names, and whether resolving parties can eventually stop querying the AT Protocol names. This needs coordination with the AT Protocol community and the IETF ATP working group.

7. Issuers

7.1. Issuer Identifier

An issuer identifier is an HTTPS origin: the string https:// followed by a host, with no port, path, or trailing slash, for example https://issuer.example. This is the same form as the issuer identifier of [EVP], and for the same reasons: it names the same thing as the iss claim of OpenID Connect and the issuer of [RFC8414], and it can be compared byte-for-byte.

Every use of the issuer identity in this document (the authorized issuer that resolution yields, the iss claim of the HVT, the issuer member of the metadata, and the base of the well-known URL) is this origin. Comparisons are byte-for-byte; no scheme defaulting, port normalization, or trailing-slash tolerance is applied.

7.2. Issuer Metadata

An issuer serves its metadata at $ISSUER/.well-known/handle-verification, where $ISSUER is its issuer identifier. For https://issuer.example, this is https://issuer.example/.well-known/handle-verification.

The metadata document is a JSON object with the following members:

  • issuer (REQUIRED): The issuer identifier. It MUST be identical to the issuer identifier that the document was fetched under, and a party that fetches the document MUST reject it if it is not (as in Section 3.3 of [RFC8414]).
  • issuance_endpoint (REQUIRED): The URL that the browser sends HVT requests to. It MUST be an https URL that is same site ([WHATWG.HTML]) with the issuer identifier, so that the HVT request carries the cookies of the issuer's site; a party that fetches the metadata MUST reject it otherwise.
  • jwks_uri (REQUIRED): The URL of the issuer's JSON Web Key Set ([RFC7517]), used to verify HVTs.
  • signing_alg_values_supported (OPTIONAL): As defined by [EVP].
  • federations (OPTIONAL): An array of https URLs, each of which identifies a federation that the accounts of the issuer belong to, such as https://atproto.com. HVP does not interpret these URLs, and verification does not depend on them. A browser API uses them to offer a verifier only the handles whose issuers belong to the federations that the verifier asks for (Appendix B).

Example:

{
  "issuer": "https://issuer.example",
  "issuance_endpoint": "https://issuer.example/hvp/issuance",
  "jwks_uri": "https://issuer.example/hvp/jwks",
  "signing_alg_values_supported": ["Ed25519", "ES256"],
  "federations": ["https://atproto.com"]
}

An issuer that also acts as an EVP issuer serves its EVP metadata and its HVP metadata at their respective well-known paths. It MAY use the same keys for both.

Open Issue: Whether requiring the issuance_endpoint to be same site with the issuer identifier is appropriate, and whether [EVP] has, or should have, the same requirement, so that HVP does not diverge from it.

7.3. HVT Request

The HVT request is the token request of [EVP], including its use of HTTP Message Signatures ([RFC9421]) and the hwk key scheme, with the following substitutions:

  • The browser performs Section 5, not EVP's issuer discovery, to obtain the issuer and its metadata, and sends the request to the issuance_endpoint of the HVP metadata.
  • The request body is a JSON object with the following members:

    • handle (REQUIRED): The handle that the user selected, in normalized form (Section 4.1).
    • sub (REQUIRED): The unique user identifier that the handle resolves to, byte-for-byte as resolution produced it: for a domain handle, its DID.
  • The Sec-Fetch-Dest header has the value handle-verification.

Example:

POST /hvp/issuance HTTP/1.1
Host: accounts.issuer.example
Cookie: session=...
Content-Type: application/json
Sec-Fetch-Dest: handle-verification
Content-Digest: \
    sha-256=:...:
Signature-Input: sig=("@method" "@authority" "@path" \
    "content-digest" "signature-key");created=1792620000
Signature: sig=:MEQCIHd8Y8qYKm5e3dV8y....:
Signature-Key: sig=hwk;kty="OKP";crv="Ed25519"; \
    x="JrQLj5P_89iXES9-vFgrIy29clF9CC_oPPsw3c5D0bs";alg="Ed25519"

{"handle":"samuelgoto.bsky.social",
 "sub":"did:plc:bftbdgcbe4zclvpkboe4hleq"}

On receipt of an HVT request, the issuer:

  1. Verifies the request headers, the HTTP Message Signature, and the Content-Digest, as in the request verification of [EVP], with handle-verification in place of email-verification as the expected Sec-Fetch-Dest.
  2. Verifies that handle is a handle of a handle type that the issuer supports (Section 4), in normalized form, and that sub is a valid unique user identifier for that handle type: for a domain handle, a syntactically valid did:plc or did:web DID. If it does not, the issuer returns an invalid_request error (Section 7.3.1).
  3. Checks that the cookies represent a signed-in user who controls handle, and whose account is sub. If they do not, the issuer returns an authentication_required error.
  4. SHOULD resolve handle (Section 5) and check that it is the authorized issuer and that resolution yields sub. An HVT from an issuer that is not authorized is rejected by the verifier anyway, so this check is not needed for security, but it avoids issuing HVTs that will fail.
  5. Creates an HVT (Section 8.2) and returns it as the value of issuance_token in an application/json response, as in [EVP].

7.3.1. Error Responses

The issuer returns the error responses of [EVP] (invalid_request, invalid_signature, authentication_required, and server errors), with the handle member in place of the email member.

8. Handle Verification Token (HVT)

The Handle Verification Token (HVT) is a JWT ([RFC7519]) issued by an issuer. It contains a handle, the unique user identifier that the handle resolves to, and the browser's public key.

8.1. HVT Structure

8.1.2. Payload

  • iss (REQUIRED): The issuer identifier (Section 7.1).
  • iat (REQUIRED): The time the HVT was issued.
  • cnf (REQUIRED): A confirmation claim ([RFC7800]) containing the browser's public key as a jwk, reproduced from the Signature-Key header of the HVT request with the same members, including its alg.
  • handle (REQUIRED): The handle that the user controls, byte-for-byte as it appeared in the HVT request.
  • sub (REQUIRED): The unique user identifier that the handle resolves to (Section 5), byte-for-byte as it appeared in the sub member of the HVT request.

Example:

{
  "iss": "https://bsky.social",
  "iat": 1792620000,
  "cnf": {
    "jwk": {
      "kty": "OKP",
      "crv": "Ed25519",
      "x": "JrQLj5P_89iXES9-vFgrIy29clF9CC_oPPsw3c5D0bs",
      "alg": "Ed25519"
    }
  },
  "handle": "samuelgoto.bsky.social",
  "sub": "did:plc:bftbdgcbe4zclvpkboe4hleq"
}

8.1.3. Format

As with the EVT, the HVT has a ~ appended to it for compatibility with SD-JWT with Key Binding ([I-D.ietf-oauth-selective-disclosure-jwt]).

8.2. HVT Creation

After verifying the request and authenticating the user, the issuer:

  1. Constructs the header with alg, kid, and typ.
  2. Constructs the payload with iss, iat, cnf (containing the public key from the Signature-Key header, including its alg member), handle, and sub.
  3. Signs the JWT with the private key identified by kid.
  4. Appends ~ to the signed JWT.

8.3. HVT Verification

Both the browser and the verifier verify the HVT:

  1. Parse the HVT into its header, payload, and signature.
  2. Verify that typ is hvt+jwt, and validate alg and kid as for the EVT in [EVP].
  3. Extract the iss, iat, cnf, handle, and sub claims. Verify that handle is a handle of a handle type that the verifier supports (Section 4), in normalized form.
  4. Resolve handle (Section 5), and verify that:

    • the unique user identifier that resolution produces is byte-for-byte identical to sub; and
    • iss is byte-for-byte identical to the authorized issuer.
  5. Fetch the issuer metadata from iss (Section 7.2).
  6. Fetch the issuer's public keys from its jwks_uri, and verify the HVT's signature with the key identified by kid.
  7. Verify that iat is within an acceptable time window.

The browser additionally verifies that handle and sub are byte-for-byte identical to the handle and unique user identifier it requested, and that cnf.jwk matches the public key it generated. The browser MAY reuse the results of the resolution it performed before the HVT request for step 4.

9. Key Binding (HVT+KB)

Key binding is the key binding of [EVP], with the HVT in place of the EVT. The browser creates a KB-JWT whose aud is the verifier's origin, whose nonce is the verifier's session nonce, and whose sd_hash is the hash of the HVT (including its trailing ~), and signs it with its private key. The HVT+KB is:

<HVT>~<KB-JWT>

The HVT+KB is compatible with SD-JWT with Key Binding ([I-D.ietf-oauth-selective-disclosure-jwt]), in the same way as the EVT+KB.

10. HVT+KB Verification

The verifier receives the HVT+KB, and verifies it by:

  1. Verifying the KB-JWT as in [EVP], with the HVT in place of the EVT: its alg matches the alg of the HVT's cnf.jwk, its aud is the verifier's origin, its nonce is the session's nonce, its iat is recent, and its sd_hash is the hash of the HVT.
  2. Verifying the HVT as described in Section 8.3, including resolving the handle itself.
  3. Verifying the KB-JWT's signature with the public key in the HVT's cnf.jwk.

If all verification steps pass, the verifier has verified that the user controls the handle in the handle claim. The verifier SHOULD identify the user's account by the unique user identifier in the sub claim, and treat the handle as a mutable attribute of the account (Section 12.2).

The verifier MUST perform its own resolution in step 2, and MUST NOT rely on resolution results provided by the browser. The verifier's resolution is what establishes that the issuer is authorized for the handle.

11. Privacy Considerations

11.1. Three-Party Model

As in [EVP], the browser intermediates between the verifier and the issuer. The issuer learns which handle the user is verifying, but not which verifier the user is signing in to, and the verifier learns the handle only after the user has chosen to share it.

11.2. Handles Are Public Identifiers

Handles and unique user identifiers are public by design: they are meant to be shared, are the same at every site, and are linked to the user's public activity in the AT Protocol or ActivityPub. A verifier that receives a handle can correlate the user with other verifiers that receive the same handle, and with the user's public activity under that handle. HVP does not change this, and offers no equivalent of the private email addresses of [EVP]. Browser APIs that use HVP should make clear to the user that the verifier will learn their handle and unique user identifier.

11.3. Resolution Traffic

Resolution consists of DNS queries and unauthenticated HTTPS requests. Unlike the lookup of [EVP], which queries only the email domain and is shared by every user of that domain, every resolution step of HVP is specific to one user: for a domain handle, for example, the DNS name queried is the handle itself, and the DID document is the user's. Any party that observes a resolution therefore learns which user is being resolved, and when.

When the browser resolves a handle, the DNS resolver and the servers involved (for a domain handle, the handle's host, the host of a did:web DID document, and the PLC directory for did:plc) can observe that the user's device is resolving that handle, but not which verifier is involved, because the requests carry no credentials or referrer.

When the verifier resolves a handle to verify an HVT+KB, the requests carry nothing that names the verifier either, but they are sent by the verifier itself. The servers involved see the verifier's network address, which they can often attribute to the verifier, for example through reverse DNS or the verifier's hosting provider, or by signing in to a suspected verifier themselves and observing which address resolves their handle. Combined with the per-user nature of resolution, this can let them learn that a particular user signed in to the verifier at that time. When a server involved in resolution, such as the host of a domain handle or of its DID document, is also the user's issuer, as is common for handles under a server's domain, the issuer can correlate the HVTs it issues with the resolutions that follow, which weakens the property, described above, that the issuer does not learn which verifier the user signs in to. A verifier that goes on to interact with the user's account in the AT Protocol or ActivityPub reveals this anyway.

Verifiers can reduce this exposure by:

  • caching resolution results within the limits of Section 5.3, which helps for returning users but not for first sign-ins;
  • resolving through an intermediary shared by many verifiers, such as a resolution service reached over Oblivious HTTP ([RFC9458]), so that the servers involved do not see the verifier's network address; and
  • for did:plc DIDs, maintaining a local copy of the PLC directory, as described below.

A browser or verifier can remove the PLC directory from per-sign-in traffic entirely by maintaining a local copy of the directory, ingested through its bulk export endpoint and kept current through its streaming endpoint ([DID-PLC]), and verifying each operation's signatures as it is ingested. Resolving a did:plc DID then consults the local copy and sends no request that reveals which DID is being resolved. This has limits:

  • It applies only to the DID-to-DID-document step. The handle-to-DID step still queries DNS records and well-known URLs of the handle's host, which observe the resolution as described above.
  • It does not change the trust placed in the PLC directory (Section 12.5.4): the local copy contains only what the directory chose to publish, in the order it chose.
  • It requires storing and continuously ingesting every operation for every DID, which is a significant cost, particularly for browsers on constrained devices and for small verifiers.

12. Security Considerations

12.1. Resolution Is Not Authentication

Resolution establishes which issuer is authorized for a handle. It does not establish that the user controls the handle: that is established by the issuer's signature on an HVT, which the issuer produces only after authenticating the user. An attacker who manipulates the browser's resolution can cause the browser to send the user's handle to an issuer of the attacker's choosing, but cannot produce an HVT that the verifier accepts, because the verifier resolves the handle itself.

12.2. Handle Reassignment

Handles can be reassigned: a domain can expire and be re-registered, and a hosting provider can let a new account take a handle that a deleted account used. Verifiers SHOULD identify accounts by the unique user identifier in the sub claim rather than by the handle claim. For example, the DID of a domain handle survives changes of handle, so a verifier that keys accounts on it is not affected when a handle moves to another account.

12.3. Server-Side Request Forgery

Resolution makes a verifier fetch URLs derived from user-controlled input. Verifiers MUST resolve only https URLs, MUST NOT fetch URLs whose host resolves to a loopback, link-local, or private IP address, and SHOULD apply limits on redirects, response size, and time.

12.4. Token Confusion

The typ of the HVT (hvt+jwt) is distinct from that of the EVT (evt+jwt), so an HVT cannot be presented as an EVT or the other way around.

12.5. Domain Handles

The following considerations are specific to domain handles.

12.5.1. DNS Spoofing

Resolution of domain handles relies on DNS TXT records, which are not authenticated unless DNSSEC is used. As in [EVP], spoofing the browser's DNS resolution discloses the handle to the attacker but yields no HVT that the verifier will accept, while spoofing the verifier's DNS resolution lets the attacker choose the DID, and therefore the authorized issuer. Verifiers SHOULD use a DNSSEC-validating resolver, and operators of handle domains SHOULD sign their zones. The other resolution steps rely on HTTPS and the Web PKI.

12.5.2. Domain Control Is Handle Control

Every domain handle rests on control of a DNS name: whoever controls the handle's domain controls its _handle and _atproto records and its well-known URLs, and therefore which DID the handle resolves to. For a did:web DID on the handle's domain, the same party also controls the DID document, and therefore the authorized issuer. This is by design: it is what lets users bring their own domain. It also means that when a domain changes hands, for example when it expires and is registered by someone else, its handle changes hands too. The back-link check means the new owner cannot claim the previous owner's DID, but can point the handle at a DID of their own.

12.5.3. Conflicting Records

A domain handle can be mapped to a DID by up to four records (Section 6.4): two with the new names, and two with the AT Protocol names, which are used only as a fallback (Section 6.4.1). Resolution is designed so that the browser and the verifier cannot reach different results for the same handle, and so that an attacker who can block or delay some lookups cannot choose between them:

  • Two records with the same names that name different DIDs cause resolution to fail, rather than letting the result depend on which record a resolving party happened to read.
  • The AT Protocol names are used only when the new names have positively established that the handle names nothing. If a lookup with the new names fails for any other reason, resolution fails, rather than falling back. Otherwise, an attacker who can make that lookup fail, for one party but not the other, could make the two parties resolve the handle through different records.

An attacker who can block lookups can still cause resolution to fail, but cannot cause it to succeed with a different result. Only the owner of the handle's domain can create records that disagree.

12.5.4. Trust in the PLC Directory

For did:plc DIDs, resolution trusts the PLC directory to return the current DID document, and therefore the current authorized issuer.

Operations in the PLC directory are signed by the account's rotation keys, and the DID is derived from the hash of the signed genesis operation, so the operation log is self-certifying: a resolving party can fetch the log and verify it instead of trusting the directory's rendering of the DID document. Doing so prevents the directory from forging operations, and therefore from naming an issuer that the account's rotation keys did not authorize.

Verifying the log does not remove all trust in the directory. As the "PLC Server Trust Model" section of [DID-PLC] states, some trust in the directory is required, and its attacks are limited to:

  • Denial of service: rejecting valid operations, or refusing to serve some information about the DID. For HVP, this includes withholding an operation that changes or removes the HandleVerification service, so that a resolving party keeps accepting HVTs from an issuer that the user no longer authorizes, or withholding an operation that adds one, so that a newly authorized issuer is rejected.
  • Misordering: in the event of a fork in the DID document's history, serving the "wrong" fork. For HVP, this determines which HandleVerification service is in effect.

Neither the browser nor the verifier can detect these attacks. Operations are submitted only to the PLC directory, and are not published anywhere else that a resolving party could observe independently: mirrors and replicas obtain their operations from the directory, and the timestamps that the directory assigns to operations, on which the resolution of forks depends, are not signed. Both the browser and the verifier therefore place this trust in the PLC directory, and HVP inherits it for every handle that resolves to a did:plc DID. Handles that resolve to a did:web DID instead rely on the security of the DID's domain, as described in Section 12.5.1.

13. IANA Considerations

13.1. Well-Known URIs

This document registers the following well-known URIs in the "Well-Known URIs" registry ([RFC8615]):

  • URI suffix: handle

    • Change controller: IETF
    • Reference: Section 6.4 of this document
    • Status: permanent
  • URI suffix: handle-verification

    • Change controller: IETF
    • Reference: Section 7.2 of this document
    • Status: permanent
  • URI suffix: atproto-did

13.2. Underscored and Globally Scoped DNS Node Names

This document registers the following entries in the "Underscored and Globally Scoped DNS Node Names" registry ([RFC8552]):

Open Issue: The atproto-did well-known URI and the _atproto DNS node name are defined by the AT Protocol. Whether this document or a document of the IETF ATP working group should register them needs coordination with that working group.

13.3. JSON Web Token Claims

This document registers the following claim in the "JSON Web Token Claims" registry ([RFC7519]):

  • Claim Name: handle
  • Claim Description: A handle that the subject controls
  • Change Controller: IETF
  • Specification Document(s): Section 8 of this document

13.4. Media Types

This document registers the application/hvt+jwt media type in the "Media Types" registry ([RFC6838]), for the typ header of the HVT.

Open Issue: Complete the media type registration template.

14. Design Rationale

14.1. Why Handle Types?

Users are identified in different ways in different ecosystems: by a domain name in the AT Protocol, by a URL in Solid and ActivityPub, and by a user name and a domain in ActivityPub implementations. Each of these already has a resolution mechanism that is deployed, and that the others do not use. Rather than choosing one, HVP lets each be defined as a handle type, with its own syntax and resolution, and shares everything that does not depend on how a handle is resolved: the issuers, the tokens, and their verification. Requiring the syntaxes to be mutually exclusive means that a browser, an issuer, and a verifier always agree on how a handle is resolved, from the handle alone.

14.2. Why Resolve Domain Handles to a DID?

HVP needs three things from the target of a domain handle: a stable identifier for the account, a document that claims the handle back, and a place to name the issuer. A DID provides all three, through its DID document, in a form that is not specific to any ecosystem, and the AT Protocol already resolves domain handles to DIDs.

The record names the kind of value in its key, did= as in the AT Protocol, so that resolution does not have to infer it, and holds a single value rather than a list. Values of other kinds, such as username handles ([USERNAME-HANDLES]), domain handles, or email addresses, can be added later with keys of their own, which resolving parties that do not support them ignore.

Only did:plc and did:web are supported, because they are the methods that the AT Protocol uses. Other methods can be added if an ecosystem needs them. A did:key DID is not sufficient on its own: its DID document is derived from the key, and so has no alsoKnownAs to claim the handle back and no services to name the issuer.

14.3. Why New Names for the Handle Records?

The AT Protocol's _atproto record and /.well-known/atproto-did URL are specific to one ecosystem, and an ActivityPub account that published them would appear to be an AT Protocol account. Neutral names let any ecosystem use the same records. The names do not mention DIDs, so that they can refer to other kinds of identifiers later, and the well-known document is JSON, so that it can carry more information later. Resolving parties fall back to the AT Protocol names when the new ones are absent (Section 6.4.1), so that every existing AT Protocol handle keeps working, and the new names take precedence when present, so that a handle can move to the new names at its own pace.

14.4. Why Not OAuth?

AT Protocol OAuth ([ATPROTO-OAUTH]) and the OAuth APIs of ActivityPub servers let a verifier sign a user in, and grant it access to the user's account. But they ask the verifier to present a text field for the user to type a handle, and they reveal the verifier to the user's authorization server. HVP lets the browser offer the user's handles without the user typing them, and keeps the verifier hidden from the issuer. A verifier that needs access to the user's account, and not only proof that the user controls a handle, still uses OAuth.

HVP also changes how interoperability is achieved. In OAuth, the authorization server's response goes to the client, and nothing in between checks it: whether an authorization server interoperates with a given client is discovered by testing that pair, and ecosystems with many of each rely on conformance test suites and certification programs. In HVP, the browser verifies every HVT (Section 8.3) with the same checks that a verifier performs, before any verifier receives it, and does not deliver an HVT that fails. An issuer that develops against a browser therefore has an independent verifier in its development loop, and an HVT that the browser accepts is accepted by any verifier that implements Section 10, unless the handle's resolution changes in between. Each issuer and each verifier needs to interoperate only with the browser's verification, rather than each issuer with each verifier.

14.5. Why Generalize EVP?

EVP already solves the same problem for email addresses, with a three-party model, proof of possession of a browser key, and a token format compatible with SD-JWT. Reusing it lets verifiers and issuers share code and keys between the two, and keeps the privacy properties. An alternative would be to profile OpenID Connect, but OpenID Connect's redirect-based flow reveals the verifier to the provider.

14.6. Why Does the Verifier Resolve the Handle?

The verifier cannot trust the browser's resolution, which could be manipulated, and it cannot trust the issuer to say which handles it is authorized for. Resolving the handle itself is how the verifier checks that the issuer is authorized, in the same way that the verifier looks up the DNS delegation in [EVP].

14.7. Why Require an Explicit HandleVerification Service?

An account already says who hosts it: an AT Protocol account names its PDS, whose authorization server authenticates it, and an ActivityPub account is hosted by the server of its actor. HVP could infer its issuer from them. It does not, for two reasons. First, there is little to gain: an issuer has to deploy HVP's issuance endpoint and metadata anyway, and a server can add a HandleVerification service to the DID documents of its accounts at the same time. Second, an HVT is a new kind of statement, presented to verifiers through the browser, and a user who authorized a server to host their account, or to sign them in with OAuth, has not necessarily authorized it to make that statement. An explicit service makes the user's opt-in visible in their DID document, or other controlled identifier document.

An explicit service also lets a user choose an issuer that is not the server that hosts their account, and gives both ecosystems the same way to name it.

14.8. Why Define Handle Resolution Here?

Handle resolution is useful outside any one ecosystem. Defining it here, in a form that is shared by the AT Protocol and ActivityPub, makes HVP implementable from this document alone, by verifiers that implement neither protocol.

14.9. Why Are Email Addresses Out of Scope?

Email addresses already have a protocol of their own, [EVP], which is further along, has implementations, and has requirements specific to email, such as private email addresses. A browser API can use EVP for email addresses and HVP for handles side by side; an email address is never parsed as a domain handle (Section 4.2).

15. Implementation Status

Note: This section is to be removed before publishing as an RFC.

There are no implementations of this document yet. Prototypes of the related IdP Registration API ([IDP-REGISTRATION]) exist in Chrome behind a flag.

16. Document History

Note: This section is to be removed before publishing as an RFC.

17. References

17.1. Normative References

[CID-1.0]
W3C, "Controlled Identifiers v1.0", W3C Recommendation, , <https://www.w3.org/TR/cid-1.0/>.
[EVP]
Hardt, D. and S. Goto, "Email Verification Protocol", Work in Progress, Internet-Draft, draft-hardt-email-verification-latest, , <https://datatracker.ietf.org/doc/draft-hardt-email-verification/>.
[I-D.ietf-oauth-selective-disclosure-jwt]
Fett, D., Yasuda, K., and B. Campbell, "Selective Disclosure for JWTs (SD-JWT)", Work in Progress, Internet-Draft, draft-ietf-oauth-selective-disclosure-jwt-22, , <https://datatracker.ietf.org/doc/html/draft-ietf-oauth-selective-disclosure-jwt-22>.
[RFC1035]
Mockapetris, P., "Domain names - implementation and specification", STD 13, RFC 1035, DOI 10.17487/RFC1035, , <https://www.rfc-editor.org/info/rfc1035>.
[RFC2119]
Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, , <https://www.rfc-editor.org/info/rfc2119>.
[RFC3986]
Berners-Lee, T., Fielding, R., and L. Masinter, "Uniform Resource Identifier (URI): Generic Syntax", STD 66, RFC 3986, DOI 10.17487/RFC3986, , <https://www.rfc-editor.org/info/rfc3986>.
[RFC5234]
Crocker, D., Ed. and P. Overell, "Augmented BNF for Syntax Specifications: ABNF", STD 68, RFC 5234, DOI 10.17487/RFC5234, , <https://www.rfc-editor.org/info/rfc5234>.
[RFC5890]
Klensin, J., "Internationalized Domain Names for Applications (IDNA): Definitions and Document Framework", RFC 5890, DOI 10.17487/RFC5890, , <https://www.rfc-editor.org/info/rfc5890>.
[RFC6838]
Freed, N., Klensin, J., and T. Hansen, "Media Type Specifications and Registration Procedures", BCP 13, RFC 6838, DOI 10.17487/RFC6838, , <https://www.rfc-editor.org/info/rfc6838>.
[RFC7517]
Jones, M., "JSON Web Key (JWK)", RFC 7517, DOI 10.17487/RFC7517, , <https://www.rfc-editor.org/info/rfc7517>.
[RFC7519]
Jones, M., Bradley, J., and N. Sakimura, "JSON Web Token (JWT)", RFC 7519, DOI 10.17487/RFC7519, , <https://www.rfc-editor.org/info/rfc7519>.
[RFC7800]
Jones, M., Bradley, J., and H. Tschofenig, "Proof-of-Possession Key Semantics for JSON Web Tokens (JWTs)", RFC 7800, DOI 10.17487/RFC7800, , <https://www.rfc-editor.org/info/rfc7800>.
[RFC8174]
Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, , <https://www.rfc-editor.org/info/rfc8174>.
[RFC8259]
Bray, T., Ed., "The JavaScript Object Notation (JSON) Data Interchange Format", STD 90, RFC 8259, DOI 10.17487/RFC8259, , <https://www.rfc-editor.org/info/rfc8259>.
[RFC8414]
Jones, M., Sakimura, N., and J. Bradley, "OAuth 2.0 Authorization Server Metadata", RFC 8414, DOI 10.17487/RFC8414, , <https://www.rfc-editor.org/info/rfc8414>.
[RFC8552]
Crocker, D., "Scoped Interpretation of DNS Resource Records through "Underscored" Naming of Attribute Leaves", BCP 222, RFC 8552, DOI 10.17487/RFC8552, , <https://www.rfc-editor.org/info/rfc8552>.
[RFC8615]
Nottingham, M., "Well-Known Uniform Resource Identifiers (URIs)", RFC 8615, DOI 10.17487/RFC8615, , <https://www.rfc-editor.org/info/rfc8615>.
[RFC9421]
Backman, A., Ed., Richer, J., Ed., and M. Sporny, "HTTP Message Signatures", RFC 9421, DOI 10.17487/RFC9421, , <https://www.rfc-editor.org/info/rfc9421>.
[WHATWG.HTML]
WHATWG, "HTML Standard", Living Standard, , <https://html.spec.whatwg.org/multipage/>.

17.2. Informative References

[ACTIVITYPUB]
W3C, "ActivityPub", W3C Recommendation, , <https://www.w3.org/TR/activitypub/>.
[ATPROTO]
Bluesky Social PBC, "AT Protocol", , <https://atproto.com/specs/atp>.
[ATPROTO-DID]
Bluesky Social PBC, "AT Protocol: DID", , <https://atproto.com/specs/did>.
[ATPROTO-HANDLE]
Bluesky Social PBC, "AT Protocol: Handle", , <https://atproto.com/specs/handle>.
[ATPROTO-OAUTH]
Bluesky Social PBC, "AT Protocol: OAuth", , <https://atproto.com/specs/oauth>.
[ATPROTO-URI]
Bluesky Social PBC, "AT Protocol: AT URI Scheme", , <https://atproto.com/specs/at-uri-scheme>.
[DID-CORE]
W3C, "Decentralized Identifiers (DIDs) v1.0", W3C Recommendation, , <https://www.w3.org/TR/did-1.0/>.
[DID-PLC]
Bluesky Social PBC, "DID PLC Method", , <https://web.plc.directory/spec/v0.1/did-plc>.
[DID-WEB]
W3C Credentials Community Group, "did:web Method Specification", , <https://w3c-ccg.github.io/did-method-web/>.
[EMAIL-VERIFICATION-API]
W3C Web Incubator Community Group, "Email Verification API", , <https://github.com/WICG/email-verification-protocol>.
[FEDCM]
W3C Federated Identity Working Group, "Federated Credential Management API", , <https://w3c-fedid.github.io/FedCM/>.
[HANDLES-TF]
Ahooja, A. and E. Smith, "Proposal to Create Handles Task Force", W3C Social Web Incubator Community Group, , <https://lists.w3.org/Archives/Public/public-swicg/2026Aug/0015.html>.
[IDP-REGISTRATION]
W3C Federated Identity Community Group, "IdP Registration API", , <https://w3c-fedid.github.io/idp-registration/>.
[JSON-LD11]
W3C, "JSON-LD 1.1", W3C Recommendation, , <https://www.w3.org/TR/json-ld11/>.
[RFC1464]
Rosenbaum, R., "Using the Domain Name System To Store Arbitrary String Attributes", RFC 1464, DOI 10.17487/RFC1464, , <https://www.rfc-editor.org/info/rfc1464>.
[RFC3696]
Klensin, J., "Application Techniques for Checking and Transformation of Names", RFC 3696, DOI 10.17487/RFC3696, , <https://www.rfc-editor.org/info/rfc3696>.
[RFC9458]
Thomson, M. and C. A. Wood, "Oblivious HTTP", RFC 9458, DOI 10.17487/RFC9458, , <https://www.rfc-editor.org/info/rfc9458>.
[URL-HANDLES]
Goto, S., @bumblefudge.com, and A. Coburn, "URL Handles", Work in Progress, Internet-Draft, draft-goto-url-handle-verification-latest, , <https://samuelgoto.github.io/handle-verification/draft-goto-url-handle-verification.html>.
[USERNAME-HANDLES]
Goto, S., "Username Handles", Work in Progress, Internet-Draft, draft-goto-username-handle-verification-latest, , <https://samuelgoto.github.io/handle-verification/draft-goto-username-handle-verification.html>.

Appendix A. Examples

This appendix shows the resolution of handles. The examples are real handles at the time of writing, which publish only the AT Protocol names, and so resolve through the fallback of Section 6.4.1. Neither publishes a HandleVerification service yet, so each shows what would be added to opt in. The values will change over time.

A.1. AT Protocol Handle on a Self-Hosted PDS

Handle:        @sgo.to
DNS TXT _atproto.sgo.to -> "did=did:plc:thaoj4hhvraos7y6v3yog7re"
DID document (plc.directory):
  alsoKnownAs: ["at://sgo.to"]                  (back-link ok)
DID:           did:plc:thaoj4hhvraos7y6v3yog7re
To opt in, add to the DID document's services:
  { "id": "#handle_verification", "type": "HandleVerification",
    "serviceEndpoint": "https://pds.sgo.to" }
Issuer:        https://pds.sgo.to

A.2. AT Protocol Handle on a Hosted PDS

Handle:        @samuelgoto.bsky.social
GET https://samuelgoto.bsky.social/.well-known/atproto-did
  -> did:plc:bftbdgcbe4zclvpkboe4hleq
DID document (plc.directory):
  alsoKnownAs: ["at://samuelgoto.bsky.social"]  (back-link ok)
DID:           did:plc:bftbdgcbe4zclvpkboe4hleq
To opt in, add to the DID document's services:
  { "id": "#handle_verification", "type": "HandleVerification",
    "serviceEndpoint": "https://bsky.social" }
Issuer:        https://bsky.social

Appendix B. Relationship to IdP Registration

This section is non-normative.

The W3C IdP Registration API ([IDP-REGISTRATION]) extends FedCM ([FEDCM]) so that a verifier can request any issuer in an open federation, and the browser keeps a registry of the user's handles. It relates to HVP the way the Email Verification API ([EMAIL-VERIFICATION-API]) relates to EVP: the browser API decides which handles to offer, lets the user register and select them, and delivers the HVT+KB to the verifier, while HVP defines what a handle is, how it is resolved, and how the HVT is issued and the HVT+KB is verified. In particular:

Appendix C. Acknowledgments

This document builds on the Email Verification Protocol by Dick Hardt and Sam Goto, on the AT Protocol handle, DID, and AT URI specifications by Bluesky, and on the Handles Task Force proposal by Anuj Ahooja and Emelia Smith.

Author's Address

Sam Goto
Google