{"id":14483555,"url":"https://github.com/keycloakify/oidc-spa","last_synced_at":"2026-04-02T00:23:19.577Z","repository":{"id":202762292,"uuid":"708084394","full_name":"keycloakify/oidc-spa","owner":"keycloakify","description":"OpenID Connect solution for browser-centric web apps.","archived":false,"fork":false,"pushed_at":"2026-03-31T06:34:16.000Z","size":4197,"stargazers_count":270,"open_issues_count":13,"forks_count":21,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-03-31T08:42:59.519Z","etag":null,"topics":["keycloak","oidc","oidc-client","typescript"],"latest_commit_sha":null,"homepage":"https://www.oidc-spa.dev","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/keycloakify.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-10-21T13:32:35.000Z","updated_at":"2026-03-26T16:06:09.000Z","dependencies_parsed_at":"2024-04-09T01:42:17.112Z","dependency_job_id":"6d3c916b-03a5-4ff2-8de3-921c3228b3cb","html_url":"https://github.com/keycloakify/oidc-spa","commit_stats":null,"previous_names":["garronej/oidc-spa","keycloakify/oidc-spa"],"tags_count":360,"template":false,"template_full_name":"garronej/ts-ci","purl":"pkg:github/keycloakify/oidc-spa","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keycloakify%2Foidc-spa","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keycloakify%2Foidc-spa/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keycloakify%2Foidc-spa/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keycloakify%2Foidc-spa/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/keycloakify","download_url":"https://codeload.github.com/keycloakify/oidc-spa/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keycloakify%2Foidc-spa/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31293369,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T21:15:39.731Z","status":"ssl_error","status_checked_at":"2026-04-01T21:15:34.046Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["keycloak","oidc","oidc-client","typescript"],"created_at":"2024-09-03T00:01:51.600Z","updated_at":"2026-04-02T00:23:19.303Z","avatar_url":"https://github.com/keycloakify.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"![oidc-spa](https://github.com/keycloakify/oidc-spa/assets/6702424/3375294c-cc31-4fc1-9fb5-1fcfa00423ba)\n\n\u003cp align=\"center\"\u003e\n    \u003cbr\u003e\n    \u003ca href=\"https://github.com/keycloakify/oidc-spa/actions\"\u003e\n      \u003cimg src=\"https://github.com/keycloakify/oidc-spa/actions/workflows/ci.yaml/badge.svg?branch=main\"\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://www.npmjs.com/package/oidc-spa\"\u003e\n      \u003cimg src=\"https://img.shields.io/npm/dw/oidc-spa\"\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://npmgraph.js.org/?q=oidc-spa\"\u003e\n      \u003cimg src=\"https://depx.co/api/badge/oidc-spa\"\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://github.com/garronej/oidc-spa/blob/main/LICENSE\"\u003e\n      \u003cimg src=\"https://img.shields.io/npm/l/oidc-spa\"\u003e\n    \u003c/a\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n  We're here to help!\u003cbr/\u003e\n  \u003ca href=\"https://discord.gg/mJdYJSdcm4\"\u003e\n    \u003cimg src=\"https://dcbadge.limes.pink/api/server/kYFZG7fQmn\"/\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://www.oidc-spa.dev\"\u003eHome\u003c/a\u003e\n  -\n  \u003ca href=\"https://docs.oidc-spa.dev\"\u003eDocumentation\u003c/a\u003e\n\u003c/p\u003e\n\noidc-spa is an OpenID Connect client built for browser-first apps.  \nIt 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.\n\n-   🔒 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)\n    and [token substitution](https://docs.oidc-spa.dev/docs/v9/security-features/token-substitution) to reduce token exposure risk.\n-   🧭 Battle-tested auth UX: token renewal, idle timeout, auto login/logout, multi-tab session sync, and reliable session restore on reload.\n-   🧩 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.\n-   🧰 Provider-aware: handles real-world quirks across Keycloak, Entra ID, Auth0, Google, and more.\n-   ✨ Developer experience: types flow from config into the API, minimal knobs, and easy-to-mock auth for tests.\n\n[Get Started](https://docs.oidc-spa.dev)\n\n## At a glance\n\nThe Framework-Agnostic Adapter:\n\n```ts\nimport { createOidc, oidcEarlyInit } from \"oidc-spa/core\"; // ~33 KB min+gzip (See: https://docs.oidc-spa.dev/resources/bundle-size)\nimport { z } from \"zod\"; // 59 KB min+gzip, but it's optional.\n\n// Call this only if you don't use oidc-spa's Vite plugin.\noidcEarlyInit({ BASE_URL: \"/\" });\n\nconst oidc = await createOidc({\n    issuerUri: \"https://auth.my-domain.net/realms/myrealm\",\n    //issuerUri: \"https://login.microsoftonline.com/...\",\n    //issuerUri: \"https://xxx.us.auth0.com/...\"\n    //issuerUri: \"https://accounts.google.com/o/oauth2/v2/auth\"\n    clientId: \"myclient\",\n    // Optional; you can write a validator by hand, or give up some type-safety, your call.\n    decodedIdTokenSchema: z.object({\n        name: z.string(),\n        picture: z.string().optional(),\n        email: z.string(),\n        realm_access: z.object({ roles: z.array(z.string()) })\n    })\n    // Yes, really, it's that simple; there are no other parameters to provide.\n    // The Redirect URI (callback URL) is the root URL of your app (no public/callback.html involved).\n});\n\n// In oidc-spa the user is either logged in or they aren't.\n// The state will never mutate without a full app reload.\n// This makes reasoning about auth much, much easier.\nif (!oidc.isUserLoggedIn) {\n    await oidc.login();\n    // Never here\n    return;\n}\n\nconst { name, realm_access } = oidc.getDecodedIdToken();\n\nconsole.log(`Hello ${name}`);\n\nconst { accessToken } = await oidc.getTokens();\n\nawait fetch(\"https://my-domain.net/api/todos\", {\n    headers: {\n        Authorization: `Bearer ${accessToken}`\n    }\n});\n\nif (realm_access.roles.includes(\"realm-admin\")) {\n    // User is an admin\n}\n```\n\nHigher-level adapters, example with React but we also feature a similar Angular adapter:\n\n\u003cimg width=\"1835\" height=\"942\" alt=\"Image\" src=\"https://github.com/user-attachments/assets/a7a18bbc-998a-459c-8cfa-93b599a45524\" /\u003e\n\nFull-stack auth solution with TanStack Start:\n\n```tsx\nimport { createServerFn } from \"@tanstack/react-start\";\nimport { enforceLogin, oidcFnMiddleware } from \"@/oidc\";\nimport fs from \"node:fs/promises\";\n\nconst getTodos = createServerFn({ method: \"GET\" })\n    .middleware([oidcFnMiddleware({ assert: \"user logged in\" })])\n    .handler(async ({ context: { oidc } }) =\u003e {\n        const userId = oidc.accessTokenClaims.sub;\n\n        const json = await fs.readFile(`todos_${userId}.json`, \"utf8\");\n\n        return JSON.parse(json);\n    });\n\nexport const Route = createFileRoute(\"/todos\")({\n    beforeLoad: enforceLogin,\n    loader: () =\u003e getTodos(),\n    component: RouteComponent\n});\n\nfunction RouteComponent() {\n    const todos = Route.useLoaderData();\n\n    return (\n        \u003cul\u003e\n            {todos.map(todo =\u003e (\n                \u003cli key={todo.id}\u003e\n                    {todo.isDone \u0026\u0026 \"✅\"} {todo.text}\n                \u003c/li\u003e\n            ))}\n        \u003c/ul\u003e\n    );\n}\n```\n\n## Sponsors\n\nProject backers, we trust and recommend their services.\n\n\u003cbr/\u003e\n\n\u003cdiv align=\"center\"\u003e\n\n![Logo Dark](https://github.com/user-attachments/assets/d8f6b6f5-3de4-4adc-ba15-cb4074e8309b#gh-dark-mode-only)\n\n\u003c/div\u003e\n\n\u003cdiv align=\"center\"\u003e\n\n![Logo Light](https://github.com/user-attachments/assets/20736d6f-f22d-4a9d-9dfe-93be209a8191#gh-light-mode-only)\n\n\u003c/div\u003e\n\n\u003cbr/\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003ci\u003e\u003ca href=\"https://phasetwo.io/?utm_source=keycloakify\"\u003e\u003cstrong\u003eKeycloak as a Service\u003c/strong\u003e\u003c/a\u003e — Keycloak community contributors of popular \u003ca href=\"https://github.com/p2-inc#our-extensions-?utm_source=keycloakify\"\u003eextensions\u003c/a\u003e providing free and dedicated \u003ca href=\"https://phasetwo.io/hosting/?utm_source=keycloakify\"\u003eKeycloak hosting\u003c/a\u003e and enterprise \u003ca href=\"https://phasetwo.io/support/?utm_source=keycloakify\"\u003eKeycloak support\u003c/a\u003e to businesses of all sizes.\u003c/i\u003e\n\u003c/p\u003e\n\n\u003cbr/\u003e\n\u003cbr/\u003e\n\u003cbr/\u003e\n\n\u003cdiv align=\"center\"\u003e\n\n![Logo Dark](https://github.com/user-attachments/assets/dd3925fb-a58a-4e91-b360-69c2fa1f1087#gh-dark-mode-only)\n\n\u003c/div\u003e\n\n\u003cdiv align=\"center\"\u003e\n\n![Logo Light](https://github.com/user-attachments/assets/6c00c201-eed7-485a-a887-70891559d69b#gh-light-mode-only)\n\n\u003c/div\u003e\n\n\u003cbr/\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://www.zone2.tech/services/keycloak-consulting\"\u003e\n    \u003ci\u003e\u003cstrong\u003eKeycloak Consulting Services\u003c/strong\u003e — Your partner in Keycloak deployment, configuration, and extension development for optimized identity management solutions.\u003c/i\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkeycloakify%2Foidc-spa","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkeycloakify%2Foidc-spa","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkeycloakify%2Foidc-spa/lists"}