{"id":31817798,"url":"https://github.com/sandros94/unauth","last_synced_at":"2025-10-11T10:25:34.605Z","repository":{"id":317625632,"uuid":"1043910246","full_name":"sandros94/unauth","owner":"sandros94","description":"A collection of server-agnostic OAuth 2.1 and OpenID Connect utilities","archived":false,"fork":false,"pushed_at":"2025-10-02T01:52:06.000Z","size":720,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-10-02T03:27:24.203Z","etag":null,"topics":["oauth","oidc"],"latest_commit_sha":null,"homepage":"","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/sandros94.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"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":"2025-08-24T21:50:22.000Z","updated_at":"2025-10-02T01:52:10.000Z","dependencies_parsed_at":"2025-10-02T03:27:43.895Z","dependency_job_id":"1add9ace-51ef-4c8e-bbc9-5ed194f0a4a8","html_url":"https://github.com/sandros94/unauth","commit_stats":null,"previous_names":["sandros94/unauth"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/sandros94/unauth","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sandros94%2Funauth","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sandros94%2Funauth/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sandros94%2Funauth/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sandros94%2Funauth/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sandros94","download_url":"https://codeload.github.com/sandros94/unauth/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sandros94%2Funauth/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279006854,"owners_count":26084204,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-11T02:00:06.511Z","response_time":55,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["oauth","oidc"],"created_at":"2025-10-11T10:25:29.507Z","updated_at":"2025-10-11T10:25:34.599Z","avatar_url":"https://github.com/sandros94.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# unauth\n\n\u003c!-- automd:badges bundlephobia style=\"flat\" color=\"FFDC3B\" --\u003e\n\n[![npm version](https://img.shields.io/npm/v/unauth?color=FFDC3B)](https://npmjs.com/package/unauth)\n[![npm downloads](https://img.shields.io/npm/dm/unauth?color=FFDC3B)](https://npm.chart.dev/unauth)\n[![bundle size](https://img.shields.io/bundlephobia/minzip/unauth?color=FFDC3B)](https://bundlephobia.com/package/unauth)\n\n\u003c!-- /automd --\u003e\n\nA collection of low-level, and high-level server-agnostic, OAuth 2.1 and OpenID Connect utilities based on JWT ([`unjwt`](https://github.com/sandros94/unjwt)). Adapters for popular frameworks are available (PRs are welcome for more!).\n\n\u003e [!WARNING]\n\u003e This package is in active development. It is not recommended for production use yet unless you are willing to help with testing and feedback.\n\u003e Expect breaking changes, as I prioritize usability and correctness over stability at this stage.\n\n## Features\n\n- OAuth 2.1 core and extensions:\n  - Authorization Code Grant (with PKCE)\n  - Refresh Token Grant\n  - Client Credentials Grant\n  - JWT Bearer Token Grant (RFC 9068)\n  - Token Introspection (RFC 7662) (planned in adapters)\n  - Token Revocation (RFC 7009) (planned in adapters)\n- OpenID Connect Core 1.0:\n  - ID Tokens\n  - UserInfo Endpoint\n  - Discovery Endpoint\n- Framework adapters:\n  - [H3 v1](https://v1.h3.dev) (also for use with Nuxt, Nitro, etc.)\n- Works in Node.js, Deno, Bun and Browsers\n- Fully typed\n\nBuilt on top of minimal dependencies:\n\n- [`unjwt`](https://github.com/sandros94/unjwt)\n- [`unsecure`](https://github.com/sandros94/unsecure)\n\n## Usage\n\nInstall the package:\n\n```sh\n# ✨ Auto-detect (supports npm, yarn, pnpm, deno and bun)\nnpx nypm install unauth\n```\n\nImport:\n\n**ESM** (Node.js, Bun, Deno)\n\n```js\n// Main functions\nimport { OAuthProvider } from \"unauth/oauth\";\nimport { OIDCProvider } from \"unauth/oidc\";\n```\n\n**CDN** (Deno, Bun and Browsers)\n\n```js\n// Main functions\nimport { OAuthProvider } from \"https://esm.sh/unauth/oauth\";\nimport { OIDCProvider } from \"https://esm.sh/unauth/oidc\";\n```\n\n### Quick start (OIDC)\n\n```ts\nimport { OIDCProvider } from \"unauth/oidc\";\nimport { generateJWK } from \"unauth/utils\";\n\n// Configure the provider once during startup.\nconst [atJwk, idJwk] = await Promise.all([\n  generateJWK(\"RS256\", { kid: \"at-rsa-1\" }),\n  generateJWK(\"RS256\", { kid: \"id-rsa-1\" }),\n]);\nconst oidc = useOIDCProvider({\n  issuer: \"https://auth.example.com\",\n  authorizationCodeOptions: {\n    privateKey: \"ac-secret\",\n  },\n  refreshTokenOptions: {\n    privateKey: \"rt-secret\",\n  },\n  accessTokenOptions: atJwk,\n  idTokenOptions: idJwk,\n});\n\n// In your authorize endpoint\nconst authorize = oidc.validateAuthorizeRequest(req.query);\nif (!authorize.success) {\n  return redirectWithError(authorize.error);\n}\n\nconst code = await oidc.issueAuthorizationCode({\n  ...authorize.value,\n  subject: \"user-123\",\n  redirect_uri: authorize.value.redirect_uri ?? DEFAULT_REDIRECT_URI,\n});\n\n// In your token endpoint\nconst normalized = oidc.validateTokenRequest(req.body);\nif (!normalized.success) {\n  return normalized.error;\n}\n\nconst grant = await oauth.issueTokenGrant(validation.value);\nif (!grant.success) {\n  return grant.error;\n}\n\nconst idToken = await oidc.introspectIdToken(grant.value.id_token);\n```\n\n### Quick start (OAuth only)\n\n```ts\nimport { OAuthProvider } from \"unauth/oauth\";\nimport { generateJWK } from \"unauth/utils\";\n\n// Configure the provider once during startup.\nconst atJwk = await generateJWK(\"RS256\", { kid: \"at-rsa-1\" });\nconst oauth = useOAuthProvider({\n  issuer: \"https://auth.example.com\",\n  authorizationCodeOptions: {\n    privateKey: \"ac-secret\",\n  },\n  refreshTokenOptions: {\n    privateKey: \"rt-secret\",\n  },\n  accessTokenOptions: atJwk,\n});\n\nconst validation = oauth.validateTokenRequest(req.body);\nif (!validation.success) {\n  return validation.error;\n}\n\nconst grant = await oauth.issueTokenGrant(validation.value);\nif (!grant.success) {\n  return grant.error;\n}\n\n// Later, verify tokens issued by the provider\nconst accessClaims = await oauth.introspectAccessToken(\n  grant.value.access_token,\n);\n```\n\n\u003e [!NOTE]\n\u003e For advanced use-cases you can import the lower-level helpers directly, e.g. `import { issueAuthorizationCode } from \"unauth/oauth\"` or `import { buildUserInfo } from \"unauth/oidc\"`, to compose custom flows while keeping the same core primitives.\n\n### Adapters\n\n- **H3 v1**: For use with [H3 v1](https://v1.h3.dev)\n\n#### Minimal H3 v1 Example\n\nIn the following example instead of using `useOIDCProvider` or `useOAuthProvider`, we use `createOIDCRouter` (or `createOAuthRouter`) which creates an H3 router with all the necessary endpoints that can be mounted as a sub-app. We also provide an `authorize` hook to validate the client and redirect URI.\n\n```ts\nimport {\n  createApp,\n  createRouter,\n  defineEventHandler,\n  getQuery,\n  useBase,\n} from \"h3\";\nimport { createOIDCRouter, validateRedirectUri } from \"unauth/h3/oidc\";\nimport { generateJWK } from \"unauth\";\n\nconst [atJwk, idJwk] = await Promise.all([\n  generateJWK(\"RS256\", { kid: \"at-rsa-1\" }),\n  generateJWK(\"RS256\", { kid: \"id-rsa-1\" }),\n]);\n\n// If the first argument is a string, it will return a handler with a base\nconst oidcRouter = createOIDCRouter({\n  issuer: \"http://localhost:3000\",\n  discovery: {\n    // the base path where the OIDC endpoints will be served\n    // (e.g. /oidc/v1/.well-known/openid-configuration)\n    base: \"/oidc/v1\",\n    // you can also override individual endpoints here, e.g.:\n    // authorization_endpoint: \"/oidc/v1/authorize\",\n  },\n\n  authorizationCodeOptions: {\n    privateKey: \"ac-secret\",\n  },\n  refreshTokenOptions: {\n    privateKey: \"rt-secret\",\n  },\n  accessTokenOptions: atJwk, // we can directly pass keys and use default options\n  idTokenOptions: idJwk, // same as accessTokenOptions\n\n  // Hook that is called when the /authorize endpoint is hit\n  authorize: async (input) =\u003e {\n    // in a real app, you'd look up the client_id and allowed redirect URIs in your database\n    if (input.client_id !== \"test-client\") {\n      return {\n        error: \"invalid_client\",\n        error_description: \"Unknown client\",\n      };\n    }\n    const validRedirectUri = validateRedirectUri(input.redirect_uri, [\n      \"http://localhost:3000/callback\", // this is the one requested\n      \"http://localhost:3000/alt-callback\",\n    ]);\n    if (!validRedirectUri.success) {\n      return validRedirectUri.error;\n    }\n\n    // in a real app, you'd determine this from the user's login session\n    const subject = \"user-123\";\n\n    return {\n      subject,\n      redirect_uri: validRedirectUri.value,\n    };\n  },\n});\n\n// Create an H3 app instance\nexport const app = createApp();\nconst router = createRouter();\n\n// Simple callback endpoint for manual testing; used by the scripted test (which intercepts the Location header)\nrouter.get(\n  \"/callback\",\n  defineEventHandler((event) =\u003e {\n    const q = getQuery\u003c{ code?: string; state?: string }\u003e(event);\n    return `Callback received. code=${q.code ?? \"\u003cnone\u003e\"} state=${q.state ?? \"\u003cnone\u003e\"}`;\n  }),\n);\n\n// Use the same base as used in `createOIDCRouter`\nrouter.use(\"/oidc/v1/**\", useBase(\"/oidc/v1\", oidcRouter.handler));\n\napp.use(router);\n```\n\n## Development\n\n\u003cdetails\u003e\n\n\u003csummary\u003elocal development\u003c/summary\u003e\n\n- Clone this repository\n- Install latest LTS version of [Node.js](https://nodejs.org/en/)\n- Enable [Corepack](https://github.com/nodejs/corepack) using `corepack enable`\n- Install dependencies using `pnpm install`\n- Run interactive tests using `pnpm dev`\n\n\u003c/details\u003e\n\n## Why unauth?\n\nI started by building `unjwt`, as I needed a cryptographycally secure way to transmit sensitive information between various programming languages and servers. Not long after I started requiring some standardization, in particular on how to prepare and expect authorization data to be shared between parties (client and servers), but as I was testing various libraries I've never been satisfied by their DX (although most of them were great for someone that already knows the topic).\nSo I started building `unauth` as a collection of low-level primitives that then can be wrapped in higher-level abstractions, via adapters, to provide a \"batteries included\" experience while retaining control and flexibility of using your preferred storage, database and web frameworks.\n\n## Credits\n\n- Thanks to [Vidbase, Inc.](https://github.com/vidbase) (in particular to [Van Nguyen](https://github.com/thegoleffect)) for the npm package name donation\n\n## License\n\n\u003c!-- automd:contributors license=MIT --\u003e\n\nPublished under the [MIT](https://github.com/sandros94/unauth/blob/main/LICENSE) license.\nMade by [community](https://github.com/sandros94/unauth/graphs/contributors) 💛\n\u003cbr\u003e\u003cbr\u003e\n\u003ca href=\"https://github.com/sandros94/unauth/graphs/contributors\"\u003e\n\u003cimg src=\"https://contrib.rocks/image?repo=sandros94/unauth\" /\u003e\n\u003c/a\u003e\n\n\u003c!-- /automd --\u003e\n\n\u003c!-- automd:with-automd --\u003e\n\n---\n\n_🤖 auto updated with [automd](https://automd.unjs.io)_\n\n\u003c!-- /automd --\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsandros94%2Funauth","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsandros94%2Funauth","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsandros94%2Funauth/lists"}