Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
f54c423
quest: open the auth line
kixelated Sep 24, 2026
288ea4f
feat(net): exchange grants over lite-06 AUTH streams (#4062)
kixelated Sep 25, 2026
1c21a2e
Merge remote-tracking branch 'origin/main' into quest/m1/auth/README
kixelated Sep 25, 2026
615f843
feat(net): exchange grants over the moq-transport MoQ Auth extension …
kixelated Sep 25, 2026
600cc6b
Merge origin/main into quest/m1/auth/README
kixelated Sep 25, 2026
5304553
Merge origin/main into quest/m1/auth/README
kixelated Sep 25, 2026
6f51a4a
quest(auth): path patterns join the line so AUTH ships with pattern g…
kixelated Sep 26, 2026
29463bf
test(interop): assert each AUTH cell's grant and refuse a publish out…
kixelated Sep 26, 2026
0bb0aa2
feat(net): reset revoked lite streams with UNAUTHORIZED (#4179)
kixelated Sep 26, 2026
09675cc
Merge origin/main into quest/m1/auth/README
kixelated Sep 26, 2026
404d05c
fix(net): a withdrawn token ends with Cancel even if the acceptor's F…
kixelated Sep 26, 2026
3fdc123
feat(net): carry path patterns in moq-lite AUTH_OK grants (#4277)
kixelated Sep 27, 2026
ea7e984
Merge origin/main into quest/m1/auth/README
kixelated Sep 28, 2026
c534db8
chore(quest): claim auth-ok-preflight
kixelated Sep 29, 2026
4b927ac
fix(net): refuse an AUTH_OK that cannot be encoded, on any sizing error
kixelated Sep 29, 2026
ee275b2
fix(net): mirror the lite 64 MiB encode ceiling in JS
kixelated Sep 29, 2026
acc9a3d
docs(net): say what a lite auth reset means after a grant
kixelated Sep 29, 2026
240a2b8
chore(quest): claim quest/m1/auth/error-codes
kixelated Sep 29, 2026
4186b50
quest(auth/narrowing): claim
kixelated Sep 29, 2026
c63b1d1
fix(net): make every AUTH stream ending exact on both sides
kixelated Sep 29, 2026
360c191
Merge pull request #4446 from moq-dev/quest/m1/auth/auth-ok-preflight
kixelated Sep 29, 2026
c82f9c7
quest(auth): AUTH protocol violations close the session everywhere
kixelated Sep 29, 2026
7e6dd2f
feat(net): narrow a live session's grant in place
kixelated Sep 29, 2026
7bc0dd0
Merge branch 'quest/m1/auth/README' into quest/m1/auth/error-codes
kixelated Sep 29, 2026
8b26a0e
quest(auth): cover a malformed inbound AUTH too
kixelated Sep 29, 2026
e34649c
feat(relay): narrow a live session on a narrower re-check
kixelated Sep 29, 2026
e2891ba
fix(net): arm JS grant watches before a request's first await
kixelated Sep 29, 2026
39d5f08
fix(net): log the AUTH_ERROR code limit, and drop the raw timer in it…
kixelated Sep 29, 2026
47abdbb
quest(auth/narrowing): done; bench the narrowing and its gates
kixelated Sep 29, 2026
d0d560a
Merge remote-tracking branch 'origin/quest/m1/auth/README' into quest…
kixelated Sep 29, 2026
196bb9a
chore: fmt and doc touch-ups
kixelated Sep 29, 2026
f211e7b
quest(auth): validate the IETF AUTH request ID too
kixelated Sep 29, 2026
7e773ee
test(relay): iterate subscriptions directly
kixelated Sep 29, 2026
5af6279
quest(auth): unknown reply types and a second AUTH, and audit the rest
kixelated Sep 29, 2026
95c01d1
quest(auth): JS fetchGroup stops when its grant is revoked
kixelated Sep 30, 2026
4da7654
feat(net): replace narrow with a symmetric auth::Handle::limit
kixelated Sep 30, 2026
ee8d254
chore(net): box the attached route
kixelated Sep 30, 2026
dbbe027
Merge remote-tracking branch 'origin/main' into quest/m1/auth/README
kixelated Sep 30, 2026
dc2b986
refactor(net): rename auth::Handle::limit to authorize
kixelated Sep 30, 2026
fc5692c
Merge pull request #4554 from moq-dev/claude/plan-auth-violations
kixelated Sep 30, 2026
c508eba
Merge remote-tracking branch 'origin/quest/m1/auth/README' into tmp-4550
kixelated Sep 30, 2026
a45fc39
Merge remote-tracking branch 'origin/quest/m1/auth/README' into quest…
kixelated Sep 30, 2026
75ca4c9
fix(net): adapt to the merged base
kixelated Sep 30, 2026
1475441
quest(auth): plan lite NOT_SUPPORTED and the 65,535-byte message cap …
kixelated Sep 30, 2026
a0ef1f1
Merge remote-tracking branch 'origin/quest/m1/auth/README' into tmp-4550
kixelated Sep 30, 2026
67e16cb
Merge pull request #4560 from moq-dev/quest/m1/auth/narrowing
kixelated Sep 30, 2026
9f1d6e5
Merge origin/quest/m1/auth/README into quest/m1/auth/error-codes
kixelated Sep 30, 2026
d2755ac
test(net): bound the JS grant-stream regression wait
kixelated Sep 30, 2026
6857be9
fix(net): settle grants with the error that ended the session
kixelated Sep 30, 2026
363367d
Merge pull request #4550 from moq-dev/quest/m1/auth/error-codes
kixelated Sep 30, 2026
707f813
Merge origin/main into quest/m1/auth/README
kixelated Sep 30, 2026
8ad7606
quest(m1/auth): move peer grants to the P2P line
kixelated Sep 30, 2026
51c19e2
Merge pull request #4590 from moq-dev/claude/auth-peer-grant-move
kixelated Sep 30, 2026
7650f6c
Merge origin/main into quest/m1/auth/README
kixelated Oct 5, 2026
1746893
Merge origin/main into quest/m1/auth/README
kixelated Oct 5, 2026
657ce49
quest(auth): request caps owns the lite message ceiling; FFI shape first
kixelated Oct 6, 2026
1d109a1
quest(auth): drop the processor link, deleted on main (#4946)
kixelated Oct 6, 2026
3c79ea0
feat(net)!: AUTH moves to moq-lite-07-wip (#5004)
kixelated Oct 7, 2026
ebcff39
Merge origin/main into quest/m1/auth/README
kixelated Oct 9, 2026
a5e5acb
refactor(net): group a session's handles so Session::new stays under …
kixelated Oct 9, 2026
724e34c
Merge remote-tracking branch 'origin/main' into auth-line-iterate
kixelated Oct 9, 2026
f3f0ef3
fix(net): gate standalone FETCH, lite announces, and TRACK_INFO on th…
kixelated Oct 9, 2026
a9f7e1b
Merge origin/main into quest/m1/auth/README
kixelated Oct 9, 2026
2fa39d4
fix(net): hold the grant across FETCH responses and TRACK_STATUS
kixelated Oct 9, 2026
6b7008d
Merge origin/main into quest/m1/auth/README
kixelated Oct 9, 2026
a710bd8
test(net): write the mid-fetch grant test's frame with main's textFrame
kixelated Oct 9, 2026
7493661
fix(net): cap the IETF AUTH_ERROR reason from its prefix
kixelated Oct 9, 2026
960118c
Merge remote-tracking branch 'origin/main' into claude/quest-iterate-…
kixelated Oct 9, 2026
86a10fe
fix(net): adapt the subscription grant arm to main's run_subscription…
kixelated Oct 9, 2026
5291e53
quest: hold every incoming request to the grant through a dispatcher-…
kixelated Oct 9, 2026
22738c6
fix(net): hold joining FETCH and TRACK_STATUS answers to the grant
kixelated Oct 9, 2026
76d08ba
fix(js): reset a TRACK_INFO answer when the grant shrinks mid-write
kixelated Oct 9, 2026
5c136aa
Merge remote-tracking branch 'origin/main' into claude/quest-iterate-…
kixelated Oct 9, 2026
8016021
test(interop): give the C++ cells their per-cell token URL
kixelated Oct 9, 2026
c2c12d0
Merge remote-tracking branch 'origin/main' into claude/quest-iterate-…
kixelated Oct 9, 2026
9fe5478
Merge remote-tracking branch 'origin/main' into claude/quest-iterate-…
kixelated Oct 9, 2026
e54d5fc
fix(net): hold a lite announce restart to the subscribe limit
kixelated Oct 10, 2026
4265219
Merge remote-tracking branch 'origin/main' into claude/quest-iterate-…
kixelated Oct 10, 2026
3203667
fix(net): keep the announce cursor when the grant grows
kixelated Oct 10, 2026
8f04641
Merge origin/main into quest/m1/auth/README
kixelated Oct 10, 2026
ce47554
quest: record the auth line landing in quest-flat-lines
kixelated Oct 10, 2026
8fdf05d
docs(js): name moq-lite-07-wip as the AUTH-capable lite version
kixelated Oct 10, 2026
9b99636
Merge origin/main into quest/m1/auth/README
kixelated Oct 10, 2026
1388eab
Merge origin/main into quest/m1/auth/README
kixelated Oct 10, 2026
872ebc2
Merge origin/main into quest/m1/auth/README
kixelated Oct 10, 2026
0ab718d
Merge origin/main into quest/m1/auth/README
kixelated Oct 10, 2026
5eb399f
fix(net): refuse trailing bytes in IETF AUTH replies
kixelated Oct 10, 2026
3c6c19a
Merge origin/main into quest/m1/auth/README
kixelated Oct 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions doc/.vitepress/drafts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,20 +128,20 @@ describe("tables render as tables", () => {

const rendered = html(page as DraftPage);

// The Bidirectional Streams table: one header, six body rows, each
// The Bidirectional Streams table: one header, seven body rows, each
// separated by a kramdown rule in the source. Cells carry the source's
// alignment, so match around the style attribute.
const header = /<th[^>]*>Stream<\/th>/.exec(rendered);
expect(header).not.toBeNull();

for (const stream of ["Announce", "Subscribe", "Fetch", "Probe", "Goaway", "Track"]) {
for (const stream of ["Announce", "Subscribe", "Fetch", "Probe", "Goaway", "Track", "Auth"]) {
expect(rendered).toMatch(new RegExp(`<td[^>]*>${stream}</td>`));
}

// One header row plus six body rows, in a single table.
// One header row plus seven body rows, in a single table.
const start = rendered.lastIndexOf("<table", header?.index);
const rows = rendered.slice(start).split("</table>")[0];
expect(rows.match(/<tr>/g) ?? []).toHaveLength(7);
expect(rows.match(/<tr>/g) ?? []).toHaveLength(8);
});
});

Expand Down
13 changes: 9 additions & 4 deletions doc/bin/relay/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,15 @@ nothing is admitted because the server was down. A grant that names nothing
refuses, and so does one already expired: expiry is exact, with no grace for
clock skew, so keep the auth server's clock in sync.

**Revalidate.** On the grant's cadence the relay asks again. A grant that no
longer covers what the session holds, or that changes `root`, `mounts`,
`peer`, or `upstream`, closes the session; it is not resized in place. A
changed `tier` keeps the session and moves its stats from then on. A 401 or
**Revalidate.** On the grant's cadence the relay asks again. A grant with the
same `root` and `mounts` resizes the live session in place, narrower or wider,
so a moderation decision lands on the session it targets and can be lifted the
same way: what falls outside a narrower grant resets with `Unauthorized`, the
broadcasts the session published outside it abort, and everything else keeps
flowing. A wider grant brings those paths back, up to what the session was
admitted with. A one-shot HTTP `/fetch` ends on a narrower grant instead. A
changed `root`, `mounts`, `peer`, or `upstream` closes the session. A changed
`tier` keeps the session and moves its stats from then on. A 401 or
403 closes it. Any other failure retries with backoff and the session lives
until `expires`, so an outage always has the bound the server chose. A grant
with `revalidate` must set `expires` for that reason.
Expand Down
18 changes: 18 additions & 0 deletions doc/concept/moq-lite.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,24 @@ A subscriber watching under a root sees advertisements named relative to that
root. When several routes advertise one prefix, each reader sees the best route
its scope can use.

## Authorization

On moq-lite 07 (`moq-lite-07-wip`, opt-in) each side presents a token on its
own Auth stream and learns what it may publish and subscribe to: a union of
[path patterns](#path-patterns), delivered exactly as issued rather than widened
to a prefix. Right after setup both sides present the credential the connection
already carried (the URL token, a client certificate, or nothing), so a
publisher learns before anyone subscribes whether its broadcasts can reach the
peer. More tokens can be added without reconnecting; the session's scope is the
union of every open token's grant.

A subscription or fetch that loses access resets with the `UNAUTHORIZED` stream
code and the session stays up. A client that publishes outside its grant closes
the session with `UNAUTHORIZED`, naming the path. moq-transport carries the same
exchange on draft-17+ through the [MoQ Auth extension](/draft/moq-auth), limited
to namespace prefixes. Older versions have no grant; the URL token keeps working
everywhere.

## Subscriptions

A subscriber names a broadcast and track. Delivery starts at the oldest group
Expand Down
1 change: 1 addition & 0 deletions doc/concept/standard.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Several project drafts extend the IETF wire without breaking it, since `SETUP`
ignores unknown parameters: [cluster](/draft/moq-cluster) routing hop lists,
[solicit](/draft/moq-solicit) to make announcements opt-in,
[hidden](/draft/moq-hidden) to keep `.`-named namespaces out of discovery,
[auth](/draft/moq-auth) to tell each peer what it may publish and subscribe to,
[active-count](/draft/moq-active-count) to count the `NAMESPACE` messages
before a `SUBSCRIBE_NAMESPACE` is caught up, and
[probe](/draft/moq-probe) for bandwidth estimation.
Expand Down
1 change: 1 addition & 0 deletions doc/lib/js/net.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ broadcast.announce();
- **Timing is per track, with no default.** A track that declares a `timescale` carries a `timestamp` on every frame; one without is [untimed](/concept/moq-lite#subscriptions) and its frames carry none. Unlike Rust, omitting it means untimed. Nothing stamps a frame for you: the publisher passes the timestamp, and the `Timed` type (`{ value, at }`) carries one into the `@moq/json` and `@moq/flate` producers.
- **Subscriber staleness is `maxDelay`.** It is media time. Passing the old `maxAge` key throws a `TypeError` naming `maxDelay`. Publisher retention is the separate `Track.Info.maxAge`.
- **Hidden paths** stay out of discovery unless the announce request opts in. See [hidden broadcasts](/concept/moq-lite#hidden-broadcasts).
- **Authorization is in band.** On moq-lite 07 (offered only when `moq-lite-07-wip` is listed in `webtransport.protocols`; the WebSocket fallback cannot offer it) and on moq-transport draft-17+ with the [MoQ Auth extension](/draft/moq-auth), `auth.grant` watches what the relay lets this side publish and subscribe to, and `auth.add(token)` presents another token without reconnecting. Publishing outside the grant closes the connection with `SessionCode.Unauthorized`, naming the path. On moq-lite, a subscription the grant stops covering resets with `StreamCode.Unauthorized` and the session stays up.
- **A graceful close waits.** `await connection.close()` withdraws announcements and gives finished tracks up to one second. `abort()` ends immediately.

Examples:
Expand Down
1 change: 1 addition & 0 deletions doc/lib/rs/moq-net.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ What you use it for, beyond what the concept page already describes:
- **Publish only while someone is watching.** `demand()` on a track, group, or broadcast says whether a subscriber is attached, which is how capture and transcode skip work nobody asked for. Holding a broadcast consumer is not demand. Holding a track consumer is, so drop one you are not reading. A shared fetch stays up until its last reader leaves.
- **You drive the session, or `moq-tokio` does.** `connect` and `accept` return a session plus a driver that never reads the clock itself. `moq_net::time::run` polls it on tokio or in the browser. `moq-tokio` and `moq-wasm` hide that. A custom transport implements `moq_net::transport::poll`.
- **A session caps what its peer can hold.** `session::Limits` (via `Client::with_limits` and `Server::with_limits`) bounds announces and subscriptions per session; the defaults suit a relay mesh, so lower them for untrusted peers. Past either, the session closes with `TOO_MANY_REQUESTS`. On moq-transport drafts 14 to 16 they also size the request-ID window, which every request counts against, so very low limits can starve it. Peer-declared lengths are capped before they are buffered.
- **Authorization is in band.** On moq-lite 07 (`moq-lite-07-wip`, opt-in) and on moq-transport draft-17+ with the [MoQ Auth extension](/draft/moq-auth), `session.auth()` presents tokens without reconnecting and watches the grant the peer sent back. A server verifies tokens itself by taking `handshake.auth().requests()` before `ok()`. `authorize(&grant)` narrows or widens a live session on any version: what falls outside resets with `Unauthorized` and the session stays up. A client that publishes outside its grant closes the session, naming the path.
- **A graceful close waits.** `session.close().await` withdraws announcements and gives finished tracks up to one second to deliver. `abort` ends immediately. IETF drafts 14 through 16 send withdrawals without waiting.

```bash
Expand Down
229 changes: 229 additions & 0 deletions drafts/draft-lcurley-moq-auth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
---
title: "MoQ Auth Extension"
abbrev: "moq-auth"
category: info

docname: draft-lcurley-moq-auth-latest
submissiontype: IETF # also: "independent", "editorial", "IAB", or "IRTF"
number:
date:
v: 3
area: wit
workgroup: moq

author:
-
fullname: Luke Curley
email: kixelated@gmail.com

normative:
moqt: I-D.ietf-moq-transport

informative:
moq-lite: I-D.lcurley-moq-lite

--- abstract

This document defines an extension for MoQ Transport {{moqt}} that tells an endpoint what its peer will let it publish and subscribe to.
Either endpoint presents a token on a request stream of its own, and the peer answers with the namespace prefixes the token grants and when that grant lapses.
Tokens can be added and withdrawn for the life of the session, so a session can outlive the credential it started with.
An endpoint that knows its grant can fail loudly on a publication that will never be accepted, instead of waiting for a subscription that never comes.

--- note_Note_to_Readers

This document was generated by an AI model from the implementation at [github.com/moq-dev/moq](https://github.com/moq-dev/moq) and is maintained alongside it.
Submit an [issue](https://github.com/moq-dev/moq/issues) or [PR](https://github.com/moq-dev/moq/pulls) if this spec sucks and you want to fix anything.

--- middle

# Conventions and Definitions
{::boilerplate bcp14-tagged}

The **presenter** of a token is the endpoint that sends it in AUTH, and the **acceptor** is the endpoint that answers.
A **grant** is what the acceptor allows for one token: the namespaces the presenter may publish to it, the namespaces the presenter may subscribe to from it, and an optional expiry.


# Introduction
{{moqt}} authorizes each request on its own.
A publisher learns that a namespace is refused only by sending PUBLISH_NAMESPACE and reading the REQUEST_ERROR, and learns nothing at all from a relay that forwards only what it is asked for.
Nothing on the wire says which role an endpoint will ever be allowed to play ([moq-transport issue 1854](https://github.com/moq-wg/moq-transport/issues/1854)), so a client authorized to publish only `alice` that publishes `bob` waits forever, and neither side logs anything.

This extension answers that question once per credential.
Right after setup each endpoint presents the credential its connection already carried, and learns the grant it earned.
Further tokens are presented on their own request streams, and the endpoint's scope is the union of every grant it holds.

The exchange is the Auth Stream of {{moq-lite}}, carried as {{moqt}} request streams and negotiated with a Setup Option.


# Setup Negotiation

An endpoint that implements this extension sends the following Setup Option ({{moqt}} Section 9.4.1):

~~~
AUTH Setup Option {
Option Key (vi64) = 0x40B60
Option Value (vi64) = 1
}
~~~

The extension is negotiated when both endpoints sent the option with a value of 1.
An endpoint MUST NOT send AUTH on a session that did not negotiate it, and an endpoint that receives AUTH anyway MUST close the session with PROTOCOL_VIOLATION, as it would for any unknown message type.

This extension is defined for the versions of {{moqt}} with the unified SETUP message, draft-17 and later.


# Auth Requests {#requests}

A presenter opens a bidirectional request stream for each token and sends a single AUTH message on it.
The stream lives as long as the token, the way a subscription's request stream outlives its SUBSCRIBE_OK.

Each endpoint SHOULD open one Auth request with an empty token as soon as the extension is negotiated.
An empty token presents the credential the connection already carried: the request URI, a client certificate, an AUTHORIZATION TOKEN Setup Option ({{moqt}} Section 9.4.1), or nothing.
Its grant tells the presenter which role the acceptor will let it play.

The acceptor answers with AUTH_OK carrying the grant, or AUTH_ERROR refusing the token.
It MAY send further AUTH_OK messages to replace the grant, such as with a lowered expiry, and the latest AUTH_OK is the token's grant.
It MAY send AUTH_ERROR after an AUTH_OK to revoke the grant.
After an AUTH_ERROR the acceptor closes its side of the stream.

The presenter withdraws a token by closing or resetting its side of the stream, and the acceptor then closes its own.
An acceptor that closes its side without an AUTH_ERROR ends the grant without a reason.

## Scope {#scope}

An endpoint's scope is the union of the grants of its open Auth requests.
A request that ends, by withdrawal, revocation, or a stream error, removes its grant from the union.
An empty union grants nothing, and the session stays open for another token.

A grant tells the presenter what the acceptor will allow; it does not replace the acceptor's own enforcement, which MUST still refuse what the grant does not cover.

When the union shrinks, the presenter SHOULD withdraw its advertisements and cancel its subscriptions that the union no longer covers, keeping the session and everything still covered.

A presenter that would advertise a namespace outside the union, once the tokens it presented at setup are answered, SHOULD close the session with UNAUTHORIZED instead of sending PUBLISH_NAMESPACE, since a subscription for it will never come.
A publication that loses coverage because the union shrank is withdrawn, which is no reason to close the session.

## Prefixes {#prefixes}

A grant names Track Namespace prefixes, encoded as the Track Namespace Prefix of SUBSCRIBE_NAMESPACE ({{moqt}}) and matched the same way: a prefix covers every namespace whose leading fields equal it.
A prefix with no fields covers every namespace, and a count of zero grants none.

An acceptor whose grant is not a union of prefixes, such as one exact namespace without its descendants, MUST NOT widen it to a prefix.
It sends AUTH_ERROR with NOT_SUPPORTED instead: as the first reply this refuses the token, and after an AUTH_OK it revokes the earlier grant.
The same holds for a grant too large to encode as one AUTH_OK: an acceptor MUST NOT trim it, and nothing of the AUTH_OK is written.
Other tokens on the session are unaffected.


# Messages

Each message is a {{moqt}} Control Message, framed by its Message Type and a 16-bit Message Length.

## AUTH {#auth}

AUTH is the first message on an Auth request stream, sent by the presenter.

~~~
AUTH Message {
Type (vi64) = 0x40B61,
Length (16),
Request ID (vi64),
Token Length (vi64),
Token (..),
}
~~~

**Request ID**:
The request's identifier, allocated like that of any other request ({{moqt}}).

**Token**:
The credential to verify, opaque to this extension.
An empty token presents the credential the connection already carried ({{requests}}).

## AUTH_OK {#auth-ok}

AUTH_OK grants the token, replacing any earlier grant on the same stream.

~~~
AUTH_OK Message {
Type (vi64) = 0x40B62,
Length (16),
Publish Count (vi64),
Publish Prefix (Track Namespace) ...,
Subscribe Count (vi64),
Subscribe Prefix (Track Namespace) ...,
Expires (vi64),
}
~~~

**Publish Prefix**:
A namespace prefix the presenter may advertise and serve ({{prefixes}}).

**Subscribe Prefix**:
A namespace prefix the presenter may subscribe to and discover.

**Expires**:
The number of milliseconds until the grant lapses, or 0 for never.
The acceptor revokes a lapsed grant with AUTH_ERROR, and the presenter uses Expires to present a replacement token in time.

## AUTH_ERROR {#auth-error}

AUTH_ERROR refuses the token, or revokes it after an AUTH_OK.

~~~
AUTH_ERROR Message {
Type (vi64) = 0x40B63,
Length (16),
Error Code (vi64),
Reason Phrase Length (vi64),
Reason Phrase (..),
}
~~~

**Error Code**:
A code from the REQUEST_ERROR registry ({{moqt}}): UNAUTHORIZED, MALFORMED_AUTH_TOKEN, EXPIRED_AUTH_TOKEN, or NOT_SUPPORTED ({{prefixes}}).

**Reason Phrase**:
A human-readable reason, at most 8,192 bytes.


# Security Considerations

A token presented in AUTH is as sensitive as one in the request URI or an AUTHORIZATION TOKEN, and relies on the same transport confidentiality.

A grant is advice to the presenter, never authority for the acceptor: the acceptor MUST enforce its own authorization on every request whatever it granted.
A presenter that trusts a grant too far only fails on the acceptor's REQUEST_ERROR, as it would without this extension.

Auth requests count against the peer's request and stream limits like any other request.


# IANA Considerations

This document requests the following registrations.
High, distinctive values are requested to avoid the low ranges reserved by {{moqt}} and to minimize collisions with provisional registrations by other extensions.

## MOQT Setup Options

This document requests one registration in the "MOQT Setup Options" registry ({{moqt}}), whose policy is Specification Required.

| Value | Name | Reference |
|:--------|:-----|:--------------|
| 0x40B60 | AUTH | This Document |

AUTH is even, so its value is a bare varint.

## MOQT Message Types

This document requests three registrations in the "MOQT Message Types" registry ({{moqt}}).

| Value | Name | Reference |
|:--------|:-----------|:--------------|
| 0x40B61 | AUTH | This Document |
| 0x40B62 | AUTH_OK | This Document |
| 0x40B63 | AUTH_ERROR | This Document |


--- back

# Acknowledgments
{:numbered="false"}

This document was drafted with the assistance of Claude, an AI assistant by Anthropic.
Loading
Loading