2.3.1 • Published 5 months ago

@identityprovider/servicestack v2.3.1

Weekly downloads
-
License
MIT
Repository
github
Last release
5 months ago

@identityprovider/servicestack

A lightweight wrapper for @identityprovider/client that seamlessly integrates with ServiceStack 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

npm install @identityprovider/servicestack

You must also install its peer dependencies:

npm install @identityprovider/client @servicestack/client

🧱 Usage

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:

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:

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:

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:

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