OAuth Bridge Server
This document defines the HTTP and deployment contract for the OAuth bridge used by CCDP. The bridge publishes platform configuration and serves the OAuth callback document.
CCDP owns the browser flow. The common and platform ceremony rules own authorization, platform-return, token-exchange, and proof semantics. This document owns only the Bridge’s public HTTP and deployment contract.
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.
Boundary
Section titled “Boundary”The OAuth bridge owns:
- OAuth application registrations and their public client configuration;
- the public ceremony configuration; and
- callback ingress, response policy, and deployment inputs.
For Callback, the bridge is a configuration-inserting, cached proxy to the CCDP Distribution. It neither implements the document nor builds its browser code. It serves no other CCDP resources. The browser-exchange profiles covered here require no Bridge ceremony state, token exchange, or notarization.
Bridge-assisted OAuth is a platform-profile choice, not prohibited by CCDP. Another platform or later ceremony version can define a Bridge service together with its HTTP contract, credential custody, and trust/evidence requirements. The route surface below requires no such service for the browser-exchange profiles; it does not preclude one where a profile requires it.
The bridge and Distribution may be cross-site and independently operated; both are code-supply-chain trust boundaries. The Distribution requires no Bridge registration and receives no application allowlists.
Deployment configuration
Section titled “Deployment configuration”- REQ-BRIDGE-01 (upholds SP-CCDP-01): The Bridge MUST derive its effective origin allowlist and Callback inputs from the deployment configuration below, not incoming requests.
One bridge deployment has these inputs. Every origin follows the
CCDP origin policy, including HTTP on exact
localhost and 127.0.0.1 hosts:
| Input | Contract |
|---|---|
allowedAppOrigins | Nonempty, duplicate-free set of canonical application origins, origin patterns, and * |
| CCDP origin | One canonical origin selected by the operator; defaults to https://lib.id when omitted |
| Platform profiles | For each enabled platform, one public OAuth client ID and a public clientCredential exactly when the platform’s ceremony sends one; no version list |
| Callback inputs | One unversioned list [allowedOrigins, ccdpOrigin] derived from the values above, following the artifact contract; no separate input configuration or CCDP version list |
Every enabled platform’s OAuth registration uses /auth/callback on the
externally reachable Bridge origin as its redirect_uri. The path is fixed,
not a deployment option. The operator registers that external URL with the
platform; the Bridge process requires neither its own public-origin setting nor
a redirect URI. Application-side redirect construction is defined below.
allowedAppOrigins has no protocol maximum. Member syntax, validation, and
matching follow popup transport REQ-POPUP-ALLOW-01 and REQ-POPUP-ALLOW-02
unchanged. The bridge narrows no member on width; admission breadth belongs
to its operator.
The bridge admits the origins its members admit on the browser side, neither
more nor fewer: a member and an origin carry one meaning across the two.
ccdpOrigin is navigation and artifact-retrieval configuration, not a Callback
frame or script source. It follows the canonical-origin rules without an
additional CSP-source alphabet restriction.
A duplicate or invalid member is a deployment error rather than something
the bridge normalizes. After resolving the default or configured ccdpOrigin,
the bridge derives one effective set:
allowedOrigins = allowedAppOrigins ∪ {ccdpOrigin}.
Adding an already-listed CCDP origin does not duplicate it. The union and its
duplicate detection compare member spellings literally: a pattern covering the
CCDP origin does not absorb it, that origin stays an exact member of the
effective set, and members whose admitted origins overlap are not duplicates.
When ccdpOrigin is omitted, this adds
https://lib.id; when overridden, only the replacement is added automatically.
https://lib.id then remains allowed only if explicitly listed.
Automatic inclusion lets Applications hosted at the selected CCDP origin use
the Bridge without repeating that origin in configuration. The Distribution is
already a trusted browser-code supplier under ASM-CCDP-01; this rule admits
its exact origin, not unrelated origins.
The effective set governs explicit Origin admission for configuration GET
and all callback connection authentication. Configuration GET also admits the
same-origin browser case defined below. The set is embedded into Callback, not
separately configured, and never inferred from a request’s Origin, Referer,
query, fragment, or body.
The CCDP origin is likewise deployment data. It is returned to the
application in public configuration and embedded into the callback document so
Callback can navigate the popup to Prover. The bridge also resolves the fixed
Callback artifact path against it; no separate Callback artifact URL is
configured. Omitting it selects the canonical https://lib.id
Distribution.
An all-HTTP local setup can use Application http://localhost:3000, Bridge
http://localhost:3001, and CCDP http://localhost:8787. The Bridge configures
the Application in allowedAppOrigins, adds the CCDP origin to allowedOrigins,
serves Callback at http://localhost:3001/auth/callback, and retrieves
http://localhost:8787/ccdp/callback.html without requiring a local certificate.
Callback receives that same HTTP allowlist and CCDP origin; the client,
Callback, Prover, and their popup connections must not reject them solely for
using HTTP. Notary selection remains with the Application, not this config.
Platform acceptance of the registered redirect URI is a separate prerequisite.
The Bridge injects the same input list regardless of CCDP version. New versions
with compatible inputs work on artifact refresh without a Bridge rebuild or
configuration change. Callback owns browser version selection; the Bridge
neither enumerates versions nor reads input declarations from the artifact.
The artifact contract owns when
an input change takes an input-contract version and when a deployment instead
coordinates by order. Admitting origin patterns and * coordinates by order: a
bridge must not be configured with either until the Callback in its selected
Distribution reads that member kind.
The public profile entries match the OAuth registrations used by Callback. The bridge enumerates no versions; the Application resolves the pairs that run under Public configuration. Selecting a shared Distribution requires no reciprocal configuration.
Route surface
Section titled “Route surface”- REQ-BRIDGE-02 (upholds SP-CCDP-01): The Bridge MUST enforce the route, method, and origin-admission rules below before performing route work.
For the profiles covered here, the bridge’s ceremony route surface is:
| Method | Route | Availability | Purpose | Origin enforcement |
|---|---|---|---|---|
GET | /api/v1/ceremony/config | always | public platform and CCDP configuration | Origin admitted by allowedOrigins; absent Origin accepted only by the same-origin rule below |
GET | /auth/callback | always | complete OAuth Callback document | none at HTTP ingress; callback authenticates its popup connection after clearing its input |
Top-level navigation may omit Origin, and an OAuth-platform callback may
identify the platform rather than the application. Referer is never an
authority input. The callback document is therefore public and
request-invariant; its browser protocol authenticates the application after it
loads.
For these profiles, no separate Callback script, prover, proving-asset,
preparation, continuation, polling, status, result, cancellation, token-exchange,
notarization, browser TLS bridge, or proof-recovery route exists on the OAuth
bridge. Unsupported methods fail without route work, except that the Bridge may
answer CORS preflight OPTIONS requests on the configuration route.
Operational endpoints such as health and metrics, and CORS preflight handling,
are outside this ceremony contract and perform no ceremony route work.
Except for the OAuth-platform-mandated callback query, bridge routes accept
no query or request body.
The v1 in /api/v1/ceremony/... versions the bridge’s JSON API.
/auth/callback is a browser protocol document; its embedded code
selects CCDPVersion from OAuth state.
PlatformCeremonyVersion independently versions one platform ceremony. There
is no request-time version negotiation.
Public configuration
Section titled “Public configuration”- REQ-BRIDGE-03: The Bridge and Application MUST follow the configuration record, response policy, and version resolution rules below. Necessity: each ceremony must freeze one mutually supported profile and Distribution.
GET /api/v1/ceremony/config returns application/json with this exact record:
interface PlatformConfig { clientId: string clientCredential?: string}
interface CeremonyConfig { ccdpOrigin: string platforms: Readonly<Record<string, PlatformConfig>>}The response rules are:
ccdpOriginis the configured canonical origin under the origin policy, with no credentials, path, query, or fragment. The Application accepts the localhost HTTP exception for this field and the Bridge origin it uses.- Each platform entry has one public client ID; the record carries no version list.
clientCredentialis present exactly when the platform’s ceremony sends one, as a nonempty printable ASCII string without whitespace. It is an intentionally public OAuth application credential, not a user access token. GitHub requires it and uses it asclient_secret. A missing, unexpected, null, empty, or wrongly typed credential is invalid. The selected platform owns any additional constraints.- Unknown fields and malformed URLs are invalid. A platform absent from the client’s closed local catalog is ignored; known entries remain exact-validated before use.
- The record contains no redirect URI, confidential credential, user token, allowlist, artifact URL, CSP source, notary setting, platform display metadata, or application-specific value.
When present, Origin must be admitted by allowedOrigins. Admission tests
canonicality before membership of any kind, so an Origin containing * fails
and a pattern member’s own spelling never admits itself. A successful
cross-origin response sets the exact requesting origin in
Access-Control-Allow-Origin, never a pattern member, permits no credentials,
and never uses *.
A same-origin browser GET may omit Origin: accept that case only when
Sec-Fetch-Site is exactly same-origin. This browser-supplied relationship
requires neither knowledge of the Bridge’s public origin nor its membership in
allowedOrigins; the response needs no CORS header. Missing or other
Sec-Fetch-Site values reject this absent-Origin case. Do not infer admission from
Referer, Host, or forwarding headers; an explicit invalid, null, or
unadmitted Origin always fails, even with Sec-Fetch-Site: same-origin.
This exception admits only the public, read-only configuration response; it
does not change Callback’s connection allowlist.
Both cases use Content-Type: application/json, Cache-Control: no-store,
Vary: Origin, Sec-Fetch-Site, and X-Content-Type-Options: nosniff. Rejected
requests return no configuration. These browser admission checks do not make
the public record a secret from non-browser clients. Request values do not
alter the response record.
The Application fetches and validates this record without credentials, then
fetches {ccdpOrigin}/ccdp/versions.json, the Distribution’s
version list, cross-origin without
credentials or redirects, and validates it by that contract. If either read
fails or the list’s ccdpVersions omits the Application’s selected CCDP
version, no ceremony is available under that protocol. The Application enables
a platform only when the record configures it and the list’s platforms map
names a version the Application implements; it enables exactly those versions,
all under the platform’s one
registration. When its caller omits a version, the Application selects the
highest enabled version. It derives redirectUri by appending the fixed
/auth/callback path to its validated canonical OAuth Bridge origin, not from
the response. It freezes the selected client ID,
public token-exchange credential when present, derived redirect URI, CCDP origin,
and mutually supported platform ceremony version in each live ceremony. It
forwards the credential unchanged through CCDP’s ProveIdentity; a configuration
refresh does not replace it in a live ceremony.
CCDP browser resources
never fetch bridge configuration; server-side Callback artifact retrieval is
separate.
Callback document
Section titled “Callback document”- REQ-BRIDGE-04 (upholds SP-CCDP-01): The Bridge MUST retrieve, configure, and serve Callback as specified below without forwarding OAuth ingress data to the Distribution.
GET /auth/callback serves Callback on the bridge origin, without
an HTTP redirect. Its artifact contract
owns the HTML, configuration slot, response policy, browser startup, version
selection, and failure UI. The bridge only:
- retrieves
{ccdpOrigin}/ccdp/callback.html, its only Distribution request, at startup and revalidates it independently of callback requests, rejecting upstream redirects; - sends no callback query, OAuth return, incoming request headers, cookies, or credentials upstream; the configured source never depends on a request;
- validates and inserts its unversioned input list using the artifact contract, then publishes the completed HTML and matching response headers atomically;
- serves the cached result until a valid replacement is ready; a failed refresh retains the last valid result, or returns an inert unavailable response when none exists; and
- suppresses or redacts callback query strings throughout its ingress proxies, access logs, traces, analytics, metrics labels, and errors.
Compatible artifact updates require no bridge rebuild. The bridge neither parses OAuth state nor selects a CCDP implementation, generates browser code, or handles protocol errors. Google fragments never reach this server; platform-mandated callback queries are not forwarded to the Distribution.
Compatibility
Section titled “Compatibility”-
REQ-BRIDGE-05: Withdrawn.
-
REQ-BRIDGE-06: The Bridge MUST preserve the public API version’s semantics when refreshing compatible Callback artifacts. Necessity: a browser artifact refresh must not silently change the public configuration contract.
A breaking JSON request or response changes the bridge API version. CCDP, platform ceremony, prover release, and popup connection versions remain independent; the CCDP version rules do not version the Bridge’s API.
Security Considerations
Section titled “Security Considerations”The Bridge participates in SP-CCDP-01 under ASM-CCDP-01 and ASM-CCDP-02. Its operator supplies OAuth registrations, public application credentials, and Callback deployment policy. Its code and deployment are trusted for correct browser code delivery and configuration, not for ledger acceptance.
CORS and Origin checks protect browser admission, not non-browser
authentication or confidentiality of the configuration. The public
clientCredential carries no user authority and is not an application
authentication boundary. Platform ceremony rules own the consequences of
publishing it and the downstream checks; renaming the field does not hide it.
For these profiles, the Bridge receives no notary address and opens no token or
notary connection.
Callback query redaction remains necessary because OAuth codes, unlike public
application credentials, are transient user-ceremony inputs.
Conformance
Section titled “Conformance”A Bridge implements configuration and Callback for every enabled platform. These tests cover the HTTP boundary, not cryptographic soundness.
- TEST-BRIDGE-01 (exercises REQ-BRIDGE-01):
Default Distribution origin is added once; a replacement is added instead. Invalid/duplicate configured origins fail, and HTTP loopback works at any port.
Configuration and Callback work without a server public-origin or redirect-URI
setting; the Application derives the fixed callback URL from its Bridge origin.
A canonical HTTPS CCDP origin with an IPv6 literal is accepted without a
separate CSP-source alphabet check; the HTTP exception remains limited to
the popup transport’s two permitted hosts.
A
*.handles.linkmember starts the bridge and admitshttps://app.handles.linkandhttps://a.b.c.handles.link. The suffix itself, a host ending in the suffix text without a label boundary, a host the suffix only prefixes, another scheme, and a port variant stay refused. A*.linkmember starts the bridge and admitshttps://handles.link; a*member starts it and admits every canonical application origin. A member carrying*that is neither*nor a well-formed pattern fails at startup, naming the member and its position, instead of falling through to exact-origin validation:https://*.handles.link,*.handles.link:8443,*.handles.link/,*.handles.link.,*..handles.link,*.,*.HANDLES.link,*.*.handles.link,*.127.0.0.1, and*handles.link. A pattern or*configured as the CCDP origin fails at startup. The union remains literal: a pattern covering the CCDP origin leaves that origin an exact member, and members whose admitted origins overlap configure successfully. - TEST-BRIDGE-02 (exercises REQ-BRIDGE-02):
A configuration GET without Origin succeeds with exactly
Sec-Fetch-Site: same-origin, even when the Bridge origin is not allowlisted; missing,same-site,cross-site,none, or malformed Fetch Metadata reject that case. An allowed explicit Origin succeeds even with cross-site metadata; an invalid,null, or unadmitted Origin rejects even with same-origin metadata. An Origin admitted only by a pattern member succeeds, and the response echoes that exact origin rather than the pattern. An Origin spelling a configured pattern rejects. Rejections occur before dependency work; accepted same-origin GET does not admit an otherwise unadmitted Application to Callback. - TEST-BRIDGE-03 (exercises REQ-BRIDGE-03): Exact public config includes GitHub’s public token-exchange credential and has no version list, redirect field, user token, or notary selection. Missing required, unexpected, empty, null, wrongly typed, or whitespace/control-bearing credentials reject; malformed known profiles reject and unknown platforms are ignored. The Application enables a platform only when the record configures it and the version list names a version it implements, uses the platform’s one registration for every enabled version, selects the highest enabled version when none is given, and enables nothing when the list fails to fetch or validate or omits its selected CCDP version. Application freezes the credential and forwards the same value to Prover despite later configuration changes.
- TEST-BRIDGE-04 (exercises REQ-BRIDGE-04): Callback queries/cookies/headers never reach the artifact request; failed refresh preserves the last valid HTML/policy pair, or serves inert unavailability.
- TEST-BRIDGE-05: Withdrawn.
- TEST-BRIDGE-06 (exercises REQ-BRIDGE-06): Compatible bundled Callback updates need no Bridge rebuild; unsupported browser versions fail locally and do not change the Bridge API version.