Drop-in OIDC authentication for every JavaScript framework.
Most OIDC libraries couple protocol logic with a specific HTTP and framework model. This can make it harder to integrate cleanly with frameworks that have their own patterns (e.g., Angular's HttpClient), forces tests to mock window and network calls, and leads every framework to re-implement the same token exchange.
oidc-js separates these concerns:
fetch, no storage, no side effects. Deterministic and testable with plain input/output.HttpClient. React uses hooks. Svelte uses runes. No framework-specific workarounds.| Package | Description | Docs |
|---|---|---|
oidc-js-core |
Pure functions for OIDC protocol operations | API |
oidc-js |
Framework-agnostic client with fetch + sessionStorage |
API |
oidc-js-react |
React provider, hooks, and route guards | API |
oidc-js-vue |
Vue plugin, composables, and navigation guard | API |
oidc-js-svelte |
Svelte 5 context and components | API |
oidc-js-angular |
Angular service, DI, and route guard | API |
oidc-js-solid |
SolidJS signals, context, and components | API |
oidc-js-preact |
Preact hooks and components | API |
oidc-js-lit |
Lit reactive controllers | API |
oidc-js-core Pure functions. No IO. No state.
|
├── oidc-js core + fetch + sessionStorage
├── oidc-js-react core + fetch + React context/hooks
├── oidc-js-vue core + fetch + Vue plugin/composables
├── oidc-js-svelte core + fetch + Svelte 5 runes/context
├── oidc-js-angular core + HttpClient + Angular signals/DI
├── oidc-js-solid core + fetch + Solid signals/context
├── oidc-js-preact core + fetch + Preact hooks
└── oidc-js-lit core + fetch + Lit reactive controllers
The core never calls fetch or touches browser APIs (except Web Crypto for PKCE). Each framework adapter composes the core functions with its own HTTP layer and state management:
HttpClient with its interceptor chain, not a fetch workaroundfetch directly, lightweight with no wrapper overheadoidc-js-core, no browser polyfills requiredfetch or windowInstall the adapter for your framework:
npm install oidc-js-react # or oidc-js-vue, oidc-js-svelte, etc.
Wrap your app with the provider:
import { AuthProvider } from "oidc-js-react";
function App() {
return (
<AuthProvider
issuer="https://auth.example.com"
clientId="my-app"
redirectUri="http://localhost:3000/callback"
scopes={["openid", "profile", "email"]}
>
<MyApp />
</AuthProvider>
);
}
Use the hook anywhere:
import { useAuth } from "oidc-js-react";
function Profile() {
const { isAuthenticated, user, actions } = useAuth();
if (!isAuthenticated) {
return <button onClick={() => actions.login()}>Log in</button>;
}
return <p>Hello, {user?.claims?.name}</p>;
}
See each package's README for framework-specific setup, full API reference, and configuration options.
nonce claim doesn't match the value sent during authorization, token parsing throws NONCE_MISMATCH. Prevents token replay.parseDiscoveryResponse validates that the issuer field in the discovery document matches the expected issuer exactly. Prevents mix-up attacks.parseTokenResponse validates required fields and computes expires_at from the response. Malformed or error responses throw typed errors.decodeJwtPayload and parseIdTokenClaims decode the JWT payload without verifying the signature. In browser-based applications, tokens are obtained directly from the token endpoint over TLS, and the client relies on the HTTPS channel and the authorization server for integrity. However, this library does not perform cryptographic signature verification. If your application requires token verification (e.g., server-side validation, zero-trust environments), you must validate tokens independently using the provider's JWKs.at_hash / c_hash claims — the library does not validate access token or code hash claims in the ID token.| Threat | Mitigation |
|---|---|
| CSRF on authorization flow | state parameter, validated on callback |
| Token replay | nonce bound to ID token, validated on exchange |
| Authorization code interception | PKCE with S256 challenge |
| Token leakage via storage | Tokens stored in memory only (not localStorage/sessionStorage). PKCE state uses sessionStorage during the redirect round-trip and is cleared immediately after callback processing. |
| XSS | Memory-only storage limits exfiltration surface. However, if your application is compromised by XSS, in-memory tokens are accessible to attacker scripts. XSS protection is your application's responsibility (CSP, input sanitization, framework protections). |
| IdP mix-up | Discovery issuer validation rejects mismatched issuers |
Every framework adapter runs against a real OIDC identity provider — no mocked endpoints, no simulated responses. The E2E suite spins up a live Autentico instance, performs actual OAuth 2.0 flows through a browser, and asserts on both the UI state and the exact OIDC network traffic.
We use Autentico, a lightweight Go-based IdP built by the same team:
This testing strategy prioritizes determinism and reproducibility over provider diversity in CI. Provider compatibility is validated separately (see below).
16 tests cover the full OIDC lifecycle across all 8 framework adapters:
| Category | Tests |
|---|---|
| Login flow | Unauthenticated state, full login with tokens, ID token claims, userinfo profile, fetchProfile toggle, logout, manual refresh |
| Security | Tokens not in storage, back-button after logout |
| Error handling | IdP error callback, CSRF state mismatch |
| Deep linking | Login from protected page preserves returnTo |
| RequireAuth | Protected content, multi-page navigation without re-auth, auto-refresh on expired token, login redirect on revoked refresh token |
Every test also asserts the exact sequence of OIDC fetch requests (discovery → token → userinfo) and page navigations (/oauth2/authorize, /oauth2/logout) to verify no unexpected network calls are made. This catches regressions that UI-only assertions would miss — like a silent double-refresh or a missing discovery call.
The test harness is framework-agnostic: each adapter implements the same data-testid contract and runs the same Playwright spec. See tests/e2e/harness.md for the full contract.
| Provider | Status | Notes |
|---|---|---|
| Autentico | Full E2E | All 16 tests, 10x repetition, all 8 adapters |
| Auth0 | Not tested | Planned |
| Keycloak | Not tested | Planned |
| AWS Cognito | Not tested | Planned |
| Azure AD / Entra ID | Not tested | Planned |
| Not tested | Planned | |
| Okta | Not tested | Planned |
The library follows the OIDC and OAuth 2.0 specifications closely (see RFC Compliance). However, real-world providers may have non-standard behaviors or quirks. Testing against your specific provider before deploying to production is strongly recommended. If you've tested with a provider not listed here, contributions are welcome.
These are deliberate constraints, not missing features:
| Choice | Tradeoff |
|---|---|
| Single test IdP for CI | Optimizes for determinism and speed over multi-provider coverage. Compatibility relies on strict RFC adherence. |
| No iframe-based silent auth | Uses explicit refresh token flow instead. Avoids third-party cookie issues and the complexity of iframe state management. |
| Memory-only token storage | Most secure for SPAs, but tokens are lost on page refresh. Apps must handle re-authentication or use refresh tokens. |
| No JWT signature verification | SPA tokens arrive over TLS from the token endpoint. Server-side verification belongs in the resource server, not the client library. |
Core has no fetch |
Framework adapters control HTTP entirely. This means the core can't auto-discover or auto-refresh — adapters handle that orchestration. |
| No silent login on load | AuthProvider does not attempt background login on mount. If the user's session is valid at the IdP but tokens were lost (page refresh), the user must click login again. This avoids invisible network requests and iframe hacks. |
All errors throw OidcError with a typed code property:
import { OidcError } from "oidc-js-core";
try {
const result = parseCallbackUrl(url, expectedState);
} catch (e) {
if (e instanceof OidcError) {
switch (e.code) {
case "STATE_MISMATCH":
// CSRF protection triggered
break;
case "AUTHORIZATION_ERROR":
// Server returned an error
break;
}
}
}
| Error Code | Thrown By | Meaning |
|---|---|---|
DISCOVERY_INVALID |
parseDiscoveryResponse |
Missing required fields or non-object input |
DISCOVERY_ISSUER_MISMATCH |
parseDiscoveryResponse |
Issuer in response doesn't match expected |
STATE_MISMATCH |
parseCallbackUrl |
State parameter doesn't match (CSRF) |
NONCE_MISMATCH |
parseTokenResponse |
Nonce in ID token doesn't match |
MISSING_AUTH_CODE |
parseCallbackUrl |
No authorization code in callback URL |
INVALID_JWT |
decodeJwtPayload |
Malformed JWT |
TOKEN_EXCHANGE_ERROR |
parseTokenResponse |
Invalid token response |
AUTHORIZATION_ERROR |
parseCallbackUrl |
Authorization server returned an error |
MISSING_REDIRECT_URI |
buildAuthUrl |
redirectUri not set in config |
MISSING_CLIENT_SECRET |
buildIntrospectRequest |
clientSecret required but not set |
Every validation and return path in the source code is annotated with the specific RFC section it implements. The library conforms to:
Before deploying to production, verify:
tokenExpirationBuffer configured appropriately (default: 30 seconds)postLogoutRedirectUri set correctly for your IdPOidcError codes your app may encounterdecodeJwtPayload is not used for server-side trust decisions# Install dependencies
pnpm install
# Build all packages
pnpm build
# Build core only
pnpm --filter oidc-js-core build
# Run tests
pnpm --filter oidc-js-core test
# Type check
pnpm --filter oidc-js-core lint
MIT