https://github.com/keycloakify/oidc-spa
OpenID Connect solution for browser-centric web apps.
https://github.com/keycloakify/oidc-spa
keycloak oidc oidc-client typescript
Last synced: 22 days ago
JSON representation
OpenID Connect solution for browser-centric web apps.
- Host: GitHub
- URL: https://github.com/keycloakify/oidc-spa
- Owner: keycloakify
- License: mit
- Created: 2023-10-21T13:32:35.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2026-01-23T03:17:10.000Z (26 days ago)
- Last Synced: 2026-01-23T18:30:09.608Z (25 days ago)
- Topics: keycloak, oidc, oidc-client, typescript
- Language: TypeScript
- Homepage: https://www.oidc-spa.dev
- Size: 4.09 MB
- Stars: 249
- Watchers: 2
- Forks: 19
- Open Issues: 8
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Security: SECURITY.md
Awesome Lists containing this project
README

oidc-spa is an OpenID Connect client built for browser-first apps.
It wraps the full Authorization Code + PKCE flow in a high-level API so you can ship secure app auth without stitching together multiple SDKs and ad-hoc glue.
- 🔒 Security-first defaults: in-memory tokens, strict redirect handling, and opt-in defenses like [DPoP](https://docs.oidc-spa.dev/v/v10/security-features/dpop)
and [token substitution](https://docs.oidc-spa.dev/docs/v9/security-features/token-substitution) to reduce token exposure risk.
- 🧠Battle-tested auth UX: token renewal, idle timeout, auto login/logout, multi-tab session sync, and reliable session restore on reload.
- 🧩 Full-stack ready: [backend token validation utilities](https://docs.oidc-spa.dev/v/v10/integration-guides/backend-token-validation) and [first-class TanStack Start integration](https://docs.oidc-spa.dev/v/v10/integration-guides/tanstack-router-start/tanstack-start) in the same library.
- 🧰 Provider-aware: handles real-world quirks across Keycloak, Entra ID, Auth0, Google, and more.
- ✨ Developer experience: types flow from config into the API, minimal knobs, and easy-to-mock auth for tests.
[Get Started](https://docs.oidc-spa.dev)
## At a glance
The Framework-Agnostic Adapter:
```ts
import { createOidc, oidcEarlyInit } from "oidc-spa/core"; // ~33 KB min+gzip (See: https://docs.oidc-spa.dev/resources/bundle-size)
import { z } from "zod"; // 59 KB min+gzip, but it's optional.
// Call this only if you don't use oidc-spa's Vite plugin.
oidcEarlyInit({ BASE_URL: "/" });
const oidc = await createOidc({
issuerUri: "https://auth.my-domain.net/realms/myrealm",
//issuerUri: "https://login.microsoftonline.com/...",
//issuerUri: "https://xxx.us.auth0.com/..."
//issuerUri: "https://accounts.google.com/o/oauth2/v2/auth"
clientId: "myclient",
// Optional; you can write a validator by hand, or give up some type-safety, your call.
decodedIdTokenSchema: z.object({
name: z.string(),
picture: z.string().optional(),
email: z.string(),
realm_access: z.object({ roles: z.array(z.string()) })
})
// Yes, really, it's that simple; there are no other parameters to provide.
// The Redirect URI (callback URL) is the root URL of your app (no public/callback.html involved).
});
// In oidc-spa the user is either logged in or they aren't.
// The state will never mutate without a full app reload.
// This makes reasoning about auth much, much easier.
if (!oidc.isUserLoggedIn) {
await oidc.login();
// Never here
return;
}
const { name, realm_access } = oidc.getDecodedIdToken();
console.log(`Hello ${name}`);
const { accessToken } = await oidc.getTokens();
await fetch("https://my-domain.net/api/todos", {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
if (realm_access.roles.includes("realm-admin")) {
// User is an admin
}
```
Higher-level adapters, example with React but we also feature a similar Angular adapter:

Full-stack auth solution with TanStack Start:
```tsx
import { createServerFn } from "@tanstack/react-start";
import { enforceLogin, oidcFnMiddleware } from "@/oidc";
import fs from "node:fs/promises";
const getTodos = createServerFn({ method: "GET" })
.middleware([oidcFnMiddleware({ assert: "user logged in" })])
.handler(async ({ context: { oidc } }) => {
const userId = oidc.accessTokenClaims.sub;
const json = await fs.readFile(`todos_${userId}.json`, "utf8");
return JSON.parse(json);
});
export const Route = createFileRoute("/todos")({
beforeLoad: enforceLogin,
loader: () => getTodos(),
component: RouteComponent
});
function RouteComponent() {
const todos = Route.useLoaderData();
return (
-
{todo.isDone && "✅"} {todo.text}
{todos.map(todo => (
))}
);
}
```
## Sponsors
Project backers, we trust and recommend their services.


Keycloak as a Service — Keycloak community contributors of popular extensions providing free and dedicated Keycloak hosting and enterprise Keycloak support to businesses of all sizes.

