···11+import { Client, simpleFetchHandler } from '@atcute/client';
22+33+// import lexicon types to augment XRPCProcedures
44+import '../lexicons/index.js';
55+66+import type {
77+ ClientAssertionCredentials,
88+ ClientAssertionFetcher,
99+ FetchClientAssertionParams,
1010+} from './types.js';
1111+1212+const CLIENT_ASSERTION_TYPE_JWT_BEARER = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer';
1313+1414+/**
1515+ * options for creating a CAB fetcher
1616+ */
1717+export interface CreateCABFetcherOptions {
1818+ /** URL of CAB backend (defaults to location.origin) */
1919+ service?: string | URL;
2020+ /** optional custom fetch implementation */
2121+ fetch?: typeof globalThis.fetch;
2222+}
2323+2424+/**
2525+ * creates a client assertion fetcher that communicates with a CAB backend.
2626+ *
2727+ * the fetcher handles DPoP proof creation and nonce retry automatically.
2828+ *
2929+ * @param options fetcher configuration
3030+ * @returns client assertion fetcher for use with oauth-browser-client
3131+ */
3232+export const createCABFetcher = (options: CreateCABFetcherOptions = {}): ClientAssertionFetcher => {
3333+ const serviceUrl = new URL(options.service ?? location.origin);
3434+3535+ const client = new Client({
3636+ handler: simpleFetchHandler({
3737+ service: serviceUrl,
3838+ fetch: options.fetch,
3939+ }),
4040+ });
4141+4242+ return async (params: FetchClientAssertionParams): Promise<ClientAssertionCredentials> => {
4343+ const { aud, createDpopProof } = params;
4444+4545+ // build the endpoint URL for DPoP proof (htu is origin + pathname only)
4646+ const htu = serviceUrl.origin + '/xrpc/dev.atcute.oauth.getClientAssertion';
4747+4848+ // create initial DPoP proof without nonce
4949+ let dpopProof = await createDpopProof(htu);
5050+5151+ // make the request
5252+ let response = await client.post('dev.atcute.oauth.getClientAssertion', {
5353+ input: { aud },
5454+ headers: { DPoP: dpopProof },
5555+ });
5656+5757+ // check for nonce requirement (UseDpopNonce error)
5858+ if (!response.ok && response.data.error === 'UseDpopNonce') {
5959+ const dpopNonce = response.headers.get('DPoP-Nonce');
6060+ if (dpopNonce) {
6161+ // retry with nonce
6262+ dpopProof = await createDpopProof(htu, dpopNonce);
6363+6464+ response = await client.post('dev.atcute.oauth.getClientAssertion', {
6565+ input: { aud },
6666+ headers: { DPoP: dpopProof },
6767+ });
6868+ }
6969+ }
7070+7171+ if (!response.ok) {
7272+ const message = response.data.message ?? response.data.error ?? 'CAB request failed';
7373+ throw new Error(message);
7474+ }
7575+7676+ const { client_assertion } = response.data;
7777+7878+ return {
7979+ client_assertion,
8080+ client_assertion_type: CLIENT_ASSERTION_TYPE_JWT_BEARER,
8181+ };
8282+ };
8383+};
8484+8585+// re-export types for convenience
8686+export type { ClientAssertionCredentials, ClientAssertionFetcher, FetchClientAssertionParams };
+35
packages/oauth/cab/lib/client/types.ts
···11+/**
22+ * client assertion credentials returned from a CAB backend.
33+ */
44+export interface ClientAssertionCredentials {
55+ /** the signed JWT assertion */
66+ client_assertion: string;
77+ /** the assertion type (always jwt-bearer) */
88+ client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer';
99+}
1010+1111+/**
1212+ * parameters for fetching a client assertion.
1313+ */
1414+export interface FetchClientAssertionParams {
1515+ /** JWK thumbprint of the DPoP key to bind the assertion to */
1616+ jkt: string;
1717+ /** authorization server issuer (audience for the assertion) */
1818+ aud: string;
1919+2020+ /**
2121+ * create a DPoP proof to prove you possess the key for the claimed jkt.
2222+ *
2323+ * @param htu origin and pathname to the CAB backend
2424+ * @param nonce optional DPoP nonce from the server
2525+ * @returns DPoP proof that can be included in the request
2626+ */
2727+ createDpopProof: (htu: string, nonce?: string) => Promise<string>;
2828+}
2929+3030+/**
3131+ * function that fetches a client assertion from a CAB backend.
3232+ */
3333+export type ClientAssertionFetcher = (
3434+ params: FetchClientAssertionParams,
3535+) => Promise<ClientAssertionCredentials>;
+1
packages/oauth/cab/lib/lexicons/index.ts
···11+export * as DevAtcuteOauthGetClientAssertion from './types/dev/atcute/oauth/getClientAssertion.js';
···11+export {
22+ exportJwkKey,
33+ exportPkcs8Key,
44+ generatePrivateKey,
55+ importJwkKey,
66+ importPkcs8Key,
77+ Keyset,
88+ type ImportKeyOptions,
99+ type KeySearchOptions,
1010+ type PrivateKey,
1111+ type SigningAlgorithm,
1212+} from '@atcute/oauth-keyset';
1313+1414+export {
1515+ buildClientMetadata,
1616+ CLIENT_ASSERTION_TYPE_JWT_BEARER,
1717+ FALLBACK_ALG,
1818+ type AtprotoAuthorizationServerMetadata,
1919+ type AtprotoProtectedResourceMetadata,
2020+ type ConfidentialClientMetadata,
2121+ type OAuthAuthorizationServerMetadata,
2222+ type OAuthClientMetadata,
2323+ type OAuthProtectedResourceMetadata,
2424+ type OAuthResponseMode,
2525+} from '@atcute/oauth-types';
2626+2727+export {
2828+ createClientAssertion,
2929+ type ClientAssertionResult,
3030+ type CreateClientAssertionOptions,
3131+} from './client-assertion.js';
3232+export {
3333+ computeJktFromJwk,
3434+ DPoPVerifyError,
3535+ verifyDPoP,
3636+ type DPoPClaims,
3737+ type DPoPVerifyOptions,
3838+ type DPoPVerifyResult,
3939+} from './dpop-verifier.js';
4040+export { createCabHandler, registerCab, type CabOptions } from './handler.js';
4141+export type { DpopSecret } from './dpop-nonce.js';