/** * SMART on FHIR Confidential Client Authorization (server-side App Launch). * * Implements the `authorization_code` grant with: * - PKCE (`S256` code challenge) * - `private_key_jwt` client authentication (RS384 / ES384) * * This is the BFF (Backend-For-Frontend) pattern where a server-side app * can securely protect a private key and authenticates to the token endpoint * using asymmetric client assertions per the SMART * `client-confidential-asymmetric` profile. * * @see https://hl7.org/fhir/smart-app-launch/client-confidential-asymmetric.html * @see https://hl7.org/fhir/smart-app-launch/app-launch.html * * @example * ```ts * const auth = new ConfidentialClientAuth({ * clientId: "my-app", * fhirBaseUrl: "https://fhir.example.com", * privateKey: process.env.PRIVATE_KEY!, * keyId: "key-1", * redirectUri: "https://my-app.com/callback", * scopes: "openid fhirUser patient/*.read offline_access", * }); * * // 1. Build authorize URL with PKCE * const { url, codeVerifier, state } = await auth.buildAuthorizeUrl(); * * // 2. Exchange code for tokens (after redirect callback) * const token = await auth.exchangeCode(code, codeVerifier); * * // 3. Refresh when expired * const refreshed = await auth.refreshToken(token.refresh_token!); * ``` */ import { type SmartSigningAlgorithm } from "./crypto.js"; import { type SmartToken } from "./smart-auth.js"; export interface ConfidentialClientConfig { /** OAuth2 client_id registered with the FHIR server. */ clientId: string; /** FHIR server base URL — used for endpoint discovery and `aud` param. */ fhirBaseUrl: string; /** * Private key for signing client assertions. * Accepts a PEM-encoded PKCS#8 string or a pre-imported `CryptoKey`. */ privateKey: string | CryptoKey; /** Key ID — must match the `kid` in the registered JWKS. */ keyId: string; /** Registered redirect URI. */ redirectUri: string; /** Space-separated OAuth2 scopes. */ scopes: string; /** Signing algorithm (default `ES384`). */ algorithm?: SmartSigningAlgorithm; /** Override token endpoint discovery. */ tokenEndpoint?: string; /** Override authorization endpoint discovery. */ authorizationEndpoint?: string; /** JWT assertion lifetime in seconds (default 300, capped at 300). */ assertionLifetimeSeconds?: number; /** Custom fetch implementation (e.g. for tests, proxies). */ fetchFn?: typeof globalThis.fetch; } export interface AuthorizeUrlResult { /** Full authorization URL to redirect the user to. */ url: string; /** PKCE code_verifier — store server-side, needed for token exchange. */ codeVerifier: string; /** Random state value — store server-side, validate on callback. */ state: string; } export declare class ConfidentialClientAuth { readonly config: ConfidentialClientConfig; private cachedEndpoints; constructor(config: ConfidentialClientConfig); /** * Build the authorization URL with PKCE parameters. * * Returns the URL to redirect the user-agent to, plus the `codeVerifier` * and `state` that the server must persist (e.g. in session) for the * callback validation. * * Per spec §2.1.9.1: * - `code_challenge_method` = `S256` * - `aud` = FHIR base URL * - `client_id` is included in the authorize request (NOT in token request) * - `state` has ≥ 122 bits of entropy */ buildAuthorizeUrl(options?: { /** EHR launch parameter (for EHR launch flow). */ launch?: string; /** Additional query parameters to include. */ extraParams?: Record; }): Promise; /** * Exchange an authorization code for tokens. * * Per spec §2.1.10.1 for confidential clients: * - `client_id` is OMITTED from the token body * - Authentication is via `client_assertion` + `client_assertion_type` * - `code_verifier` proves the PKCE challenge */ exchangeCode(code: string, codeVerifier: string): Promise; /** * Refresh an access token using a refresh token. * * Per spec §2.1.12.1: * - `grant_type` = `refresh_token` * - Confidential client authenticates with `client_assertion` * - `scope` is optional (strict subset of original) */ refreshToken(refreshToken: string, scope?: string): Promise; private resolveEndpoints; private resolveTokenEndpoint; private buildAssertion; private postToken; } //# sourceMappingURL=confidential-client.d.ts.map