diff --git a/claude.md b/claude.md new file mode 100644 index 0000000..819ca97 --- /dev/null +++ b/claude.md @@ -0,0 +1,235 @@ +# Okta OAuth2 Client SDKs - Monorepo + +> **Status**: Beta +> **Documentation**: https://okta-client-js.netlify.app/ + +This is a monorepo containing a collection of OAuth2 client libraries for JavaScript/TypeScript, designed to simplify OAuth2 integration in JavaScript applications. + +## ๐Ÿ“ฆ Package Architecture + +This monorepo contains three interconnected packages: + +### Foundation Layer +- **[@okta/auth-foundation](./packages/auth-foundation/claude.md)** - Core library providing foundational OAuth2 primitives, token handling, HTTP clients, and platform abstractions. All other packages depend on this. + +### Token Acquisition Layer +- **[@okta/oauth2-flows](./packages/oauth2-flows/claude.md)** - Implements environment-agnostic OAuth2 authentication flows (Authorization Code, Logout). + +### Platform/Management Layer +- **[@okta/spa-platform](./packages/spa-platform/claude.md)** - High-level utilities for managing token lifecycles, storage, browser tab synchronization, and making authenticated requests in browser/SPA environments. + +### Dependency Graph +``` +@okta/spa-platform + โ”œโ”€โ†’ @okta/oauth2-flows (optional peer dependency) + โ””โ”€โ†’ @okta/auth-foundation + +@okta/oauth2-flows + โ””โ”€โ†’ @okta/auth-foundation + +@okta/auth-foundation + (no dependencies) +``` + +## ๐Ÿ—๏ธ Monorepo Structure + +``` +okta-client-javascript/ +โ”œโ”€โ”€ packages/ +โ”‚ โ”œโ”€โ”€ auth-foundation/ # Core foundation library +โ”‚ โ”œโ”€โ”€ oauth2-flows/ # OAuth2 flow implementations +โ”‚ โ””โ”€โ”€ spa-platform/ # SPA platform utilities +โ”œโ”€โ”€ tooling/ # Shared build/config packages +โ”‚ โ”œโ”€โ”€ eslint-config/ +โ”‚ โ”œโ”€โ”€ jest-helpers/ +โ”‚ โ”œโ”€โ”€ rollup-config/ +โ”‚ โ””โ”€โ”€ typescript-config/ +โ”œโ”€โ”€ e2e/ # End-to-end test applications +โ”‚ โ””โ”€โ”€ apps/ +โ”‚ โ”œโ”€โ”€ redirect-model/ +โ”‚ โ””โ”€โ”€ token-broker/ +โ”œโ”€โ”€ docs/ # Documentation +โ””โ”€โ”€ scripts/ # Build and automation scripts +``` + +## ๐Ÿ› ๏ธ Technology Stack + +- **Language**: TypeScript 5.9+ +- **Module System**: ESM (ES Modules) +- **Package Manager**: Yarn (workspaces) +- **Build Orchestrator**: Turborepo +- **Build Tool**: Rollup (via shared config) +- **Type Generation**: TypeScript compiler +- **Testing**: Jest (with browser/node-specific configs) +- **Linting**: ESLint (via shared config) + +## ๐Ÿš€ Getting Started (Development) + +### Prerequisites +- Node.js >= 20.11.0 (recommended: >= 22.13.1) +- Yarn >= 1.19.0 + +### Initial Setup +```bash +# Check Node version +node --version # should be >=20 + +# Install all dependencies +yarn + +# Build all packages +yarn build +``` + +### Development Workflow + +```bash +# Build all packages in watch mode +yarn build:watch + +# Run tests across all packages +yarn test + +# Lint all packages +yarn lint + +# Work on a specific package +cd packages/auth-foundation +yarn test:watch +``` + +### Package Build Process + +Each package follows the same build pattern: +1. **ESM Bundle**: Rollup bundles TypeScript โ†’ ESM JavaScript +2. **Type Definitions**: TypeScript compiler generates `.d.ts` files +3. **Output**: `dist/esm/` (JavaScript) + `dist/types/` (TypeScript definitions) + +## ๐Ÿ“ Shared Tooling Packages + +All packages use shared configurations from the `tooling/` directory: + +- `@repo/typescript-config` - Shared TypeScript configurations +- `@repo/rollup-config` - Shared Rollup build config +- `@repo/eslint-config` - Shared ESLint rules +- `@repo/jest-helpers` - Jest test utilities and mocks + +These are internal workspace packages (not published) that ensure consistency across the monorepo. + +## ๐Ÿงช Testing Strategy + +### Test Environments +Each package supports multiple test environments: +- **Browser tests**: `jest.browser.config.js` - Simulates browser environment +- **Node tests**: `jest.node.config.js` - Runs in Node.js environment +- **Default**: `jest.config.js` - Runs all tests + +### Running Tests +```bash +# From root - all packages +yarn test + +# Specific package, specific environment +cd packages/auth-foundation +yarn test:browser # Browser environment tests +yarn test:node # Node.js environment tests +yarn test:watch # Watch mode +``` + +## ๐Ÿ“š Working with the Codebase + +### Adding Dependencies + +```bash +# Add to a specific package +yarn workspace @okta/auth-foundation add + +# Add dev dependency +yarn workspace @okta/auth-foundation add -D + +# Add to root +yarn add -W +``` + +### Creating a New Package + +1. Create package directory: `packages/my-package/` +2. Add `package.json` with workspace references to shared configs +3. Set up `src/`, `test/`, `tsconfig.json`, `rollup.config.mjs` +4. Update root-level documentation + +### Common Patterns + +**Package Exports Structure**: +```json +{ + "exports": { + ".": { + "types": "./dist/types/index.d.ts", + "import": "./dist/esm/index.js" + }, + "./submodule": { + "types": "./dist/types/submodule.d.ts", + "import": "./dist/esm/submodule.js" + } + } +} +``` + +**Internal vs Public APIs**: +- Some packages export `/internal` paths for use by other packages +- These should NOT be used by external consumers + +## ๐Ÿ” Security & OAuth2 Concepts + +### DPoP Support +The SDKs support **DPoP (Demonstrating Proof-of-Possession)** for enhanced security in OAuth2 flows. See package-specific documentation for implementation details. +Reference: https://datatracker.ietf.org/doc/html/rfc9449 + +### Token Types +- **Access Token**: Used for API authorization +- **ID Token**: Contains user identity information (JWT) +- **Refresh Token**: Used to obtain new access tokens + +## ๐ŸŽฏ Sample Applications + +Sample applications are located in `e2e/apps/`: + +### Redirect Model (`e2e/apps/redirect-model`) +Demonstrates Authorization Code Flow using the redirect model. + +### Token Broker (`e2e/apps/token-broker`) +Demonstrates obtaining an "all-scoped" token and using it to request downscoped access tokens. + +### Running Samples +See [e2e/apps/README.md](./e2e/apps/README.md) for setup instructions, including: +- Configuring Okta applications +- Setting up `testenv` file with credentials +- Running the development server + +## ๐Ÿ“– External Resources + +- **API Documentation**: https://okta-client-js.netlify.app/ +- **Repository**: https://github.com/okta/okta-client-javascript +- **Issues**: https://github.com/okta/okta-client-javascript/issues + +## ๐Ÿ’ก Working with Claude + +When working on this codebase with Claude: + +1. **For cross-package changes**: Reference this root `claude.md` for architecture and shared patterns +2. **For package-specific work**: Reference the individual package's `claude.md` file +3. **For OAuth2 flows**: Start with `@okta/oauth2-flows` +4. **For token management**: Look at `@okta/spa-platform` +5. **For core abstractions**: Dive into `@okta/auth-foundation` + +### Key Files to Reference +- `turbo.json` - Turborepo task pipeline configuration +- `package.json` (root) - Workspace configuration +- Individual `package.json` files - Package-specific configs and dependencies + +--- + +**Version**: 0.6.0 (all packages currently in sync) +**License**: Apache-2.0 +**Maintainer**: jared.perreault@okta.com \ No newline at end of file diff --git a/packages/auth-foundation/claude.md b/packages/auth-foundation/claude.md new file mode 100644 index 0000000..5b22ae3 --- /dev/null +++ b/packages/auth-foundation/claude.md @@ -0,0 +1,256 @@ +# @okta/auth-foundation + +**Package**: `@okta/auth-foundation` +**Version**: 0.6.0 +**Type**: Internal/Foundation Library +**Purpose**: Core foundation library providing OAuth2 primitives, token handling, HTTP abstractions, and platform utilities. + +> ๐Ÿ“Œ **Context**: This is the foundational package on which all other packages in this monorepo depend. Includes some default implementations of interfaces which are not intended for production use. See [root claude.md](../../claude.md) for monorepo architecture. + +## ๐ŸŽฏ Package Overview + +This package provides the building blocks for OAuth2 authentication and authorization. It is NOT designed to be used directly by end-users, but rather as a dependency for higher-level packages like `@okta/oauth2-flows` and `@okta/spa-platform`. + +### What This Package Provides + +- **OAuth2 Core Primitives**: Token structures, credential handling, OAuth2 protocol utilities +- **HTTP Client Abstraction**: Platform-agnostic HTTP client (FetchClient) +- **Token Management**: Token parsing, validation, refresh logic +- **Crypto Utilities**: PKCE, JWT operations, key generation +- **Platform Abstractions**: Browser/Node.js environment detection and utilities +- **Error Handling**: Standardized error types for OAuth2 flows + +## ๐Ÿ“ Source Code Structure + +``` +src/ +โ”œโ”€โ”€ Credential/ # Credential/token credential abstractions +โ”œโ”€โ”€ crypto/ # Cryptographic utilities (PKCE, JWT, etc.) +โ”œโ”€โ”€ errors/ # Error classes and types +โ”œโ”€โ”€ http/ # HTTP client abstractions +โ”œโ”€โ”€ jwt/ # JWT parsing and validation +โ”œโ”€โ”€ oauth2/ # OAuth2 protocol utilities +โ”œโ”€โ”€ platform/ # Service Locator dependency manager +โ”œโ”€โ”€ types/ # TypeScript type definitions +โ”œโ”€โ”€ utils/ # General utilities +โ”œโ”€โ”€ FetchClient.ts # HTTP client implementation +โ”œโ”€โ”€ Token.ts # Token models and operations +โ”œโ”€โ”€ TokenOrchestrator.ts # Token lifecycle orchestration +โ”œโ”€โ”€ core.ts # Core exports WITHOUT platform dependency implementations +โ”œโ”€โ”€ index.ts # Public API exports WITH default platform dependency implementations +โ””โ”€โ”€ internal.ts # Internal API exports (for other packages) +``` + +## ๐Ÿ“ฆ Package Exports + +### Public API (`@okta/auth-foundation`) +```typescript +import { /* public APIs */ } from '@okta/auth-foundation'; +``` + +**Available paths**: +- `.` - Main exports (public API) WITH default platform dependency implementations +- `./core` - Core primitives WITHOUT platform dependency implementations +- `./internal` - Internal APIs (for use by `@okta/oauth2-flows`, `@okta/spa-platform`) + +โš ๏ธ **Note**: The `/internal` export is for use by other packages in this monorepo only. External consumers should NOT use this path. + +## ๐Ÿ”‘ Key Components + +### 1. Token (`Token.ts`) +Represents OAuth2 tokens (access, ID, refresh). + +**Responsibilities**: +- Parse and validate token structures +- Handle token expiration checks +- Extract claims from ID tokens (JWT) +- Token serialization/deserialization + +### 2. FetchClient (`FetchClient.ts`) +Platform-agnostic HTTP client for making authenticated requests. + +**Features**: +- DPoP (Demonstrating Proof-of-Possession) support +- Automatic retry logic +- Platform-specific fetch implementations (browser/Node.js) +- Request/response interceptors + +### 3. TokenOrchestrator (`TokenOrchestrator.ts`) +Coordinates token lifecycle operations. + +**Responsibilities**: +- Token refresh logic +- Token storage coordination +- Handling concurrent token requests + +### 4. Cryptographic Utilities (`crypto/`) +Provides OAuth2-required cryptographic operations. + +**Capabilities**: +- PKCE code verifier/challenge generation +- JWT signing and verification +- Key pair generation +- Random value generation + +### 5. Platform Abstractions (`platform/`) +Platform dependency manager. Follows the Service Locator pattern. + +**Supports**: +- Browser detection +- Node.js detection +- Storage abstraction (localStorage, sessionStorage, in-memory) +- URL parsing and manipulation + +## ๐Ÿ”ง Development + +### Building +```bash +# From package directory +yarn build # Build ESM + types +yarn build:watch # Watch mode + +# Individual steps +yarn build:esm # Rollup bundle +yarn build:types # TypeScript definitions +``` + +### Testing +```bash +yarn test # Run all tests +yarn test:unit # Unit tests only +yarn test:browser # Browser environment tests +yarn test:node # Node.js environment tests +yarn test:watch # Watch mode +``` + +### Linting +```bash +yarn lint +``` + +## ๐Ÿ—๏ธ Architecture Patterns + +### Exports Strategy +This package uses **multi-entry exports** to provide different levels of API access: + +1. **Public API** (`index.ts`): Stable, user-facing APIs +2. **Core API** (`core.ts`): Lower-level primitives +3. **Internal API** (`internal.ts`): For use by other monorepo packages only + +### Platform Agnosticism +Code is written to work in both browser and Node.js environments: +- Use platform abstractions from `platform/` +- Avoid direct DOM or Node.js API calls +- Utilize environment-specific implementations via dependency injection + +### Dependency Injection +Many components accept dependencies as constructor parameters to enable: +- Testing with mocks +- Platform-specific implementations +- Customization by higher-level packages + +## ๐Ÿงช Testing Strategy + +### Test Organization +- **Unit tests**: Co-located with source files in `test/` +- **Environment-specific tests**: Separated via Jest configs +- **Mocking**: Uses Jest mocks and `@repo/jest-helpers` + +### Test Configurations +- `jest.config.js` - Base configuration (runs all tests) +- `jest.browser.config.js` - Browser environment simulation +- `jest.node.config.js` - Node.js environment + +### Writing Tests +```typescript +// Example test structure +describe('Token', () => { + it('should parse access token', () => { + // Arrange + const tokenString = '...'; + + // Act + const token = Token.parse(tokenString); + + // Assert + expect(token.accessToken).toBe('...'); + }); +}); +``` + +## ๐Ÿ”— Integration with Other Packages + +### Used By +- `@okta/oauth2-flows` - Uses token, HTTP, and OAuth2 utilities +- `@okta/spa-platform` - Uses token management, storage, and platform abstractions + +### Consumption Pattern +```typescript +// From @okta/oauth2-flows or @okta/spa-platform +import { Token, FetchClient } from '@okta/auth-foundation'; +import { /* internal APIs */ } from '@okta/auth-foundation/internal'; +``` + +### Internal Consumption Pattern +```typescript +// Internal packages (like @okta/spa-platform) should always import `@okta/auth-foundation/core` +import { Token, FetchClient } from '@okta/auth-foundation/core'; +``` + +## ๐Ÿ“˜ Key Concepts + +### Token Types +- **Access Token**: OAuth2 access token (opaque or JWT) +- **ID Token**: OpenID Connect ID token (always JWT) +- **Refresh Token**: OAuth2 refresh token (opaque) + +### DPoP (Demonstrating Proof-of-Possession) +DPoP binds tokens to a client's cryptographic key, preventing token theft attacks. + +**Flow**: +1. Client generates a key pair +2. Sends public key in DPoP header with token request +3. Receives DPoP-bound access token +4. Must use private key to create DPoP proof for each API request + +**Reference**: https://datatracker.ietf.org/doc/html/rfc9449 + +### PKCE (Proof Key for Code Exchange) +PKCE protects the authorization code flow from interception attacks. + +**Flow**: +1. Generate random `code_verifier` +2. Create `code_challenge` from verifier +3. Send challenge with authorization request +4. Send verifier with token request + +**Reference**: https://datatracker.ietf.org/doc/html/rfc7636 + +## ๐Ÿšจ Common Gotchas + +1. **Side Effects**: The package includes `oktaUserAgent.ts` as a side effect (sets User-Agent header) +2. **Internal APIs**: Do NOT use `/internal` exports outside of this monorepo +3. **ESM Only**: This package is ESM-only (no CommonJS builds) +4. **Platform Detection**: Some features require browser or Node.js - check platform before use + +## ๐Ÿ’ก Working with Claude + +When modifying this package: + +1. **Adding new utilities**: Consider if they belong in `utils/`, `crypto/`, or `platform/` +2. **Changing exports**: Update `package.json` exports AND the corresponding `.ts` file +3. **Platform-specific code**: Use platform abstractions, never direct `window` or `process` access +4. **Breaking changes**: This affects ALL dependent packages - coordinate carefully +5. **Testing**: Always add both browser and Node tests for new features + +### Important Files +- `src/index.ts` - Public API surface +- `src/internal.ts` - Internal API for other packages +- `package.json` - Export paths and peer dependencies +- `rollup.config.mjs` - Build configuration + +--- + +**Dependencies**: None (this is the foundation!) +**Peer Dependencies**: None +**Private**: Yes (not published to npm separately) \ No newline at end of file diff --git a/packages/auth-foundation/package.json b/packages/auth-foundation/package.json index 2b160f8..9cfe345 100644 --- a/packages/auth-foundation/package.json +++ b/packages/auth-foundation/package.json @@ -26,10 +26,6 @@ "types": "./dist/types/core.d.ts", "import": "./dist/esm/core.js" }, - "./client": { - "types": "./dist/types/client.d.ts", - "import": "./dist/esm/client.js" - }, "./internal": { "types": "./dist/types/internal.d.ts", "import": "./dist/esm/internal.js" diff --git a/packages/oauth2-flows/claude.md b/packages/oauth2-flows/claude.md new file mode 100644 index 0000000..36fa8f4 --- /dev/null +++ b/packages/oauth2-flows/claude.md @@ -0,0 +1,343 @@ +# @okta/oauth2-flows + +**Package**: `@okta/oauth2-flows` +**Version**: 0.6.0 +**Type**: Token Acquisition Library +**Purpose**: Implementations of OAuth2 authentication flows for browser/SPA environments. + +> ๐Ÿ“Œ **Context**: This package implements OAuth2 flows using primitives from `@okta/auth-foundation`. See [root claude.md](../../claude.md) for monorepo architecture. + +## ๐ŸŽฏ Package Overview + +This package provides production-ready implementations of OAuth2 authentication and logout flows, designed in a environment-agnostic way. + +### What This Package Provides + +- **Authorization Code Flow**: Full implementation with PKCE support +- **Logout Flows**: Session logout and token revocation +- **Redirect Handling**: Parse OAuth2 redirect callbacks +- **Authentication Transactions**: Stateful authentication flow management +- **DPoP Support**: Demonstrating Proof-of-Possession for enhanced security + +### What This Package Does NOT Provide + +- Token storage/management (see `@okta/spa-platform`) +- Token lifecycle management (renewal, etc.) +- Environment specific implementation details +- Making authenticated API requests + +## ๐Ÿ“ Source Code Structure + +``` +src/ +โ”œโ”€โ”€ AuthorizationCodeFlow/ # Authorization Code Flow implementation +โ”‚ โ”œโ”€โ”€ AuthorizationCodeFlow.ts +โ”‚ โ”œโ”€โ”€ AuthorizationCodeFlowOptions.ts +โ”‚ โ””โ”€โ”€ (related utilities) +โ”œโ”€โ”€ SessionLogoutFlow/ # Session logout implementation +โ”‚ โ”œโ”€โ”€ SessionLogoutFlow.ts +โ”‚ โ””โ”€โ”€ (related utilities) +โ”œโ”€โ”€ AuthenticationFlow.ts # Base authentication flow abstraction +โ”œโ”€โ”€ AuthTransaction.ts # Authentication transaction state +โ”œโ”€โ”€ LogoutFlow.ts # Base logout flow abstraction +โ”œโ”€โ”€ types.ts # TypeScript type definitions +โ””โ”€โ”€ index.ts # Public API exports +``` + +## ๐Ÿ“ฆ Package Exports + +```typescript +import { AuthorizationCodeFlow, SessionLogoutFlow } from '@okta/oauth2-flows'; +``` + +**Main exports**: +- `AuthorizationCodeFlow` - Authorization Code Flow with PKCE +- `SessionLogoutFlow` - Logout flow implementation +- `AuthenticationFlow` - Base class for authentication flows +- `AuthTransaction` - Transaction state management +- Related types and interfaces + +## ๐Ÿ”‘ Key Components + +### 1. AuthorizationCodeFlow + +Implements the OAuth2 Authorization Code Flow with PKCE. + +**Usage Pattern**: +```typescript +import { AuthorizationCodeFlow } from '@okta/oauth2-flows'; +import { FetchClient } from '@okta/auth-foundation'; + +const flow = new AuthorizationCodeFlow({ + issuer: 'https://dev-123456.okta.com/oauth2/default', + clientId: 'your-client-id', + redirectUri: 'http://localhost:8080/login/callback', + scopes: ['openid', 'profile', 'email'], +}); + +// Start authentication +const { url, codeVerifier, state } = await flow.start(); +// Store codeVerifier and state for callback handling +window.location.href = url; // Redirect to Okta + +// Handle callback (after redirect back) +const tokens = await flow.handleCallback({ + code: '...', // from URL params + state: '...', // from URL params + codeVerifier: '...', // retrieved from storage +}); +``` + +**Features**: +- PKCE code challenge generation +- State parameter for CSRF protection +- Nonce for ID token validation +- DPoP support (optional) + +**References:** +- Authorization Code Flow: https://datatracker.ietf.org/doc/html/rfc6749#section-4.1 +- PKCE: https://datatracker.ietf.org/doc/html/rfc7636 +- DPoP: https://datatracker.ietf.org/doc/html/rfc9449 + +### 2. SessionLogoutFlow + +Implements OAuth2 logout functionality. + +**Usage Pattern**: +```typescript +import { SessionLogoutFlow } from '@okta/oauth2-flows'; + +const logoutFlow = new SessionLogoutFlow({ + issuer: 'https://dev-123456.okta.com/oauth2/default', + clientId: 'your-client-id', + postLogoutRedirectUri: 'http://localhost:8080/logout', +}); + +// Start logout +const { url } = await logoutFlow.start({ + idToken: 'user-id-token', // Optional: for id_token_hint +}); + +window.location.href = url; // Redirect to logout endpoint +``` + +**References** +- RP-Initiated Logout: https://openid.net/specs/openid-connect-session-1_0-17.html + +### 3. AuthTransaction + +Manages the state of an authentication transaction across redirects. + +**Responsibilities**: +- Track authentication progress +- Store flow-specific parameters (PKCE verifier, state, nonce) +- Handle transaction lifecycle + +### 4. AuthenticationFlow (Base Class) + +Abstract base class for all authentication flows. + +**Purpose**: +- Defines common flow interface +- Provides shared flow utilities +- Enforces consistent patterns + +## ๐Ÿ”ง Development + +### Building +```bash +yarn build # Build ESM + types +yarn build:watch # Watch mode +yarn build:esm # Rollup bundle only +yarn build:types # TypeScript definitions only +``` + +### Testing +```bash +yarn test # Run all tests +yarn test:unit # Unit tests +yarn test:browser # Browser environment tests +yarn test:node # Node.js environment tests +yarn test:watch # Watch mode +``` + +### Linting +```bash +yarn lint +``` + +## ๐Ÿ—๏ธ Architecture Patterns + +### Flow Lifecycle + +1. **Initialization**: Create flow instance with configuration +2. **Start**: Generate authorization URL with security parameters +3. **Redirect**: User redirected to Okta for authentication +4. **Callback**: Handle redirect back with authorization code +5. **Token Exchange**: Exchange code for tokens using stored verifier +6. **Complete**: Return tokens to caller + +### Security Features + +**PKCE (Proof Key for Code Exchange)**: +- Prevents authorization code interception +- Flow generates random `code_verifier` +- Sends `code_challenge` (SHA-256 hash) with auth request +- Sends `code_verifier` with token request +- Reference: https://datatracker.ietf.org/doc/html/rfc7636 + +**State Parameter**: +- CSRF protection +- Generated per-transaction +- Validated on callback + +**Nonce**: +- Replay attack prevention for ID tokens +- Embedded in ID token claims +- Validated after token exchange + +**DPoP (optional)**: +- Binds tokens to cryptographic key +- Prevents token theft/replay attacks +- Reference: https://datatracker.ietf.org/doc/html/rfc9449 + +### Dependency Injection + +Flows depend on `@okta/auth-foundation` for: +- HTTP client (`FetchClient`) +- Token parsing and validation +- Cryptographic utilities +- OAuth2 protocol helpers + +## ๐Ÿงช Testing Strategy + +### Test Organization +Tests are located in `test/` and organized by component: +- Flow implementations +- Transaction state management +- Security parameter generation +- Error handling + +### Browser vs Node Tests +- **Browser tests**: Focus on browser-specific APIs (URL parsing, redirect handling) +- **Node tests**: Focus on token exchange and API interactions + +### Mocking Strategy +Uses `@repo/jest-helpers` to mock: +- HTTP responses from OAuth2 endpoints +- Browser APIs (location, localStorage) +- Crypto operations + +## ๐Ÿ”— Integration with Other Packages + +### Dependencies +- `@okta/auth-foundation` (peer dependency): Core primitives + +### Used By +- `@okta/spa-platform`: Higher-level platform SDK that wraps these flows with token management + +### Typical Usage Context +```typescript +// In @okta/spa-platform or user application +import { AuthorizationCodeFlow } from '@okta/oauth2-flows'; +import { FetchClient } from '@okta/auth-foundation'; + +// Flow handles authentication +// Platform SDK handles token storage/management +``` + +## ๐Ÿ“˜ Key Concepts + +### OAuth2 Authorization Code Flow + +**Standard Flow**: +1. Client redirects user to authorization server +2. User authenticates and consents +3. Authorization server redirects back with code +4. Client exchanges code for tokens + +**With PKCE**: +- Adds code_verifier/code_challenge mechanism +- Required for public clients (SPAs) +- Prevents authorization code interception + +### Redirect-Based Authentication + +All flows in this package use browser redirects: +- User leaves your app temporarily +- Authenticates at Okta +- Returns to your app with authorization code +- App exchanges code for tokens + +**State Management Challenge**: App must preserve state across redirects +- Store PKCE verifier before redirect +- Retrieve verifier on callback +- Validate state parameter matches + +### DPoP Flow + +When DPoP is enabled: +1. Client generates key pair +2. Creates DPoP proof (signed JWT) +3. Sends proof in `DPoP` header +4. Receives DPoP-bound token +5. Must use same key for subsequent requests + +## ๐Ÿšจ Common Gotchas + +1. **DPoP Binding**: If using DPoP, same key must be used for all requests with that token +2. **No Token Management**: This package only acquires tokens - use `@okta/spa-platform` for management + +## ๐ŸŽฏ Common Use Cases + +### Standard SPA Login +```typescript +// 1. Start flow +const flow = new AuthorizationCodeFlow(config); +const url = await flow.start(); + +// 2. Store for later +const transaction = new AuthTransaction(flow.context); +await transaction.save(); + +// 3. Redirect +window.location.href = url; + +// 4. Handle callback (in callback route) +const { token, context } = await signInFlow.resume(window.location.href); +``` + +### Logout +```typescript +const logoutFlow = new SessionLogoutFlow(config); +const { url } = await logoutFlow.start({ idToken }); +window.location.href = url; +``` + +## ๐Ÿ’ก Working with Claude + +When modifying this package: + +1. **Adding new flows**: Extend `AuthenticationFlow` or `LogoutFlow` base classes +2. **Security parameters**: Never skip PKCE, state, or nonce validation +3. **Browser APIs**: All code must work in browser environment +4. **Node APIs**: All code must work in NodeJS environment +5. **Error handling**: Use error types from `@okta/auth-foundation/errors` +6. **Testing**: Add tests for both happy path and error scenarios + +### Important Files +- `src/AuthorizationCodeFlow/` - Main authentication flow +- `src/SessionLogoutFlow/` - Logout implementation +- `src/AuthTransaction.ts` - Transaction state management +- `package.json` - Peer dependencies on `@okta/auth-foundation` + +### Related Documentation +- OAuth2 spec: https://oauth.net/2/ +- PKCE spec: https://oauth.net/2/pkce/ +- OpenID Connect: https://openid.net/connect/ + +--- + +**Peer Dependencies**: `@okta/auth-foundation@*` +**Target Environment**: Browser (SPA) +**Private**: Yes (not published separately) \ No newline at end of file diff --git a/packages/spa-platform/claude.md b/packages/spa-platform/claude.md new file mode 100644 index 0000000..841f19b --- /dev/null +++ b/packages/spa-platform/claude.md @@ -0,0 +1,562 @@ +# @okta/spa-platform + +**Package**: `@okta/spa-platform` +**Version**: 0.6.0 +**Type**: Platform/Management Library +**Purpose**: High-level SPA platform SDK for token lifecycle management, storage, and authenticated requests. + +> ๐Ÿ“Œ **Context**: This is the highest-level package in the monorepo, designed for direct use by SPA applications. See [root claude.md](../../claude.md) for monorepo architecture. + +## ๐ŸŽฏ Package Overview + +This package provides a complete, production-ready SDK for Single Page Applications to handle OAuth2 authentication, token management, and authenticated API requests. + +### What This Package Provides + +- **Credential Management**: High-level `Credential` abstraction for tokens with automatic refresh +- **Token Orchestrators**: Specialized orchestrators for different authentication patterns + - `AuthorizationCodeFlowOrchestrator` - Standard SPA authentication flow + - `HostOrchestrator` - Multi-app/iframe token delegation architecture +- **Browser Token Storage**: `BrowserTokenStorage` for secure token persistence +- **OAuth2 Client**: Browser-specific OAuth2 client with tab synchronization +- **Authenticated FetchClient**: HTTP client with automatic token injection +- **Flow Wrappers**: Browser-optimized versions of `@okta/oauth2-flows` +- **DPoP Support**: Browser-specific DPoP key management and signing + +### What This Package Does NOT Provide + +- Server-side authentication (this is SPA-only) +- Native mobile app support +- OAuth2 flow implementations (delegates to `@okta/oauth2-flows`) + +## ๐Ÿ“ Source Code Structure + +``` +src/ +โ”œโ”€โ”€ Credential/ # Credential management and abstractions +โ”œโ”€โ”€ FetchClient/ # HTTP client with token injection +โ”œโ”€โ”€ flows/ # Flow integrations (wrapping @okta/oauth2-flows) +โ”œโ”€โ”€ orchestrators/ # Token lifecycle orchestrators +โ”œโ”€โ”€ platform/ # Browser-specific platform utilities +โ”œโ”€โ”€ utils/ # Utility functions +โ””โ”€โ”€ index.ts # Public API exports +``` + +## ๐Ÿ“ฆ Package Exports + +```typescript +import { /* platform APIs */ } from '@okta/spa-platform'; +``` + +**Main exports**: +- Credential managers +- Authenticated fetch client +- Token orchestrators +- Browser-specific utilities +- Integrated flow wrappers + +## ๐Ÿ”‘ Key Components + +### 1. Credential (`Credential/Credential.ts`) + +High-level abstraction representing an OAuth2 credential (token + metadata). + +**Key Features**: +- Wraps a `Token` with lifecycle management +- Automatic refresh via `refreshIfNeeded()` +- Static methods for finding and storing credentials +- Integration with `BrowserTokenStorage` + +**Usage**: +```typescript +import { Credential } from '@okta/spa-platform'; + +// Store a credential +const credential = await Credential.store(token, ['app:main']); + +// Find credentials by filter +const credentials = await Credential.find((meta) => + meta.scopes?.includes('profile') +); + +// Refresh if needed (checks expiration automatically) +await credential.refreshIfNeeded(); + +// Access the token +const accessToken = credential.token.accessToken; + +// Remove credential +await credential.remove(); +``` + +### 2. AuthorizationCodeFlowOrchestrator (`orchestrators/AuthorizationCodeFlowOrchestrator.ts`) + +Implements `TokenOrchestrator` for standard SPA authentication flows. + +**Responsibilities**: +- Manage authentication flow lifecycle +- Store and retrieve credentials +- Handle redirects and callbacks +- Automatic token refresh +- Event emission for UI integration + +**Usage**: +```typescript +import { + AuthorizationCodeFlowOrchestrator, + AuthorizationCodeFlow +} from '@okta/spa-platform'; + +// Create flow +const flow = new AuthorizationCodeFlow({ + issuer: 'https://dev-123456.okta.com/oauth2/default', + clientId: 'your-client-id', + redirectUri: 'http://localhost:8080/callback', + scopes: ['openid', 'profile', 'offline_access'], +}); + +// Create orchestrator +const orchestrator = new AuthorizationCodeFlowOrchestrator(flow, { + avoidPrompting: false, + emitBeforeRedirect: true, + getOriginalUri: () => window.location.pathname, + tags: ['main-app'], +}); + +// Listen for events +orchestrator.on('login_prompt_required', ({ done, params }) => { + console.log('Login required', params); + done(); // Call when ready to proceed +}); + +orchestrator.on('error', ({ error }) => { + console.error('Auth error', error); +}); + +// Get token (will redirect if not authenticated) +const token = await orchestrator.getToken({ + scopes: ['openid', 'profile'], +}); + +// Resume flow after redirect callback +await orchestrator.resumeFlow(window.location.href); +``` + +### 3. HostOrchestrator (`orchestrators/HostOrchestrator/`) + +Advanced multi-app architecture for token delegation between host app and sub-apps (e.g., iframes). + +**Components**: +- `HostOrchestrator.Host` - Receives and fulfills token requests from sub-apps +- `HostOrchestrator.SubApp` - Delegates token requests to host +- `HostOrchestrator.ProxyHost` - Adapts any orchestrator into a host + +**Use Cases**: +- Micro-frontend architectures +- Main app + embedded iframes +- Shared authentication across multiple SPAs + +**Usage - Host App**: +```typescript +import { + HostOrchestrator, + AuthorizationCodeFlowOrchestrator, + AuthorizationCodeFlow +} from '@okta/spa-platform'; + +// Create standard orchestrator +const flow = new AuthorizationCodeFlow({ /* config */ }); +const orchestrator = new AuthorizationCodeFlowOrchestrator(flow); + +// Wrap in ProxyHost to enable delegation +const host = new HostOrchestrator.ProxyHost('main-app', orchestrator); + +// Host automatically handles token requests from sub-apps +``` + +**Usage - Sub-App (iframe)**: +```typescript +import { HostOrchestrator } from '@okta/spa-platform'; + +// Create SubApp orchestrator that delegates to host +const subApp = new HostOrchestrator.SubApp({ + scopes: ['openid', 'profile'], + targetOrigin: 'https://main-app.example.com', +}); + +// Use like any orchestrator +const token = await subApp.getToken(); +``` + +### 4. BrowserTokenStorage (`Credential/TokenStorage.ts`) + +Browser-specific token storage implementation. + +**Features**: +- Supports `localStorage` and `sessionStorage` +- Automatic serialization/deserialization +- Tab-synchronized updates +- Secure storage practices +- Encrypts tokens at-rest + +### 5. OAuth2Client (`platform/OAuth2Client.ts`) + +Browser-specific OAuth2 client extending `@okta/auth-foundation` client. + +**Key Feature**: +- **Tab-Synchronized Refresh**: Uses `SynchronizedResult` to coordinate token refresh across browser tabs +- Only one tab performs refresh, others wait for result +- Prevents duplicate refresh requests + +**How It Works**: +```typescript +// Multiple tabs call refresh simultaneously +// Only one actually makes the request +// Others receive the result via storage events +const refreshedToken = await client.refresh(token); +``` + +### 6. FetchClient (`FetchClient/`) + +Authenticated HTTP client for making API requests. + +**Features**: +- Automatic access token injection in `Authorization` header +- DPoP proof generation (if token is DPoP-bound) +- Token refresh on 401 responses +- Platform-specific fetch implementation + +**Usage**: +```typescript +import { + FetchClient, + Credential, + AuthorizationCodeFlowOrchestrator, + AuthorizationCodeFlow +} from '@okta/spa-platform'; + +// Create standard orchestrator +const flow = new AuthorizationCodeFlow({ /* config */ }); +const orchestrator = new AuthorizationCodeFlowOrchestrator(flow); + +const client = new FetchClient(orchestrator); + +// Automatically adds Authorization header +const response = await client.fetch('https://api.example.com/data'); +const data = await response.json(); +``` + +### 7. Flow Wrappers (`flows/`) + +Browser-optimized versions of `@okta/oauth2-flows`: +- `AuthorizationCodeFlow` - Extends base flow with browser storage integration +- `SessionLogoutFlow` - Extends base logout with browser cleanup + +These wrap the core flows and add browser-specific functionality like: +- Automatic state/verifier storage in sessionStorage +- Callback parsing from `window.location` +- Browser redirect handling + + +## ๐Ÿ”ง Development + +### Building +```bash +yarn build # Build ESM + types +yarn build:watch # Watch mode +yarn build:esm # Rollup bundle only +yarn build:types # TypeScript definitions only +``` + +### Testing +```bash +yarn test # Run all tests +yarn test:unit # Unit tests +yarn test:watch # Watch mode +``` + +### Linting +```bash +yarn lint +``` + +## ๐Ÿ—๏ธ Architecture Patterns + +### Layered Architecture + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ SPA Application Code โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ @okta/spa-platform โ”‚ +โ”‚ โ€ข Token Orchestration โ”‚ +โ”‚ โ€ข Storage Management โ”‚ +โ”‚ โ€ข Tab Synchronization โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ @okta/oauth2-flows โ”‚ +โ”‚ โ€ข Authorization Code Flow โ”‚ +โ”‚ โ€ข Logout Flows โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ @okta/auth-foundation โ”‚ +โ”‚ โ€ข Token Primitives โ”‚ +โ”‚ โ€ข HTTP Client โ”‚ +โ”‚ โ€ข Crypto Utils โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### Credential-Based Architecture + +This package uses a **Credential** abstraction rather than raw tokens: + +``` +Application Code + โ†“ +Credential (high-level) + โ†“ +Token (from @okta/auth-foundation) + โ†“ +OAuth2 protocol primitives +``` + +**Benefits**: +- Automatic refresh management +- Storage abstraction +- Metadata (scopes, tags) attached to tokens +- Query/filter capabilities + +### Orchestrator Pattern + +`TokenOrchestrator` (from `@okta/auth-foundation`) defines the contract: +- `getToken(params)` - Get or request a token +- Event emission for lifecycle hooks + +This package provides implementations: +- `AuthorizationCodeFlowOrchestrator` - Standard SPA flow +- `HostOrchestrator.Host` - Token delegation receiver +- `HostOrchestrator.SubApp` - Token delegation requestor + +### Tab Synchronization + +**Challenge**: Multiple browser tabs need consistent auth state + +**Solution**: +- `BrowserTokenStorage` uses storage events +- `SynchronizedResult` prevents duplicate operations +- `OAuth2Client.prepareRefreshRequest` coordinates refresh across tabs + +**Flow**: +1. Tab A starts token refresh +2. Tab A creates `SynchronizedResult` with unique key +3. Tab A stores "pending" state in localStorage +4. Tab B attempts refresh with same key +5. Tab B sees "pending", waits for result +6. Tab A completes, stores result +7. Tab B receives result via storage event + +## ๐Ÿงช Testing Strategy + +### Test Organization +Tests are co-located in `test/` and focus on: +- Token renewal logic +- Tab synchronization +- Storage operations +- HTTP client behavior +- Error handling + +### Browser Environment +All tests simulate browser environment: +- Mock `localStorage` and `sessionStorage` +- Mock `BroadcastChannel` +- Mock `fetch` API + +### Mocking Strategy +Uses `@repo/jest-helpers` to mock: +- Browser storage APIs +- Token endpoints +- Time (for expiration testing) + +## ๐Ÿ”— Integration with Other Packages + +### Dependencies +- `@okta/auth-foundation` (peer dependency): Core primitives +- `@okta/oauth2-flows` (optional peer dependency): OAuth2 flows + +### Typical Usage +```typescript +import { + FetchClient, + Credential, + AuthorizationCodeFlowOrchestrator, + AuthorizationCodeFlow +} from '@okta/spa-platform'; + +// Create standard orchestrator +const flow = new AuthorizationCodeFlow({ /* config */ }); +const orchestrator = new AuthorizationCodeFlowOrchestrator(flow); + +const client = new FetchClient(orchestrator); + +// Automatically adds Authorization header +const response = await client.fetch('https://api.example.com/data'); +const data = await response.json(); +``` + +## ๐Ÿ“˜ Key Concepts + +### Token Renewal + +**Why?** +- Access tokens are short-lived (typically 1 hour) +- Must be renewed before expiry to avoid interruptions + +**How?** +- Orchestrator checks expiration periodically +- Uses refresh token to get new access token +- Updates storage atomically +- Notifies all tabs of new tokens + +### Cross-Tab Authentication + +**Challenges**: +- User might authenticate in Tab A +- Tab B needs to know about authentication +- User might logout in Tab A +- Tab B should also logout + +**Solution**: +- Tab sync via storage events or BroadcastChannel +- Shared token storage +- Coordinated logout + +### Secure Token Storage + +**Best Practices**: +1. **Never store tokens in cookies** (CSRF risk) +2. **Encrypt at rest** (XSS protection) +3. **Use localStorage** only if persistence is required +4. **Never log tokens** (security risk) +5. **Clear storage on logout** + +### DPoP in SPA Context + +**DPoP with SPAs**: +- Private key must be stored securely +- Use Web Crypto API for key generation +- Store (non-extractable) key in IndexedDB (not localStorage) +- Same key must be used for token and API requests + +## ๐Ÿšจ Common Gotchas + +1. **Storage Permissions**: Some browsers block storage in iframes or incognito mode +2. **Token Expiry**: Orchestrator must start BEFORE tokens expire +3. **Tab Sync Delay**: Storage events have slight delay - don't rely on instant sync +4. **Memory Leaks**: Always call `orchestrator.stop()` when component unmounts +5. **DPoP Keys**: Must persist DPoP keys across page reloads (use IndexedDB) + +## ๐ŸŽฏ Common Use Cases + +### Basic SPA Authentication +```typescript +import { + AuthorizationCodeFlowOrchestrator, + AuthorizationCodeFlow, + Credential +} from '@okta/spa-platform'; + +// 1. Setup +const flow = new AuthorizationCodeFlow({ + issuer: 'https://dev-123456.okta.com/oauth2/default', + clientId: 'client-id', + redirectUri: 'http://localhost:8080/callback', + scopes: ['openid', 'profile', 'offline_access'], +}); + +const orchestrator = new AuthorizationCodeFlowOrchestrator(flow); + +// 2. Check if already authenticated +const token = await orchestrator.getToken(); +// If not authenticated, this redirects to Okta + +// 3. Handle callback (in /callback route) +if (window.location.pathname === '/callback') { + await orchestrator.resumeFlow(); + // Redirect to originalUri or home +} + +// 4. Make API requests +const credential = (await Credential.find(() => true))[0]; +await credential.refreshIfNeeded(); +const accessToken = credential.token.accessToken; +``` + +### Logout +```typescript +import { SessionLogoutFlow } from '@okta/oauth2-flows'; + +// Logout from Okta +const logoutFlow = new SessionLogoutFlow(config); +const credential = await Credential.getDefault(); +const url = await logoutFlow.start(credential.token?.idToken); +window.location.href = url; +``` + +### Multi-App Architecture +```typescript +// Host App (main-app.example.com) +import { + HostOrchestrator, + AuthorizationCodeFlowOrchestrator, + AuthorizationCodeFlow +} from '@okta/spa-platform'; + +const flow = new AuthorizationCodeFlow({ /* config */ }); +const orchestrator = new AuthorizationCodeFlowOrchestrator(flow); +const host = new HostOrchestrator.ProxyHost('main', orchestrator); + +// Sub-App (iframe in main app) +import { HostOrchestrator } from '@okta/spa-platform'; + +const subApp = new HostOrchestrator.SubApp({ + scopes: ['openid', 'profile'], + targetOrigin: 'https://main-app.example.com', +}); + +const token = await subApp.getToken(); // Requests from host +``` + +## ๐Ÿ’ก Working with Claude + +When modifying this package: + +1. **Browser-Only**: All code must work in browser environment +2. **Storage Access**: Always handle storage errors gracefully (incognito, permissions) +3. **Tab Sync**: Test with multiple tabs/windows open +4. **Token Security**: Never log or expose tokens +5. **Orchestrator Lifecycle**: Always clean up (call `stop()`) to prevent memory leaks + +### Important Files +- `src/Credential/` - Credential abstraction and storage +- `src/orchestrators/` - Token orchestrators +- `src/flows/` - Browser-specific flow wrappers +- `src/platform/OAuth2Client.ts` - Tab-synchronized refresh +- `src/index.ts` - Public API surface + +### Related Documentation +- OAuth2 Token Refresh: https://oauth.net/2/refresh-tokens/ +- Web Storage API: https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API +- BroadcastChannel API: https://developer.mozilla.org/en-US/docs/Web/API/BroadcastChannel + +--- + +**Peer Dependencies**: +- `@okta/auth-foundation@*` (required) +- `@okta/oauth2-flows@*` (optional) + +**Target Environment**: Browser (SPA only) +**Private**: Yes (not published separately) \ No newline at end of file