@appdirect/spa-oauth-pkce
v0.0.1-main-4e5590b
Published
Framework-agnostic OAuth 2.0 Authorization Code + PKCE client for browser SPAs
Readme
@appdirect/spa-oauth-pkce
Framework-agnostic OAuth 2.0 Authorization Code + PKCE client for browser SPAs. No BFF required.
Designed for AppDirect marketplaces — supply issuer and OAuth endpoints; the SDK handles /auth/token and /auth/refresh automatically.
Features
- OAuth 2.0 Authorization Code + PKCE (S256)
- OIDC ID token validation (issuer, audience, expiry, nonce)
- Explicit provider configuration (no browser-side OIDC discovery)
- Built-in AppDirect secondary exchange (
/auth/token) and session refresh (/auth/refresh) - Memory-first token storage with optional sessionStorage persistence
- CORS-safe token requests via XMLHttpRequest
- Event-driven
AuthManagerAPI — works with React, Vue, Angular, Svelte, or vanilla JS - Zero runtime dependencies
Install
Published to the public npm registry
npm install @appdirect/spa-oauth-pkceQuick start
import { createAuthManager, createOAuthConfig } from "@appdirect/spa-oauth-pkce";
const config = createOAuthConfig({
clientId: "your-client-id",
provider: {
issuer: "https://your-provider.example.com",
authorizationEndpoint: "https://your-provider.example.com/oauth2/authorize",
tokenEndpoint: "https://your-provider.example.com/oauth2/token",
},
persistTokens: true,
});
const auth = createAuthManager(config);
await auth.init(); // call on every page load
auth.subscribe((state) => {
if (state.status === "authenticated") {
console.log("Logged in as", state.user?.email);
}
});
auth.login(); // redirect to provider
auth.logout(); // clear sessionDocumentation
| Guide | Description | | ---------------------------------------- | --------------------------------------------------------------- | | Usage Guide | Configuration, API reference, framework integration | | Architecture | Module layout, storage model, two-token model, extension points | | Flow | PKCE sequence diagrams, state machine, security model |
Architecture
See docs/ARCHITECTURE.md for the full module layout, storage model, and extension points.
┌─────────────────────────────────────────────────┐
│ Your SPA (any framework) │
│ auth.subscribe() → update UI │
│ auth.login() / logout() / getAccessToken() │
└────────────────────┬────────────────────────────┘
│
┌────────────────────▼────────────────────────────┐
│ AuthManager │
│ init() · login() · logout() · refresh() │
│ /auth/token · /auth/refresh · autoRefresh │
└────────────────────┬────────────────────────────┘
│
┌────────────────────▼────────────────────────────┐
│ OAuthClient │
│ authorize() · handleCallback() · refresh() │
└──┬─────────┬─────────┬─────────┬───────────────┘
│ │ │ │
pkce jwt xhr token-storage
+ auth-refreshTwo API tiers:
| Tier | Use when |
| --------------------- | --------------------------------------------- |
| createAuthManager() | Most apps — lifecycle + subscribe() |
| OAuthClient | Full control over authorize/callback/refresh |
| createOAuthConfig() | Build config from explicit provider endpoints |
Provider configuration
const config = createOAuthConfig({
clientId: "my-app",
provider: {
issuer: "https://your-provider.example.com",
authorizationEndpoint: "https://your-provider.example.com/oauth2/authorize",
tokenEndpoint: "https://your-provider.example.com/oauth2/token",
},
});See Usage Guide → Provider configuration for JSON import and tokenEndpointAuthMethod.
AppDirect token flow
createAuthManager() always performs the AppDirect two-step token model:
- After OAuth2 callback → POST
{issuer}/auth/tokenwith OAuth2 access token as Bearer - Session refresh → POST
{issuer}/auth/refreshwith session refresh token as Bearer
No path configuration is required from consumer apps. Override only with a custom onTokensReceived hook if needed.
const auth = createAuthManager(config);The session refresh token is stored in oauth_token_set.refreshToken (single source of truth). authRefreshTokenStorage is a facade over that field; AuthSnapshot.authRefreshToken mirrors it.
| Token | Storage | After reload |
| --------------------- | ----------------------------------------- | ------------------------------------ |
| Session access token | oauth_token_set.accessToken | Restored when persistTokens: true |
| Session refresh token | oauth_token_set.refreshToken | Restored when persistTokens: true |
| OAuth2 access token | AuthSnapshot.oauth2AccessToken (memory) | null |
| User profile | AuthSnapshot.user | null — app loads via bootstrap API |
Session restore is access-token-based: it checks expiry and refreshes via /auth/refresh when needed; user profile is not loaded on restore (user is null until the app provides it).
See Usage Guide → Secondary token exchange.
CORS requirements
The token endpoint must allow cross-origin requests from your SPA origin. See Usage Guide → CORS requirements.
Security
- Public client only — no
client_secretin browser code - PKCE S256, state (CSRF), nonce (replay) validation on callback
- Session restore is access-token-based — no ID token re-validation on reload
- User profile after restore is the app's responsibility
- Session tokens in
oauth_token_setonly — no separate refresh or OAuth2 storage keys - OAuth2 access token in memory after callback only — never persisted
- Memory-first tokens; never uses
localStorage - Callback URL cleaned from browser history
License
MIT
