@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
npm install @identityprovider/client
🧱 Usage
1. Instantiate the client
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
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:
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):
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:
- Call the Identity Provider's
/connect/token
- 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.
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.
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.
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:
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