@babelfhir-ts/smart-auth
v0.5.1
Published
SMART on FHIR v2 authorization with PKCE — discovery, token management, authenticated fetch, SMART Health Links.
Downloads
3,129
Maintainers
Readme
@babelfhir-ts/smart-auth
SMART on FHIR v2 authorization with PKCE — discovery, token management, authenticated fetch, SMART Health Links.
Installation
npm install @babelfhir-ts/smart-authRuntime dependencies: jose and oauth4webapi for JOSE and PKCE, kill-the-clipboard for SHL parsing and JWE decryption. All MIT.
kill-the-clipboard is only reachable through the ./health-links subpath, so importing this package for PKCE alone does not pull it into your bundle.
Usage
Standalone Launch
import { SmartAuth } from '@babelfhir-ts/smart-auth';
const auth = new SmartAuth({
clientId: 'my-app',
redirectUri: 'http://localhost:3000/callback',
scope: 'openid fhirUser patient/*.read',
fhirBaseUrl: 'https://launch.smarthealthit.org/v/r4/fhir',
});
// Start authorization (redirects to auth server)
await auth.authorize();
// Handle callback
if (auth.isCallback()) {
await auth.handleCallback();
}
// Get authenticated fetch function
const authenticatedFetch = auth.createAuthenticatedFetch();
const response = await authenticatedFetch('/Patient/example');Endpoint Discovery
import { discoverEndpoints } from '@babelfhir-ts/smart-auth';
const endpoints = await discoverEndpoints('https://launch.smarthealthit.org/v/r4/fhir');
// { authorizationEndpoint, tokenEndpoint, ... }Token Management
// Check authentication status
auth.isAuthenticated(); // boolean
// Get current token
const token = auth.getToken(); // SmartToken | null
// Refresh expired token
await auth.refreshAccessToken();
// Get valid token (auto-refreshes if expired)
const validToken = await auth.getValidToken();
// Logout
auth.logout();EHR Launch
const auth = new SmartAuth({ clientId: 'my-app', redirectUri: '...' });
const mode = auth.detectLaunchMode(); // 'standalone' | 'ehr'
if (mode === 'ehr') {
await auth.startEhrLaunch();
} else {
await auth.startStandaloneLaunch('https://my-fhir-server.com/fhir');
}SMART Health Links
A SMART Health Link hands the recipient a shlink:/eyJ… URI rather than a token. Resolving it returns ordinary SMART API Access credentials, so the rest of the app is unchanged.
import {
parseShl, isShlExpired, resolveShl, createShlFetch, HealthLinkError,
} from '@babelfhir-ts/smart-auth/health-links';
const { shl } = parseShl(window.location.hash);
if (isShlExpired(shl)) throw new Error('This share link has expired');
try {
const { access, label, expiresAt } = await resolveShl(shl, { recipient: 'my-viewer' });
const client = new FhirClient(access.aud, createShlFetch(access));
} catch (error) {
if (error instanceof HealthLinkError && error.code === 'passcode-required') {
// prompt, then call again with { recipient, passcode }
}
}recipient identifies your application to the manifest endpoint (spec §4.1) and is required. Failures throw HealthLinkError with a code of malformed, passcode-required, not-found, expired, manifest, decrypt or network — branch on the code, never on the message.
access.query carries the spec's optional search hints. A link scoped to part of a record has no other way to say what it is about, so prefer the hints over assuming the whole patient is reachable:
const searches = access.query ?? [`Patient/${access.patient}/$everything`];SmartApiAccess covers only what the spec defines. Issuers that add their own fields type them at the call site:
import type { SmartApiAccess } from '@babelfhir-ts/smart-auth/health-links';
interface PartialShare extends SmartApiAccess { complete?: boolean }
const { access } = await resolveShl<PartialShare>(shl, { recipient: 'my-viewer' });API
| Export | Description |
|---|---|
| SmartAuth | Full SMART v2 lifecycle with PKCE |
| discoverEndpoints | Auto-discover SMART endpoints from FHIR server |
| BackendServicesAuth | SMART Backend Services (client_credentials) |
| ConfidentialClientAuth | Confidential client with private-key JWT |
| buildClientAssertion | Signed client_assertion for private-key JWT |
| parseShl | Read a shlink:/… URI, fragment or bare payload |
| isShlExpired | Whether the link's own expiry has passed |
| resolveShl | Fetch the manifest, decrypt, return credentials |
| createShlFetch | Fetch wrapper that attaches the SHL bearer token |
| HealthLinkError | Failure carrying a HealthLinkErrorCode |
| SmartConfig | Configuration type |
| SmartToken | Token response type |
| SmartConfiguration | Well-known configuration type |
| SmartApiAccess | Decrypted SHL credentials type |
| ShlResult | resolveShl return type |
| LaunchMode | 'standalone' \| 'ehr' |
Part of BabelFHIR-TS
This package provides SMART on FHIR authentication for @babelfhir-ts/client-r4 and @babelfhir-ts/client-r4b. It can also be used standalone for any SMART on FHIR integration.
License
MIT
