npm package: @identityprovider/servicestack
Description: A lightweight wrapper for @identityprovider/client that integrates with ServiceStack's JsonServiceClient. Simplifies secure token exchange using your local API while maintaining full support for OpenID Connect flows, PKCE, and ID token validation.
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/servicestack

A lightweight wrapper for [`@identityprovider/client`](https://www.npmjs.com/package/@identityprovider/client) that seamlessly integrates with [ServiceStack](https://docs.servicestack.net/) via its `JsonServiceClient`.

This SDK is designed to simplify OpenID Connect (OIDC) authentication and token management for applications that already use ServiceStack. It provides a unified client with built-in support for:

## ✨ Features

- Authorization Code Flow with PKCE
- `silentAuthorize()` via invisible iframe (`prompt=none`)
- Secure logout with `postLogoutRedirectUri` and `idTokenHint`
- Session-scoped PKCE values stored in `sessionStorage`
- Configurable PKCE cleanup delay (handles React double-render issues)
- Optional integration with your backend to manage `HttpOnly` cookie-based tokens
- Full ID token validation
- Seamless drop-in for `JsonServiceClient-based` apps

## 🚀 Installation

```bash
npm install @identityprovider/servicestack
```

You must also install its peer dependencies:

```bash
npm install @identityprovider/client @servicestack/client
```

## 🧱 Usage

```typescript
import { ServiceStackClient } from "@identityprovider/servicestack";

const client = new ServiceStackClient(
    "https://my-app.com",                 // Your app's API base URL
    "https://identity.mycompany.com",     // Your IdP's base URL
    "my-client-id",                       // OIDC client_id
    5000                                  // Optional: PKCE cleanup delay (ms)
);

// Start login
await client.authorize({
    redirectUri: "https://my-app.com/callback",
    scope: "openid profile email"
});

// Silent login if user is already authenticated
const code = await client.silentAuthorize({
    scope: "openid profile email"
});

// Exchange the code for tokens
const tokens = await client.token({
    code,
    redirectUri: "https://my-app.com/callback"
});

// Log out the user
client.logout({
    postLogoutRedirectUri: "https://my-app.com/logout"
});
```

## 🔄 PKCE Session Storage

PKCE `state`, `nonce`, and `code_verifier` values are stored in `sessionStorage` and automatically cleared after a *successful token exchange*, using a delay (default: 5 seconds) to support React’s double-rendering behavior.

You can customize this delay by passing a fourth argument to the constructor:

```typescript
const client = new ServiceStackClient(apiUrl, idpUrl, clientId, 7000); // 7 seconds
```

## 🔐 HttpOnly Token Flow
If you’re managing tokens via secure `HttpOnly` cookies (recommended), pass a `tokenExchangeHandler` to the `.token()` method:

```typescript
await client.token({
    code,
    redirectUri: "https://my-app.com/callback",
    tokenExchangeHandler: async (request) => {
        const res = await fetch("/api/token", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify(request),
            credentials: "include"
        });

        if (!res.ok) throw new Error("Token exchange failed");

        return await res.json();
    }
});
```

## 🛠️ ServiceStack-Specific Features

Because this client extends `JsonServiceClient`, you can use ServiceStack features like:

```typescript
client.enableAutoRefreshToken = true;

client.onAuthenticationRequired = async () => {
    await client.authorize({ redirectUri: "https://my-app.com/callback" });
};

client.bearerToken = tokens.accessToken; // If you manage tokens manually
client.refreshTokenUri = "/api/refresh"; // If you use custom refresh endpoint
```

## 🧪 Typed Error Handling

This client surfaces all errors from `@identityprovider/client`, including:
- `StateMismatchError`
- `CodeVerifierMissingError`
- `NonceMissingError`
- `IdTokenMissingError`
- `TokenExchangeError`
- `SilentAuthorizationError`

Handle them with `instanceof`:

```typescript
try {
  await client.token();
} catch (e) {
  if (e instanceof TokenExchangeError) {
    console.error("Token error:", e.message);
  }
}
```

## 🛡️ Security Best Practices

- Always use `state` and `nonce` (handled automatically)
- Prefer `HttpOnly` cookie storage over in-memory access tokens
- Use `SameSite=Lax` or `Strict` for session cookies
- Avoid storing tokens in `localStorage` or sharing across tabs

## 📦 License
MIT