| Internet-Draft | mocha-identity | July 2026 |
| Jennings & Nandakumar | Expires 7 January 2027 | [Page] |
This document specifies identity, authentication, authorization, and federation for MOCHA (MoQ Open Communication & Hosting Architecture). It defines how users authenticate with Identity Providers (IdPs), how authorization tokens (C4M and Privacy Pass) are issued and validated at relays, how permissions map to MOQT namespaces, and how users from one Provider obtain access to resources on a federated Provider.¶
This note is to be removed before publishing as an RFC.¶
Status information for this document may be found at https://datatracker.ietf.org/doc/draft-jennings-moq-mocha-identity/.¶
Discussion of this document takes place on the Media Over QUIC Working Group mailing list (mailto:moq@ietf.org), which is archived at https://mailarchive.ietf.org/arch/browse/moq/. Subscribe at https://www.ietf.org/mailman/listinfo/moq/.¶
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 7 January 2027.¶
Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
MOCHA requires participants to prove their identity and obtain authorization before publishing or subscribing to tracks on MOQT relays. This document specifies the identity and authorization architecture, covering:¶
See BCP 14 [RFC2119] [RFC8174].¶
This document uses terminology defined in [MOCHA-ARCH], including Provider, Organization, Team, Channel, Device, and DevID.¶
The following identifiers are defined in this document:¶
A hashed client identifier derived via HMAC. Used in namespace tuples and track names instead of the raw clientID. See Section 10.¶
A hashed device identifier derived via HMAC. Used in track names instead of the raw devID. See Section 10.¶
The following additional terms are used:¶
A service that authenticates users and asserts their identity. Examples include OIDC-compliant providers, enterprise SSO, and self-hosted identity services. The IdP answers "who is this user?"¶
The MOCHA component that validates identity assertions from IdPs, determines user permissions, and issues access tokens (C4M) for relay access.¶
Access Tokens: A C4M (CAT for MOQT) CWT (CBOR Web Token) that encodes a
user's identity and MOQT namespace permissions. Defined in
[C4M]. When used in Mocha, the token includes a DPoP confirmation
claim (cnf) binding it to the client's key.¶
ID Token: : An OIDC (OpenID Connect) token that provides the authenticated identity of the user.¶
A mechanism that binds a C4M token to the client's asymmetric key pair. The client generates a fresh DPoP proof for each MOQT action, preventing token theft and replay. See Section 5.2.¶
The MOQT parameter that carries authentication credentials. It can appear in CLIENT_SETUP, SUBSCRIBE, SUBSCRIBE_NAMESPACE, PUBLISH, PUBLISH_NAMESPACE, and FETCH messages.¶
Validates access tokens and enforces access control for MOQT operations. Relays do not authenticate users directly; they trust tokens signed by known issuers.¶
The client does a normal OpenID SSO login flow to authenticate to the Provider Token Service to get an ID Token and an Access Token with DPoP.¶
For each Org at the Provider, the Provider keeps tracks of: * all of the users in the Org * for each users in its Org, all of their devices * all the teams in its Org, * for each team, all of users (in the Org or other Providers/Orgs) and their roles, * for each team, all of its channels, * for each of the channels, its users (in this Org or other Providers/Orgs) and their roles, * for each of the channels, all of the meetings¶
TODO: Add support for federation¶
The Provider Token Service takes an ID Token and generates Access Token(s) with the required scopes for Relays for all the Mocha services. The Provider Token Service coordiates with the CDNs providing the Relays so the relays can validate the Access Tokens.¶
The exact scopes needed for Access Tokens are defined in the specifications that define the Mocha service.¶
Clients use the Access Tokens when sending requests to the Relays.¶
A Client that is acting as User on a different provider can also ask for Access Tokens for this resources in this Provider. This allows federated actions where a Client on Provider A can participated in meetings or channels that are owned by an Org on Provier B.¶
MOCHA Identity Architecture
+----------+ +----------+
| IdP | (OIDC, Enterprise SSO, etc.) | PP-Issuer|
+----+-----+ +----+-----+
| |
| ID Token blind sign |
v v
+----+-----+ (2) AUTHORIZATION param +------+------+
| Client | -----------------------------> | Relay |
| (Device) | (C4M + DPoP ) | (Validates) |
+----+-----+ +------+------+
| |
| (1) authenticate + request token |
v |
+----+-----------------------------------------+ |
| Token Service | |
| - Validates IdP assertions | |
| - Checks membership/permissions | |
| - Mints C4M tokens with DPoP binding | |
| - Proxies PP blind token requests | |
+----------------------------------------------+ |
|
(3) SUBSCRIBE / PUBLISH granted |
<------------------------------------------+
Before obtaining authorization tokens, a user must authenticate with an Identity Provider. MOCHA supports pluggable IdPs. The Token Service validates the IdP assertion and establishes a session.¶
Client IdP (OIDC) Token Service
| | |
| (1) OAuth/OIDC flow | |
| (browser redirect or | |
| native sign-in) | |
|------------------------->| |
| | |
| (2) ID Token (JWT) | |
|<-------------------------| |
| | |
| (3) POST /auth/oidc | |
| Authorization: Bearer <ID Token> |
|-------------------------------------------------->|
| | |
| | (4) Verify signature |
| | via JWKS fetch |
| |<-----------------------|
| |----------------------->|
| | |
| | (5) Validate claims: |
| | iss, aud, exp, |
| | email_verified |
| | |
| (6) 200 OK |
| { user_id, session_token } |
|<--------------------------------------------------|
| | |
The Token Service MUST validate the ID Token per [OIDC] Section 3.1.3.7 (issuer, audience, expiry, signature via JWKS).¶
For deployments allowing pseudonymous access:¶
Client Token Service
| |
| POST /auth/guest |
| { "display_name": "Alice" } |
|------------------------------------->|
| |
| 200 OK |
| { "user_id": "guest-7a3f", |
| "session_token": "s_..." } |
|<-------------------------------------|
| |
Guest users receive a server-assigned pseudonymous identity. Guest sessions MAY have reduced permissions compared to authenticated users.¶
C4M (CAT for MOQT) [C4M] is the primary authorization mechanism for identified access. MOCHA deployments MUST use DPoP (Demonstrating Proof of Possession) to bind C4M tokens to the client's key pair, preventing token theft and replay.¶
Token format and CWT claims (Section 2), DPoP claims and proof generation (Section 3.1), and DPoP proof process and token binding flow (Section 3.2) from [C4M] MUST be used for MOCHA authorization.¶
After authentication, the client requests a C4M token scoped to
a specific channel and role. The client provides its public key
for DPoP binding. The Token Service validates the session,
checks membership, determines role permissions, and mints a
C4M token with a cnf claim binding it to the client's key.¶
The complete HTTP issuance flow is specified in Section 9.1.¶
The Token Service maps the user's role and channel membership
to moq_scopes within the C4M token. The scopes use the MOCHA
namespace hierarchy:¶
moq_scopes: {
publish: ["mocha/<provider>/<org>/<team>/<channel>/participant/*"],
subscribe: ["mocha/<provider>/<org>/<team>/<channel>/*"]
}
¶
Scope enforcement follows the positional namespace matching
rules defined in [C4M], Section 2.1. The relay evaluates
each namespace field against the corresponding match object
in the token's moqt claim. Actions not explicitly permitted
by the token are denied by default.¶
For example, a MOCHA participant token might encode scopes that permit publishing under their device prefix within a channel, while allowing subscription to all tracks in that channel:¶
Token scope (publish): mocha/provider.example/acme/eng/general/
participant/<device-id>/...
Token scope (subscribe): mocha/provider.example/acme/eng/general/...
PUBLISH request: mocha/provider.example/acme/eng/general/
participant/device-a3f2/messages/live
Result: PERMITTED (matches publish scope)
SUBSCRIBE request: mocha/provider.example/acme/eng/general/
moderator/device-b1c4/messages/live
Result: PERMITTED (matches subscribe scope)
¶
Privacy Pass [PrivacyPassAuth] provides cryptographic unlinkability: the relay can verify a token was legitimately issued without correlating it to the identity that requested it. This enables anonymous participation in channels. Illustrative call flows are provided in Appendix A.¶
For scoped access, Partially Blind RSA tokens carry public metadata encoding the permitted MOCHA namespaces without breaking unlinkability. The relay uses these extensions for scope enforcement identical to C4M tokens, but cannot determine which user holds the token.¶
Token types, issuance protocol, attester authentication, and token wire format are defined in [PrivacyPassAuth]. The Token Service acts as Attester ([PrivacyPassAuth], Section 3) and authenticates to the PP-Issuer using HTTP Message Signatures [RFC9421].¶
In the MOCHA context, the Token Service:¶
Validates the client's session and channel membership (as in the C4M flow).¶
Determines the namespace scope to encode in the token's public metadata (for PBRS tokens).¶
Attests to the PP-Issuer that the client is authorized for the requested scope.¶
The client presents the resulting token in the AUTHORIZATION parameter of any MOQT control message (CLIENT_SETUP, SUBSCRIBE, PUBLISH, etc.).¶
MOCHA deployments MAY use the Privacy Pass reverse flow [PP-REVERSE] where the relay acts as both attester and issuer. The client bootstraps with a publicly verifiable token from an external issuer, and the relay responds with privately verifiable tokens for subsequent operations.¶
The reverse flow enables continuous unlinkable authorization without requiring an external issuer after the initial bootstrap. Token replenishment is piggybacked on normal MOQT control message exchanges using batched tokens [PP-BATCH]. The bootstrap, continuous authorization, and batch token encoding are defined in [PrivacyPassAuth] (Sections 4-5) and [PP-REVERSE] (Section 4).¶
Roles (Owner, Moderator, Participant, Observer) are defined in [MOCHA-ARCH] and assigned at the Organization, Team, and Channel levels. The permissions associated with each role (publish, subscribe, moderation, management) are application policy specified in [MOCHA-ARCH].¶
This document defines how the Token Service translates a user's role into C4M token scopes. When a user requests a token, the Token Service determines their role for the target channel and encodes the corresponding namespace permissions:¶
User: alice@provider.example
Team: acme/eng
Channel: general
Role: participant
Resulting C4M scopes:
publish: ["mocha/provider.example/acme/eng/general/participant/*"]
subscribe: ["mocha/provider.example/acme/eng/general/*"]
User: bob@provider.example
Role: moderator
Resulting C4M scopes:
publish: ["mocha/provider.example/acme/eng/general/moderator/*",
"mocha/provider.example/acme/eng/general/moderation/*"]
subscribe: ["mocha/provider.example/acme/eng/general/*"]
¶
The scope patterns follow the MOCHA namespace hierarchy and are enforced by the relay per [C4M], Section 2.1. The same scopes apply when using Privacy Pass with PBRS tokens — the namespace permissions are encoded in the token's public metadata extensions (see [PrivacyPassAuth]) and enforced identically by the relay.¶
Federation allows a user authenticated with one Provider to participate in Teams and Channels hosted by a different Provider. The home Provider vouches for the user's identity via a signed assertion, and the remote Provider issues a locally-valid C4M token after verifying trust and applying its federation policies.¶
+------------------+ +------------------+ | Provider A | | Provider B | | (home) | | (remote) | | | Federation | | | Token Service A |<------------------>| Token Service B | | Relay A | Trust | Relay B | | Users: Alice | | Users: Bob | +------------------+ +------------------+ Alice (home: A) wants to join a Channel on Provider B.
Providers establish federation trust via mutual discovery and key exchange:¶
Provider A Provider B
| |
| (1) GET /.well-known/mocha-federation |
|----------------------------------------->|
| |
| (2) { realm, federation_relays, |
| signing_key, policies } |
|<-----------------------------------------|
| |
| (3) Verify signing_key via DNSSEC/TLS |
| |
| (4) Store Provider B's public key |
| in trusted federation peers |
| |
| (5) Provider B does same for A |
|<---------------------------------------->|
| |
The well-known document:¶
GET https://provider-b.example/.well-known/mocha-federation
{
"realm": "provider-b.example",
"federation_relays": ["moqt://relay.provider-b.example:443"],
"signing_key": "<Ed25519 public key, base64>",
"token_exchange_endpoint": "https://auth.provider-b.example/federation/exchange",
"policies": {
"allow_inbound": true,
"allowed_providers": ["*"],
"blocked_providers": []
}
}
¶
When Alice (Provider A) wants to access a Channel on Provider B, a token exchange occurs:¶
Alice Token Service A Token Service B Relay B
| | | |
| (1) Request | | |
| federated | | |
| access to | | |
| provider-b/ | | |
| org/team/ch | | |
|--------------->| | |
| | | |
| | (2) Create signed | |
| | federation assertion| |
| | (Ed25519): | |
| | { sub, home_realm, | |
| | target, roles, | |
| | exp } | |
| | | |
| | (3) POST /federation/exchange |
| | { assertion, requested_scope } |
| |--------------------->| |
| | | |
| | (4) Verify assertion signature |
| | against Provider A's |
| | known public key |
| | | |
| | (5) Check federation policies: |
| | - Provider A allowed? |
| | - Target channel permits |
| | remote users? |
| | - Role mapping valid? |
| | | |
| | (6) Mint C4M token with |
| | Provider B's signing key |
| | | |
| | (7) { token: <C4M>, expires_at } |
| |<--------------------| |
| | | |
| (8) Federated | | |
| C4M token | | |
|<---------------| | |
| | | |
| (9) CLIENT_SETUP { AUTHORIZATION: <C4M from B> } |
|-------------------------------------------------------->|
| | | |
| | | (10) Validate |
| | | with B's key |
| | | |
| (11) SERVER_SETUP |
|<--------------------------------------------------------|
| | | |
The home Provider creates an Ed25519-signed assertion containing the user's identity (issuer, subject), the target Provider and namespace, the user's home roles, a validity window (issued-at and expiry), and a fresh nonce to prevent replay. The assertion is scoped to the remote Provider via an audience claim.¶
The remote Provider validates the signature against the home Provider's known public key, checks the assertion has not expired, and maps the home roles to local permissions according to its federation policy. The remote Provider MAY downgrade roles (e.g., a home "moderator" becomes a remote "participant").¶
The Token Service is the MOCHA component responsible for minting, refreshing, and revoking C4M access tokens.¶
The Token Service interacts with other components via two mechanisms:¶
Clients call the Token Service HTTP endpoint to mint initial C4M tokens (before a MOQT session exists). See Section 9.1.¶
The Token Service publishes on MOQT tracks that relays and clients subscribe to:¶
Per-client refresh track: delivers rotated tokens to each client. See Section 9.2.¶
Revocation track: notifies relays and clients of revoked tokens. See Section 9.3.¶
Before establishing a MOQT session, a client obtains a C4M token from the Token Service via its HTTP API. The client presents its session credentials (obtained after IdP authentication per Section 4) along with its DPoP public key:¶
Client Token Service
| |
| POST /token |
| Authorization: Bearer <session_token>|
| { |
| "org_id": "acme", |
| "team_id": "engineering", |
| "channel_id": "general", |
| "role": "participant", |
| "dpop_jwk": { <client public key> }|
| } |
|-------------------------------------->|
| |
| (1) Validate session |
| (2) Check membership and role |
| (3) Mint C4M token with DPoP binding |
| |
| 200 OK |
| { "token": "<base64 CWT>", |
| "expires_at": 1749903600 } |
|<--------------------------------------|
| |
The Token Service maps the user's role and channel membership
to moq_scopes within the C4M token per Section 7.¶
C4M tokens are intentionally short-lived. To maintain continuous access without requiring the client to re-authenticate via the IdP, MOCHA provides in-session token refresh over MOQT.¶
Once a MOQT session is active, the client subscribes to a per-user token refresh track. The Token Service proactively publishes a new token before the current one expires:¶
Client Relay Token Service
| | |
| SUBSCRIBE | |
| mocha/<provider>/ | |
| tokens/<user_id> | |
|-------------------->| |
| |--------------------->|
| | |
| | (token nearing |
| | expiry) |
| | |
| | OBJECT: new token |
| |<---------------------|
| new token | |
|<--------------------| |
| | |
The Token Service SHOULD publish the refreshed token at approximately 75% of the current token's lifetime, providing an overlap window where both the old and new tokens are valid.¶
For immediate access removal (e.g., user banned, role changed, device compromised), the Token Service publishes revocation objects to a dedicated revocation track. Both relays and clients subscribe to this track.¶
Namespace: mocha/<provider>/tokens Track: revocations¶
Each revocation object carries the SHA-256 hash of the revoked token, enabling relays and clients to identify the specific token without exposing its contents:¶
{
"token_hash": "<SHA-256 hash of the revoked C4M token>",
"revoked_at": 1749901500,
"reason": "role_change",
"affected_scopes": [
"mocha/provider.example/acme/eng/private/*"
]
}
¶
SHA-256 hash of the C4M token being revoked, hex-encoded. Relays and clients match this against hashes of tokens they hold or have validated.¶
Revocation time in seconds since Unix epoch.¶
Human-readable reason for the revocation (e.g., "role_change", "ban", "device_compromised").¶
List of namespace scope patterns affected by this revocation, for informational purposes.¶
Relays MUST subscribe to the revocation track of each Token Service whose tokens they accept. Upon receiving a revocation object, the relay:¶
Computes the SHA-256 hash of each active token it has validated for the affected session(s).¶
If a match is found, immediately terminates any SUBSCRIBE or PUBLISH operations authorized by that token.¶
Caches the token_hash for the remainder of the token's original validity period to reject any late-arriving requests using the revoked token.¶
Clients SHOULD also subscribe to the revocation track. Upon receiving a revocation matching a token they hold, clients MUST stop using that token and request a new one via HTTP (Section 9.1) or await a refresh on their token track.¶
Token Service Relay Client
| | |
| PUBLISH revocation | |
| (token_hash=0xab..) | |
|-------------------->| |
| | |
| | (match found: |
| | terminate session)|
| | |
| | OBJECT: revocation|
| |------------------>|
| | |
| | | (stop using
| | | revoked token,
| | | request new one)
| | |
Both the HClientID and HDevID used in namespace tuples and track names are derived using HMAC with a shared secret K between the client and the provider. The clientID is the identifier for the client participant obtained out of band, and "||" denotes concatenation.¶
The shared secret K is established during the authentication flow (Section 4) and is unique per client-provider pair.¶
The HClientID is computed as:¶
HClientID = HMAC(K, <namespace-tuple-without-HClientID> ||
clientID)
¶
The HDevID is computed as:¶
HDevID = HMAC(K, <namespace-tuple-with-HClientID> || devID)¶
where devID is the device identifier used as the device identity in the MoQ track. The devID MAY be the hash of the client's public key used for DPoP binding (Section 5.2), providing a stable per-device identifier tied to the cryptographic material.¶
These derived identifiers ensure that:¶
The actual clientID and devID are not exposed on the wire¶
Different namespaces produce different HClientID/HDevID values for the same client, preventing cross-namespace correlation by relays¶
Only the client and the provider (who share K) can verify the mapping between the raw identifiers and their hashed forms¶
TODO¶
This document makes no requests of IANA.¶
This appendix illustrates the Privacy Pass flows for MOCHA. The normative protocol is defined in [PrivacyPassAuth] and [PP-REVERSE].¶
The client authenticates with the IdP, then obtains a Privacy Pass token via the Token Service (Attester) and external PP-Issuer, and finally presents it to the relay.¶
Client IdP Token Service PP-Issuer Relay
| | (Attester) | |
| | | | |
| (1) Authenticate | | |
|------------->| | | |
| | | | |
| (2) ID Token | | | |
|<-------------| | | |
| | | | |
| (3) POST /auth/oidc | | |
| { ID Token } | | |
|----------------------------->| | |
| | | | |
| (4) session_token | | |
|<-----------------------------| | |
| | | | |
| (5) POST /auth/privacypass/challenge | |
| { room_id, token_type } | | |
|----------------------------->| | |
| | | | |
| | | (6) Fetch issuer | |
| | | public key | |
| | |----------------->| |
| | |<-----------------| |
| | | | |
| (7) TokenChallenge + | | |
| issuer_pub_key | | |
|<-----------------------------| | |
| | | | |
| (8) Generate nonce, | | |
| blind token_input | | |
| | | | |
| (9) POST /auth/privacypass/token-request | |
| { blinded_element } | | |
|----------------------------->| | |
| | | | |
| | | (10) Forward | |
| | | + HTTP sig | |
| | |----------------->| |
| | | | |
| | | (11) Verify | |
| | | attester sig, | |
| | | sign token | |
| | | | |
| | | (12) blind_sig | |
| | |<-----------------| |
| | | | |
| (13) blind_signature | | |
|<-----------------------------| | |
| | | | |
| (14) Unblind, assemble | | |
| final token | | |
| | | | |
| (15) CLIENT_SETUP { AUTHORIZATION: token } |
|-------------------------------------------------------------->|
| | | | |
| | | | (16) Verify |
| | | | with issuer |
| | | | public key |
| | | | |
| (17) SERVER_SETUP (identity unknown to relay) |
|<--------------------------------------------------------------|
| | | | |
The reverse flow has three phases: bootstrap, session establishment, and continuous authorization with token replenishment.¶
The client connects without credentials. The relay challenges the client, who obtains a publicly verifiable token from an external issuer.¶
Client PP-Issuer (external) Relay
| | |
| (1) CLIENT_SETUP (no auth) |
|------------------------------------------------>|
| | |
| (2) UNAUTHORIZED { TokenChallenge } |
|<------------------------------------------------|
| | |
| (3) Blind token | |
| request | |
|--------------------->| |
| | |
| (4) blind_signature | |
|<---------------------| |
| | |
| (5) Unblind, assemble publicly |
| verifiable token (type 0x0002) |
| | |
The client presents the bootstrap token and requests a batch of privately verifiable tokens from the relay (now acting as issuer).¶
Client Relay (Issuer)
| |
| (1) CLIENT_SETUP { |
| AUTHORIZATION: Token (0x0002), |
| GenericBatchTokenRequest |
| } |
|---------------------------------------->|
| |
| (2) Validate bootstrap token |
| (3) Issue privately verifiable |
| tokens (type 0x0001/0x0005) |
| |
| (4) SERVER_SETUP { |
| GenericBatchTokenResponse |
| } |
|<----------------------------------------|
| |