npm package: @identityprovider/client
Description: A lightweight TypeScript SDK for integrating with an OpenID Connect-compliant Identity Provider. Supports PKCE-based authorization code flow, token exchange, silent authentication, and logout. Designed to work with any frontend application, with optional 
Author: Cornel Bastiaanse
Latest version: 2.3.1
Deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.

# @identityprovider/client

A lightweight and framework-agnostic TypeScript client for interacting with an OpenID Connect (OIDC) Identity Provider using Authorization Code Flow with PKCE.

## ✨ Features

- Full PKCE-based authorization
- `authorize()` + `silentAuthorize()` for login flows
- Handles `state`, `nonce`, `codeVerifier` generation, storage, & validation
- Uses `sessionStorage` for short-term, tab-isolated security artifacts
- Token exchange with direct IdP call or custom local API (ideal for secure `HttpOnly` cookies)
- Configurable PKCE cleanup delay for React/SPAs
- Robust error handling with custom error types
- Framework-agnostic: works with React, Vue, Svelte, plain JavaScript

---

## 🚀 Installation

```bash
npm install @identityprovider/client
```

## 🧱 Usage

### 1. Instantiate the client

```typescript
import { IdentityProviderClient } from "@identityprovider/client";

// cleanupDelayMs (optional): default is 5000 ms (5 seconds)
const client = new IdentityProviderClient(
    "https://idp.example.com",     // Base URL of the Identity Provider
    "your-client-id",              // OIDC client_id
    5000                           // Optional: PKCE cleanup delay in milliseconds
);
```

### 2. Start the authorization flow

```typescript
await client.authorize({
  redirectUri: "https://your-app.com/callback",
  scope: "openid profile email"
});
```

This will redirect the user to the Identity Provider's login screen.

### 3. Handle the callback and exchange tokens

✅ In-memory/localStorage token handling:

```typescript
const response = await client.token({ 
    code,
    redirectUri: "https://your-app.com/callback"
});
// response.accessToken, response.idToken, etc.
```
You do not need to pass any options.

🔐 Secure HttpOnly cookie-based flow (recommended):
```typescript
await client.token({
  code,
  redirectUri: "https://your-app.com/callback",
  tokenExchangeHandler: async (request) => {
    const res = await fetch("/api/connect/token", {
      method: "POST",
      body: JSON.stringify(request),
      headers: {
        "Content-Type": "application/json"
      },
      credentials: "include" // important!
    });

    if (!res.ok) {
      throw new Error("Token exchange failed");
    }

    return await res.json();
  }
});
```
Your backend should then:

1. Call the Identity Provider's `/connect/token`
2. Set secure HttpOnly cookies (access and refresh tokens)

## 🔄 Silent Authorization

You can use the `silentAuthorize` method to refresh tokens without user interaction. This is useful for maintaining a session without prompting the user.

```typescript
const code = await client.silentAuthorize({ scope: "openid profile email" });
```

This will:
- Loads an invisible iframe
- Performs a `prompt=none` flow
- Resolves with an authorization code if still authenticated
- Throws `SilentAuthorizationError` if not

## 🚪 Logout

Use the `logout()` method to log out the user by redirecting them to the Identity Provider’s logout endpoint.

```typescript
client.logout({ postLogoutRedirectUri: "https://your-app.com/logout" });
```

## ⚙️ Advanced Options

### `cleanupDelayMs` (constructor)

Controls how long to retain PKCE-related values (`state`, `nonce`, `code_verifier`) in `sessionStorage` after a successful token exchange.

```typescript
new IdentityProviderClient("https://idp", "clientId", 7000); // Keep values for 7s
```

Recommended: at least 3–5 seconds for React dev mode double renders.

## ❌ Typed Error Handling

All client errors extend `Error` and can be caught using `instanceof`:
- `StateMismatchError`
- `CodeVerifierMissingError`
- `NonceMissingError`
- `IdTokenMissingError`
- `TokenExchangeError`
- `SilentAuthorizationError`

Example:

```typescript
try {
  await client.token();
} catch (err) {
  if (err instanceof StateMismatchError) {
    alert("State does not match — possible CSRF!");
  }
}
```

## 🛡️ Security Best Practices

- Always validate `state` and `nonce` (the client does this for you)
- Prefer using `HttpOnly` cookies via your own API
- Set `SameSite=Strict` or `Lax` on cookies where appropriate
- Avoid persisting tokens in localStorage

## 📦 License
MIT