Client SDK Guide
This guide is the adopter-facing authority for the current TypeScript SDK surface. It explains package boundaries, stable entry points, environment/controller responsibilities, and the current 0.3.x scope boundary.
It does not carry roadmap history or implementation chronology. Use 100-ROADMAP.md for release backlog and deferred work, 110-TS_SDK_MIGRATIONS.md for public-surface migration decisions, and 021-REFERENCE-APP-OUTPOSTS.md for the downstream adopter case.
Goal
The SDK gives browser, React, Angular, and server-host adopters explicit auth-context entry points without turning reference-app glue into public API. The current 0.3.x baseline is browser-owned token-set auth plus thin basic-auth/session helpers; mixed-custody, BFF, and server-side token ownership remain outside the SDK baseline.
Current Scope and Boundaries
Current authority:
@securitydept/clientowns foundation environment primitives, persistence, cancellation, tracing, and shared auth coordination.@securitydept/basic-auth-context-clientand@securitydept/session-context-clientown thin auth-context helpers for browser and server hosts.@securitydept/token-set-context-clientowns browser-owned token-set modes, registry lifecycle, access-token substrate vocabulary, and OIDC mode entries.@securitydept/client-react/@securitydept/client-angularown shared framework-router glue.- context-specific React / Angular packages own provider, hook, DI, and signal integration for their families.
Non-authority:
apps/webui/src/api/*, pages, copy, route tables, and diagnostics UI are reference-app glue.~/workspace/outpostsis downstream calibration evidence, not an SDK API template.- provider choice, chooser UI, product flow semantics, and app-local failure copy remain adopter responsibilities.
Top-Level Decisions
- TypeScript is the only active SDK productization track for
0.3.x. - Framework adapters stay thin and consume shared core owners rather than becoming first owners of framework-neutral behavior.
- Public surface changes move together with inventory, evidence, docs anchors, and migration ledger entries.
- The current
0.3.xrelease-preparation line is packaging, documentation, downstream-adopter correctness, and release readiness work; it does not add a new auth context.
Terminology and Naming
- auth context: a deployment-oriented family such as basic-auth, session, or token-set.
- mode: a concrete operating shape inside an auth context, such as
frontend-oidcorbackend-oidc. - environment: a host composition-root dependency object. It carries transport, stores, clock, scheduler, logging/tracing, and host capabilities such as page location/history. Core client constructor dependencies are also an environment; they are not a separate runtime object.
- capability: the narrowest structural view a helper needs from an environment or host object, such as
PageLocationCapabilityorPageLocationHistoryCapability. - client: a protocol/domain object that performs auth, session, OIDC, token, or resource operations.
- registry: a multi-client owner for registration, readiness/lazy lifecycle, keyed lookup, URL/callback discrimination, and route/resource orchestration.
- controller: a framework-neutral state-machine or flow-orchestration owner. It owns state/signals, in-flight coalescing/dedupe, disposal, and commands such as
resume(),refresh(), orlogout(). - service: a host/framework facade or broader application service entry. A service may wrap a controller, but it must not redefine the controller's state-machine semantics.
- adapter: a framework-specific host integration layer.
- reference app: proof and example, not default owner.
Naming rule: dependency objects use Environment or a narrower Capability suffix; state/flow owners use Controller; framework facades use Service; protocol objects use Client; multi-client lifecycle owners use Registry. Do not introduce new public XxxRuntime names for dependency bags or state owners.
Packaging Style
Packages are small, explicit, and side-effect-light. Root exports carry stable family contracts where possible. /web, /server, framework, and router subpaths carry host-specific glue and remain provisional until wider evidence exists.
Recommended Repository Layout
Adopters should keep SDK usage close to the auth boundary:
src/auth/
environment.ts
tokenSet.ts
routes.ts
api.tsDo not copy apps/webui folders as a product template. Lift only the SDK entry shapes that match your host.
TypeScript SDK Coding Standards
Enum-like String Domains
Use export const Foo = { ... } as const plus export type Foo = (typeof Foo)[keyof typeof Foo] for public string domains.
Named Constants for Public Contracts
Repeated telemetry, storage, route, or error vocabulary used across packages must have named constants.
API Shape: Options Object First
Public functions use an options object for optional parameters. A positional second argument is acceptable only when it is uniquely ergonomic and unlikely to widen. If a public API widens, convert the whole second argument to options even when that is a breaking change.
Foundation Design
The foundation layer is not an auth product shell. It exists so family packages can share environment-safe contracts.
State Primitives
State primitives are explicit, host-owned, and framework-neutral. Framework adapters may expose convenience hooks or signals, but the shared state contract remains in foundation or family owners.
Event Primitives
Events should describe machine-facing lifecycle facts. User-facing presentation belongs to the host.
@securitydept/client/events exposes the foundation event-stream traits and operator facade used by token-set lifecycle telemetry. Family packages should emit SecurityDept event traits as their public contract; RxJS is an implementation and interop detail, not the shape adapters must expose.
Transport
Transport is always injected or selected by the host. SDK packages must not assume a global fetch policy beyond the documented browser/server entry.
Persistence
@securitydept/client/persistence owns RecordStore semantics, including single-consume callback state through take(). @securitydept/client/persistence/web owns browser persistence adapters.
Auth Coordination
@securitydept/client/auth-coordination owns planner-host and requirement orchestration primitives. It is headless: it may decide required actions, but it does not own chooser UI, route copy, or product flow semantics.
Configuration
Read configuration in three layers:
- foundation environments/capabilities
- auth-context config
- adapter/host registration glue
Do not flatten these into one global config DSL for the current baseline.
Scheduling and Unified Input Sources
Scheduling, cancellation, abort interop, visibility, storage, and promise/signal helpers live in foundation and web subpaths. They are shared primitives, not a stream DSL.
Internal Dependency Injection
Framework DI remains an adapter concern: Angular DI and React Context live in their adapter packages. Framework-neutral host capability resolution is a foundation concern. Core clients consume ClientEnvironment; non-client-bound helpers consume explicit typed environment objects or narrower capability views created by the host composition root.
The canonical foundation model is:
ClientEnvironmentis the flattened foundation client dependency environment. It directly carries transport, scheduler, clock, logging/tracing, and persistence/session stores. HistoricalClientRuntimenaming is retired and not canonical vocabulary.WebClientEnvironmentis the Web-capable client environment and does not imply a page document.PageClientEnvironmentextends the Web environment with page-only capabilities such aswindow.locationandwindow.history.- Helpers should request the narrowest capability view they need, for example
Pick<WebClientEnvironment, "transport" | "sessionStore">orPageLocationHistoryCapability, rather than accepting the full environment by default. environment.runtime,ClientRuntime,createRuntime(),createWebRuntime(), andderiveClientRuntime()are retired naming artifacts. New public API and documentation must use Environment names such asClientEnvironment,createClientEnvironment(), orderiveClientEnvironment().- Public option keys that carry environment-like dependency sources should stay named
environment; the required capability is expressed by the type, not by introducing parallel keys such aspageEnvironment.
Conceptual split:
| Concept | Owns | Does not own | Naming |
|---|---|---|---|
| Environment | host dependencies and capabilities | business lifecycle state machine | ClientEnvironment, WebClientEnvironment, PageClientEnvironment |
| Capability | minimal structural dependency view | unrelated host dependencies | PageLocationCapability |
| Client | protocol/domain operations | framework lifecycle or DI | SessionContextClient, BackendOidcModeClient |
| Registry | multi-client registration/readiness/discrimination | UI policy or framework state | TokenSetAuthRegistry |
| Controller | framework-neutral flow/state orchestration | framework DI facade or product UI | TokenSetCallbackResumeController, SessionContextController |
| Service | framework/host facade over clients/controllers | duplicated core state semantics | SessionContextService, CallbackResumeService |
Do not model these objects as a DI container, service locator, provider tree, global singleton, or business config DSL. Auth-context configuration such as base URLs, source keys, account binding, and product routes remains in family config or host code, not in the foundation environment.
Foundation Web presets are explicit composition templates, not automatic host detection:
| Preset factory | Returns | Default page capabilities | Default Web storage | Intended host |
|---|---|---|---|---|
createBrowserPageClientEnvironment(options) | PageClientEnvironment | yes | yes | real browser page, tab, or popup document |
createBrowserWorkerClientEnvironment(options) | WebClientEnvironment | no | no | dedicated/shared worker-style browser host |
createServiceWorkerClientEnvironment(options) | WebClientEnvironment | no | no | service worker |
createBrowserExtensionBackgroundClientEnvironment(options) | WebClientEnvironment | no | no | extension background or MV3 service-worker-style host |
Preset names are public vocabulary for docs, trace/error context, and tests through ClientEnvironmentPreset. Do not use a string-driven createEnvironmentFromPreset(name) or global-shape detection to guess the host. Worker, service-worker, and extension-background presets must receive persistence/session stores explicitly when they need storage, and page-only helpers must fail fast when used outside a page environment.
When a host needs stable layered environment ownership across routes, commands, or framework adapters, use ClientEnvironmentService from @securitydept/client/web as the reusable foundation resolver. resolveClientEnvironment() / resolveWebEnvironment() / resolvePageEnvironment() coalesce concurrent async materialization, while read*() exposes Suspense-compatible render-time reads by throwing the shared pending promise or cached error. In React, the canonical bridge is ClientEnvironmentServiceProvider plus useClientEnvironmentService() / usePageClientEnvironment() from @securitydept/client-react; in Angular, the canonical DI bridge is providePageClientEnvironment({ environment }) from @securitydept/client-angular. Service instances belong to the framework composition root (provider/context, injector, or another host-owned scope), not to JS module-cache singletons.
This rule applies beyond @securitydept/client: context packages and framework adapters must use the same boundary for public helpers. Any helper that reads host globals, performs page navigation, constructs a client, or owns transport/store/scheduler/clock wiring should accept a client environment or a narrow capability view. Provider, DI, and top-level adapter registration APIs may accept a full environment as composition roots; ordinary hooks, guards, interceptors, services, and convenience helpers should not each redeclare the full dependency bag.
Context Client Design
basic-auth-context-client
Stable root surface for basic-auth boundary helpers. The /web and /server entries provide thin host helpers, and React/Angular adapters remain host wrappers.
session-context-client
Stable root surface for session login URL, post-auth redirect, user-info, logout, and browser-shell convenience. SessionContextController is the framework-neutral state owner for user-info refresh, logout cleanup, and redirect helpers. Framework adapters consume this controller through hooks, DI, signals, or observables rather than duplicating session semantics.
token-set-context-client
Provisional token-set family for browser-owned OIDC/token material flows. It owns backend-oidc-mode, frontend-oidc-mode, orchestration, access-token-substrate, and registry entries. The registry entry owns shared callback resume orchestration through TokenSetCallbackResumeController; React hooks and Angular services/components bridge that controller instead of becoming callback state machines.
SSR / Server-Side Support
basic-auth-context and session-context
Server-host adopters should use the dedicated /server helper entries for host-neutral request/response coordination.
token-set-context
Server-side token ownership, BFF, and mixed-custody remain outside the current 0.3.x SDK baseline. The current SDK baseline is browser-owned token-set.
Error Model
SDK errors expose machine-facing codes and host-facing recovery hints where relevant. Host copy and UI state remain adopter-owned. Do not parse opaque Error.message strings for control flow.
Cancellation and Disposal
@securitydept/client/web owns browser cancellation interop, including AbortSignal bridges. Long-lived hosts should wire cancellation and disposal explicitly.
Logging, Tracing, and Testing
@securitydept/client owns the minimal trace event and operation-correlation primitives used by SDK flows. @securitydept/test-utils remains experimental and is not a current beta npm publish target.
Build, Compatibility, and Side Effects
Output and Compatibility
Packages target modern ESM hosts and TypeScript project references. Angular packages are built with ng-packagr; non-Angular SDK packages are built with tsdown.
Polyfills
SDK packages must not silently install global polyfills. Adopters own runtime polyfill decisions.
sideEffects / Tree Shaking
Packages should remain import-safe and side-effect-light. Registration side effects belong to explicit provider/adapter functions.
API Stability
Current 0.x Freeze Semantics
The canonical meaning is now:
| Stability | Meaning | Change discipline |
|---|---|---|
stable | frozen adopter-facing surface | stable-deprecation-first |
provisional | public and usable, but still allowed to evolve under migration discipline | provisional-migration-required |
experimental | allowed to move quickly with no stability promise | experimental-fast-break |
Current Contract Snapshot
The table below is the current TS SDK public-surface authority snapshot. It must remain aligned with public-surface-inventory.json.
| Surface | Stability | Owner | Change discipline |
|---|---|---|---|
@securitydept/client | stable | foundation | stable-deprecation-first |
@securitydept/client/persistence | stable | foundation | stable-deprecation-first |
@securitydept/client/persistence/web | stable | foundation | stable-deprecation-first |
@securitydept/client/web | stable | foundation | stable-deprecation-first |
@securitydept/client/auth-coordination | provisional | foundation | provisional-migration-required |
@securitydept/client/web-router | provisional | foundation | provisional-migration-required |
@securitydept/basic-auth-context-client | stable | basic-auth-context | stable-deprecation-first |
@securitydept/basic-auth-context-client/web | provisional | basic-auth-context | provisional-migration-required |
@securitydept/basic-auth-context-client/server | provisional | basic-auth-context | provisional-migration-required |
@securitydept/basic-auth-context-client-react | provisional | basic-auth-context | provisional-migration-required |
@securitydept/session-context-client | stable | session-context | stable-deprecation-first |
@securitydept/session-context-client/web | provisional | session-context | provisional-migration-required |
@securitydept/session-context-client/server | provisional | session-context | provisional-migration-required |
@securitydept/session-context-client-react | provisional | session-context | provisional-migration-required |
@securitydept/client/events | provisional | foundation | provisional-migration-required |
@securitydept/token-set-context-client/backend-oidc-mode | provisional | token-set-context | provisional-migration-required |
@securitydept/token-set-context-client/backend-oidc-mode/web | provisional | token-set-context | provisional-migration-required |
@securitydept/token-set-context-client/frontend-oidc-mode | provisional | token-set-context | provisional-migration-required |
@securitydept/token-set-context-client/orchestration | provisional | token-set-context | provisional-migration-required |
@securitydept/token-set-context-client/access-token-substrate | provisional | token-set-context | provisional-migration-required |
@securitydept/token-set-context-client/registry | provisional | token-set-context | provisional-migration-required |
@securitydept/token-set-context-client/web-router | provisional | token-set-context | provisional-migration-required |
@securitydept/test-utils | experimental | foundation | experimental-fast-break |
@securitydept/basic-auth-context-client-angular | provisional | basic-auth-context | provisional-migration-required |
@securitydept/session-context-client-angular | provisional | session-context | provisional-migration-required |
@securitydept/token-set-context-client-react | provisional | token-set-context | provisional-migration-required |
@securitydept/token-set-context-client-react/react-query | provisional | token-set-context | provisional-migration-required |
@securitydept/token-set-context-client-react/tanstack-router | provisional | token-set-context | provisional-migration-required |
@securitydept/token-set-context-client-angular | provisional | token-set-context | provisional-migration-required |
@securitydept/client-react | provisional | shared-framework | provisional-migration-required |
@securitydept/client-react/tanstack-router | provisional | shared-framework | provisional-migration-required |
@securitydept/client-angular | provisional | shared-framework | provisional-migration-required |
How To Read token-set-context-client Subpaths
/backend-oidc-mode: platform-neutral client/service/token-material entry./backend-oidc-mode/web: browser redirect, callback, storage, and bootstrap glue./frontend-oidc-mode: browser-owned OIDC client mode and config projection materialization./orchestration: protocol-agnostic token lifecycle and route requirement primitives./access-token-substrate: access-token propagation vocabulary and substrate contract./registry: shared multi-client lifecycle core./web-router: token-set-specific raw Web Router helper that calls the canonical auth barrier before redirect/block fallback.
Capability Boundary Rules
- Framework router glue belongs to shared framework adapters.
- Browser token-lifecycle glue belongs to the token-set family.
- App-local business API wrappers are not SDK public surface.
- Reference apps provide evidence; they do not define package ownership by themselves.
token-set-context-client Frontend Subpath / Abstraction Split
Frontend adopters should reason in layers: foundation coordination, token-set mode/substrate/registry, then framework adapter. The token-set family is not the sole owner of every frontend helper.
Config Projection Source Contract (frontend-oidc-mode/config-source.ts)
frontend-oidc-mode owns projection-source precedence, validation, freshness, restore, and revalidation. createFrontendOidcModeBrowserClient() owns browser materialization from an explicit FrontendOidcModeWebClientEnvironment; the host owns config endpoint wiring, environment creation, and page routes.
Reference-App Host Evidence (apps/webui / apps/server)
apps/webui and apps/server prove the current reference-app baseline: backend-mode and frontend-mode host splits, keyed callback/readiness, React Query token-set management flows, route security, dashboard bearer access, browser harness reporting, and shared error/diagnosis consumption.
Framework Router Adapters
Framework router adapters are owned by:
@securitydept/client-react/tanstack-router@securitydept/client-angular
Canonical semantics: full matched-route chain aggregation, inherit / merge / replace, child-route serializable metadata, root-level runtime policy, and no product chooser UI in the SDK.
Angular token-set route handlers receive a route unauthenticated context with attemptedUrl. Use that value when starting OIDC redirect login so the attempted navigation is recorded as postAuthRedirectUri. The canonical Angular path is to provide one provider-scoped environment source from the composition root with providePageClientEnvironment({ environment }), where environment is typically a stable ClientEnvironmentService or another inject-safe resolver owned by Angular DI, then call createTokenSetOidcLoginRedirectHandler({ ... }) without re-creating page capability in every guard path. The shared contract is OidcRedirectLoginClient plus OidcRedirectLoginOptions from @securitydept/token-set-context-client/registry; Angular, React/TanStack, FrontendOidcModeClient, and backend-oidc web clients all target that capability instead of a mode-qualified helper. The helper keeps environment as its public key, but that key now represents a stable page-environment source that the guard awaits inside the same async flow as registry lookup; use an already-materialized page environment object only when the host intentionally owns synchronous page capability. Do not read Angular Router.url for this value inside a guard handler, because the attempted navigation has not been committed yet. A handler that has started a full-page external redirect should not resolve to false; the SDK helper returns a never-settling guard result after starting the redirect so Angular does not finalize an in-app navigation cancel while the page is leaving.
TanStack Router's createSecureBeforeLoad() likewise passes an unauthenticated handler context with attemptedUrl. React/TanStack adopters that start a full-page external auth redirect should use createExternalRedirectBeforeLoadHandler(), call their login client inside the callback, and pass context.attemptedUrl as postAuthRedirectUri. Do not infer the target page from window.location; while beforeLoad is running, the current document URL may still be the previously committed route.
token-set-context-client v1 Scope Baseline
The current 0.3.x baseline is browser-owned token-set with framework adapters, registry lifecycle, route orchestration, readiness, callback handling, reference-app proof, and downstream adopter calibration.
Outside the baseline: mixed-custody, BFF, server-side token ownership, heavier chooser UI, and non-TS SDK productization.
Adopter Checklist
Things that must not be treated as SDK surface
- reference-app page components and UI copy
apps/webui/src/api/*business wrappers- adopter route tables and page state
- one-off data shaping for a single app
Checklist before integration
- Pick the auth context first.
- Pick browser, framework, or server-host entry second.
- Confirm whether the entry is stable, provisional, or experimental.
- Accept the current
0.3.xboundary before depending on token-set behavior.
Verified Environments / Host Assumptions
Verified means focused evidence, reference-app proof, or downstream-adopter proof exists. It does not mean broad coverage across every host.
Current evidence covers Node/browser foundation behavior, React 19, Angular, TanStack Router, raw Web Router, apps/webui, and outposts. Host support should be described through ECMAScript requirements, adapter capabilities, and real evidence.
Minimal Entry Paths
1. Foundation entry: environment stays explicit
Use @securitydept/client for shared primitives. It is not a product-level auth shell.
2. Browser entry: ./backend-oidc-mode/web owns browser glue
Use @securitydept/token-set-context-client/backend-oidc-mode/web for backend-owned OIDC/token-set browser flows.
This subpath is browser-host glue, not a promise that every Web-like runtime has page navigation. Use the foundation environment boundary before choosing helpers:
- Browser client construction should take a
WebClientEnvironmentcreated at the host composition root. Do not pass transport, scheduler, clock, persistent store, and session store independently to every helper. - Worker-like hosts, service workers, and extension backgrounds may create/restore clients and run token-state APIs, but they must not run page callback capture by default.
- Page-only helpers may read
window.location/window.historyonly throughPageClientEnvironment; their names or options must make the page boundary explicit, such ascurrentPageLocationAsPostAuthRedirectUri,buildAuthorizeUrlReturningToCurrentPage,bootstrapBackendOidcModePageClient, andcaptureBackendOidcModePageCallbackFragment. For token-set OIDC login, the shared browser entry isloginWithRedirect({ environment, postAuthRedirectUri })on anOidcRedirectLoginClient:FrontendOidcModeClientsatisfies that contract directly, andcreateBackendOidcModeWebClient(...)materializes the same method whileloginWithBackendOidcRedirect(...)remains a legacy/convenience alias. Popup helpers such asloginWithBackendOidcPopupandrelayBackendOidcPopupCallbackremain page-only and fail fast outside an explicit environment. - Host-injected callback helpers must receive a
BackendOidcModeWebClientEnvironmentor explicit page/callback-fragment capabilities.loginWithBackendOidcPopup()requiresBackendOidcModePopupLoginCapability, andresetBackendOidcModeBrowserState()requires an explicitcallbackFragmentStore; ordinary helpers do not construct global session-storage-backed fragment stores. They must fail fast when a required capability is missing instead of falling through towindow is not definedor stale URL parsing.
Recommended host environments:
| Host | Environment | Callback capture | Restore/token state | Storage defaults |
|---|---|---|---|---|
| browser page/tab/popup | PageClientEnvironment | yes | yes | page storage may be used |
| browser worker | WebClientEnvironment | no by default | yes | explicit store only |
| service worker | WebClientEnvironment | no by default | yes | explicit store only |
| extension background | WebClientEnvironment | no by default | yes | explicit store only |
Do not decide whether callback bootstrap is allowed by checking globalThis.location. A service worker or extension background may expose a location-like object without page history semantics. Page detection must validate page/document capabilities such as window.location and window.history.replaceState.
The same page-boundary rule applies to basic-auth and session /web redirect helpers: redirect helpers that read or write window.location are page helpers. Worker-like hosts must pass explicit URL/navigation capabilities or keep redirect initiation in a real page context.
3. React entry: dedicated adapter packages own Provider and hook wiring
Use:
@securitydept/client-react@securitydept/basic-auth-context-client-react@securitydept/session-context-client-react@securitydept/token-set-context-client-react
Provider config follows the three-layer model: auth-context config, environment/capability dependencies where applicable, and host registration glue.
ClientEnvironmentServiceProvider({ service })is the canonical React composition-root bridge for provider-scoped environment ownership. Render-time consumers should read page capability throughusePageClientEnvironment()under Suspense and an error boundary; command/event code should useuseClientEnvironmentService().resolvePageEnvironment().SessionContextProvideris an adapter leaf overSessionContextController. UseSessionContextProvider({ config, environment, initialRefresh })when React should create the controller, or passcontrollerwhen the host owns it. Hooks bridge controller state withuseSyncExternalStore; they do not own user-info fetch or logout state machines.- The legacy single-client
BackendOidcModeContextProvideris also environment-first: useBackendOidcModeContextProvider({ config, environment })with aBackendOidcModeWebClientEnvironment, and let the provider materialize the browser client throughcreateBackendOidcModeWebClient(...)instead of accepting a raw dependency/capability bag. TokenSetAuthProvider({ clients })remains the multi-client registration root. Each entry'sclientFactorystill owns auth-context config plus environment composition; ordinary hooks and keyed accessors do not accept a full environment.useTokenSetCallbackResume({ getCurrentUrl, describeError })remains a page-route convenience hook over the sharedTokenSetCallbackResumeController. The explicitgetCurrentUrloverride wins overwindow.location.href, and when no current URL is available the hook stays idle instead of forcing callback handling. Default failure presentation comes fromreadTokenSetCallbackResumeErrorDetails()in@securitydept/token-set-context-client/registry; mode-specific copy such as frontend-oidc wording belongs in an explicitdescribeErroroverride owned by the host or adapter.
4. Angular entry: thin DI wrappers preserve canonical owner boundaries
Use:
@securitydept/basic-auth-context-client-angular@securitydept/session-context-client-angular@securitydept/token-set-context-client-angular
Layering rules:
provideBasicAuthContext({ config }): auth-context config only.provideSessionContext({ config, environment, initialRefresh }): adapter leaf overSessionContextController; Angular DI readsenvironment.transportandenvironment.sessionStorefrom the sameWebClientEnvironmentand registers the controller. Service construction does not auto-probe user-info unlessinitialRefreshis explicit.SessionContextService: signal / observable facade over the controller. Low-level auth-context behavior remains onSessionContextService.client.provideTokenSetAuth({ clients, idleWarmup }): Angular host registration; each client entry still owns auth-context config and environment composition.providePageClientEnvironment({ environment }): canonical Angular DI bridge for page-scoped capability resolution. The canonical value is a provider-scopedClientEnvironmentServiceor another inject-safe stable resolver that can await page capability; passing a synchronous page environment object is only the already-materialized host-owned case.CallbackResumeServicewraps the sharedTokenSetCallbackResumeControllerand exposes component-freeresume(url)state through Angular signals / observables.TokenSetCallbackComponentis only a page-only convenience component on top of that service; custom hosts, SSR-like tests, or shell adapters can override URL/policy tokens or callCallbackResumeService.resume(url)directly.handleCallback(url)remains a compatibility wrapper.provideTokenSetBearerInterceptor(options?)/createTokenSetBearerInterceptor(registry, options?): freshness-aware bearer-header injection using the SDK options-object API form. Before addingAuthorization, the interceptor calls the shared refresh barrier. An expired token with refresh material is refreshed before the protected request proceeds; an expired token without usable refresh material produces no stale bearer and the auth state is cleared.BearerInterceptorOptions.strictUrlMatchcontrols unmatched URL behavior:- default
strictUrlMatch: false: keeps the single-client convenience fallback that injectsregistry.accessToken()for unmatched URLs; use only when the host calls exactly one registered backend. strictUrlMatch: true: unmatched URLs receive noAuthorizationheader.- multi-backend, multi-audience, or third-party-traffic Angular adopters MUST use
strictUrlMatch: true. TOKEN_SET_BEARER_INTERCEPTOR_OPTIONSis exported for advanced DI/test overrides.
- default
Freshness is owned by the token-set core, not by one framework adapter. ensureAuthForResource(options) is the canonical async barrier for route entry, resume reconciliation, authorized transports, interceptors, and React Query requests. It emits domain auth events, shares the coalesced refresh barrier, and can return an Authorization header plus an opaque temporary token handle. Event payloads must not contain raw access, refresh, or ID token values; token handles are only descriptors that can be resolved by the owning in-memory store while valid. authorizationHeader() remains a synchronous fresh-or-null projection, while ensureAuthorizationHeader() is a compatibility wrapper over the canonical resource barrier. AuthMaterialController has no protocol-level refresh capability, so its authorizationHeader and createTransport() use fresh-or-null projection: expired or invalid-expiry material returns null, and requireAuthorization: true raises unauthenticated instead of sending a stale bearer. registry.accessToken() remains a sync convenience and returns null for expired material; use registry.ensureAuthForResource({ key, needsAuthorizationHeader: true }), registry.ensureAccessToken(key), or registry.ensureAuthorizationHeader(key) when a request must wait for refresh. Calling async registry helpers without a key is only valid when exactly one matching client is registered or ready, depending on the helper.
Browser-owned frontend/backend OIDC factories attach page-resume reconciliation by default. Angular provideTokenSetAuth(...) installs the same default for registry-managed browser clients, including clientFactory implementations that directly return createFrontendOidcModeClient(...). On visibilitychange back to visible, pageshow, focus, and online, the client calls ensureAuthForResource({ source: "resume", forceRefreshWhenDue: true }) and emits resume requested/skipped/completed/failed events. This is a recovery barrier, not an interactive login trigger: refresh failures clear or preserve auth state through the normal token-set client paths, and route/request handlers decide whether to start login. Set per-client resumeReconciliation: false only for hosts that install their own equivalent lifecycle hook; pass resumeReconciliationOptions to provide custom browser targets or throttling in tests.
Short access-token lifetimes should be handled through SDK-owned barriers: persisted restore forces refresh when material is already in the refresh window, browser resume reconciles after hidden tabs/sleep/bfcache, Angular route aggregation waits for restorePromise and ensureAuthForResource({ source: "route_guard", forceRefreshWhenDue: true }) before invoking unauthenticated handlers, and protected requests use ensureAuthForResource({ source: "http_interceptor" | "authorized_transport", needsAuthorizationHeader: true, forceRefreshWhenDue: true }). When frontend-oidc-mode or another token-set mode can stamp accessTokenIssuedAt, token freshness now caps refresh-window and clock-skew calculations relative to the token lifetime instead of applying a raw fixed window to every token. That keeps newly issued short-lived tokens fresh at issuance while still entering refresh_due early enough for restore, resume, route-entry, and request-time refresh recovery. TanStack Router hosts should use createTokenSetSecureBeforeLoad() from @securitydept/token-set-context-client-react/tanstack-router; raw web hosts should use createTokenSetWebRouteAuthCandidate() from @securitydept/token-set-context-client/web-router. Both helpers call ensureAuthForResource({ source: "tanstack_before_load" | "raw_web_router", forceRefreshWhenDue: true }) before redirect/block fallback.
If a downstream resource server reports ExpiredSignature, the rejection is correct: the frontend sent an expired JWT and the SDK/adopter must not inject that bearer. Diagnose whether the browser has refresh material before blaming the refresh barrier:
Object.entries(localStorage)
.filter(([k]) => k.includes("outposts.web.auth"))
.map(([key, raw]) => {
try {
const parsed = JSON.parse(raw);
const tokens = parsed.value?.tokens ?? parsed.tokens;
return {
key,
accessTokenExpiresAt: tokens?.accessTokenExpiresAt,
hasRefreshMaterial: Boolean(tokens?.refreshMaterial),
};
} catch {
return { key, parseError: true };
}
});When hasRefreshMaterial=false, check the IdP, requested scopes, and refresh-token policy; the SDK still must not send the expired access token. For Authentik deployments this usually means verifying that offline_access is requested/allowed and that refresh-token rotation/lifetime settings allow the browser client to keep usable refresh material. When hasRefreshMaterial=true, the SDK should refresh before route admission, page-resume recovery, or the first protected request, or move the client to unauthenticated state; ExpiredSignature should not appear on a request sent through the SDK bearer interceptor or authorized transport.
5. SSR / server-host entry: dedicated ./server helpers
Use:
@securitydept/basic-auth-context-client/server@securitydept/session-context-client/server
Do not import /web subpaths into server-hosted code.
Provisional Adapter Maintenance Standard
./web, ./server, and framework packages are maintained at a stricter provisional bar: stable boundaries, safe import-time behavior, ordinary usage without reference-app glue, focused evidence, real dogfooding, and accurate verified-environment claims.
Provisional Adapter Promotion Checklist
| Condition | Requirement |
|---|---|
| capability boundary is stable | no owner reshuffle across a sustained release window |
| minimal entry is clear | explainable without a full reference page |
| ordinary usage is mature | no app-local glue dependency |
| focused evidence is complete | lifecycle, regression, and import-contract guardrails exist |
| verified environments are explicit | host validation is not overstated |
Current Promotion Readiness (snapshot, not roadmap)
| Adapter / Surface | Current judgment |
|---|---|
@securitydept/client/web | stable foundation-owned browser helper surface |
@securitydept/client/auth-coordination | provisional; planner-host and matched-route-chain contract established |
@securitydept/client/web-router | provisional; raw Web baseline established |
basic-auth-context-client/web | provisional; thin browser convenience established |
session-context-client/web | provisional; login redirect convenience established |
basic-auth-context-client/server / session-context-client/server | provisional; SSR/server-host baseline established |
*-react / *-angular adapter family | provisional; real reference-app/downstream proof exists, broad host matrix does not |
@securitydept/token-set-context-client/frontend-oidc-mode | provisional; keyed pending-state and single-consume callback semantics formalized |
token-set-context-client-react/react-query | provisional; canonical token-set groups/entries consumer path established |
Raw Web Router Baseline
Subpath: @securitydept/client/web-router
The raw Web Router baseline is for non-framework hosts. It uses Navigation API first, History API fallback, and one planner-host submission per full matched-route chain.
Shared Client Lifecycle Contract
Subpath: @securitydept/token-set-context-client/registry
The registry owns primary / lazy initialization priority, preload, whenReady, idleWarmup, reset, keyed lookup aligned with callback/readiness behavior, and the shared generic callback failure presenter describeTokenSetCallbackError(). React and Angular adapters consume this shared core, while mode-specific copy such as describeFrontendOidcModeCallbackError() stays under the mode owner and must be injected explicitly.
React Query Integration
Subpath: @securitydept/token-set-context-client-react/react-query
This is the token-set React consumer surface. It owns groups/entries read and write hooks, readiness queries, keyed hook ergonomics, freshness-aware authorization-header derivation, query-key namespace, and canonical invalidation for token-set management flows. It is not the login, refresh, or lifecycle authority; request-time bearer injection still delegates to the token-set core refresh barrier.
The subpath's module-level fetch transport is request-level convenience only. It does not own auth lifecycle, persistence, refresh, or browser environment state. Hosts may override the resource request transport through requestOptions.transport; authorization headers still come from the token-set client service rather than from a parallel request lifecycle owner.
Examples and Reference Implementations
Primary Real Reference Apps
apps/server: auth, propagation, route composition, server error/diagnosis proof.apps/webui: React/browser/multi-context auth shell, token-set reference page, dashboard, browser harness report, and SDK dogfooding proof.
Downstream Reference Case: Outposts
~/workspace/outposts validates the real Angular adopter path. It uses provideTokenSetAuth(...) plus provideTokenSetBearerInterceptor({ strictUrlMatch: true }), proving strict URL-prefix bounded bearer injection against a downstream confluence backend. The path also calibrates stale-token handling: the SDK must refresh or clear before the first protected Confluence request instead of sending an expired bearer that the backend correctly rejects with ExpiredSignature. Its app-local auth service remains adopter glue, not an SDK API template.
Downstream verification should use local pnpm link: dependencies for SecurityDept SDK packages, not package-manager overrides. Plain TS packages can link to package roots; Angular packages should link to their built dist/ outputs after rebuilding, then clear the downstream Angular/Vite cache before browser verification.
Current Bundle / Code Split Judgment
Bundle and code-splitting are engineering optimization topics, not public-contract blockers for the current 0.3.x line.
Demo and OIDC Provider
Demos explain contracts. Provider choice and demo pages do not define package boundaries or replace focused evidence.
Requirements for Future Developers and AI Agents
- Do not rename or rebuild the client SDK as
auth-runtime. - Do not let framework adapters pollute foundation packages.
- Do not introduce import-time side effects or default global polyfills.
- Do not productize reference-app or adopter glue as SDK API.
- Do not move mixed-custody / BFF / server-side token ownership into the current SDK baseline.
- Move public surface, docs, examples, inventory, and migration notes together.