@identityprovider/servicestack v2.3.1
@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
andidTokenHint
- 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
andnonce
(handled automatically) - Prefer
HttpOnly
cookie storage over in-memory access tokens - Use
SameSite=Lax
orStrict
for session cookies - Avoid storing tokens in
localStorage
or sharing across tabs
📦 License
MIT