{"_id":"@0-auth/zero-auth-idp","_rev":"2-0d51008693ca91c6424db2b7f7edf118","name":"@0-auth/zero-auth-idp","dist-tags":{"latest":"0.2.0"},"versions":{"0.1.0":{"name":"@0-auth/zero-auth-idp","version":"0.1.0","keywords":["oauth","oauth2","authorization-server","pkce","express","authentication"],"author":{"name":"Darshan Kinge"},"license":"MIT","_id":"@0-auth/zero-auth-idp@0.1.0","maintainers":[{"name":"wtfdrshn","email":"itsmedarshan8@gmail.com"}],"homepage":"https://zero-auth.netlify.app/","bugs":{"url":"https://github.com/0-auth/zero-auth/issues"},"dist":{"shasum":"832b67b54b2c1f5b2ed79da74f430c716258dfd9","tarball":"https://registry.npmjs.org/@0-auth/zero-auth-idp/-/zero-auth-idp-0.1.0.tgz","fileCount":10,"integrity":"sha512-SWfAUstSIqtfGM2zRpQf5bSuhGQPoXSnMfhrG7K+a/2y3gXvfb32KpkgH8MV8WtkgNMw8rRTKSRoidd3CEzRsA==","signatures":[{"sig":"MEQCIBMxFhNaadkTiLLYovDZnl+UIYlzqNdp7semdlF4zo6pAiAQP+TDNxxG2SAKogZHUWVYY1Z3Salm4KRPSZI5ONQefQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@0-auth%2fzero-auth-idp@0.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":190221},"main":"./dist/index.js","types":"./dist/index.d.ts","module":"./dist/index.mjs","engines":{"node":">=18.0.0"},"exports":{".":{"import":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"require":{"types":"./dist/index.d.ts","default":"./dist/index.js"}}},"gitHead":"3a27d751e3cd589cb3cf7f63bb4612277b1b2024","scripts":{"lint":"eslint src tests","test":"vitest run","build":"tsup","typecheck":"tsc --noEmit","format:check":"prettier --check \"src/**/*.ts\" \"tests/**/*.ts\"","test:coverage":"vitest run --coverage","prepublishOnly":"npm run lint && npm run typecheck && npm run test && npm run build"},"_npmUser":{"name":"wtfdrshn","email":"itsmedarshan8@gmail.com"},"repository":{"url":"git+https://github.com/0-auth/zero-auth.git","type":"git"},"_npmVersion":"10.8.2","description":"Minimal self-hosted OAuth authorization server with a backend-hosted UI","directories":{},"sideEffects":false,"_nodeVersion":"20.20.2","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.0","eslint":"^9.0.0","vitest":"^2.0.0","express":"^4.21.2","prettier":"^3.3.3","supertest":"^7.0.0","typescript":"^5.6.0","@types/node":"^22.0.0","@types/express":"^5.0.0","@types/supertest":"^6.0.2","@vitest/coverage-v8":"^2.0.0","eslint-config-prettier":"^9.1.0","@typescript-eslint/parser":"^8.0.0","@typescript-eslint/eslint-plugin":"^8.0.0"},"peerDependencies":{"express":"^4.18.0 || ^5.0.0"},"peerDependenciesMeta":{"express":{"optional":false}},"_npmOperationalInternal":{"tmp":"tmp/zero-auth-idp_0.1.0_1788604497166_0.6937818130015461","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@0-auth/zero-auth-idp","version":"0.2.0","description":"Minimal self-hosted OAuth/OIDC authorization server with a backend-hosted UI","author":{"name":"Darshan Kinge"},"license":"MIT","keywords":["oauth","oauth2","authorization-server","openid-connect","oidc","pkce","express","authentication"],"homepage":"https://zero-auth.netlify.app/","repository":{"type":"git","url":"git+https://github.com/0-auth/zero-auth.git"},"bugs":{"url":"https://github.com/0-auth/zero-auth/issues"},"main":"./dist/index.js","module":"./dist/index.mjs","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"require":{"types":"./dist/index.d.ts","default":"./dist/index.js"}}},"scripts":{"build":"tsup","test":"vitest run","test:coverage":"vitest run --coverage","lint":"eslint src tests","format:check":"prettier --check \"src/**/*.ts\" \"tests/**/*.ts\"","typecheck":"tsc --noEmit","prepublishOnly":"npm run lint && npm run typecheck && npm run test && npm run build"},"peerDependencies":{"express":"^4.18.0 || ^5.0.0"},"peerDependenciesMeta":{"express":{"optional":false}},"dependencies":{"jose":"^5.9.6"},"devDependencies":{"@types/express":"^5.0.0","@types/node":"^22.0.0","@types/supertest":"^6.0.2","@typescript-eslint/eslint-plugin":"^8.0.0","@typescript-eslint/parser":"^8.0.0","@vitest/coverage-v8":"^2.0.0","eslint":"^9.0.0","eslint-config-prettier":"^9.1.0","express":"^4.21.2","prettier":"^3.3.3","supertest":"^7.0.0","tsup":"^8.3.0","typescript":"^5.6.0","vitest":"^2.0.0"},"engines":{"node":">=18.0.0"},"sideEffects":false,"_id":"@0-auth/zero-auth-idp@0.2.0","gitHead":"e1453bac578e6eefeb6b7f4685e0aa9540ab9d71","_nodeVersion":"22.18.0","_npmVersion":"10.2.5","dist":{"integrity":"sha512-VEuBSktqFLTvGEqGO+ZxfZm/kiFE8y03xxIg2DDHJIDK8DvdPI4rQUa11jjCiXr60ufYaXYSPkyVrrvH68LhiQ==","shasum":"a9f16017a80fcd596e130ac3e747cd1c28594463","tarball":"https://registry.npmjs.org/@0-auth/zero-auth-idp/-/zero-auth-idp-0.2.0.tgz","fileCount":10,"unpackedSize":255920,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDy8h5UxrR7nbo+1SL6ZZh5WdLrdWh+WOsEfa7Pib/4LwIhANHNBb/jb6zjubBNCYZ6WJo2TJiH7gyUxG7SkN6okHoG"}]},"_npmUser":{"name":"wtfdrshn","email":"itsmedarshan8@gmail.com"},"directories":{},"maintainers":[{"name":"wtfdrshn","email":"itsmedarshan8@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/zero-auth-idp_0.2.0_1788892680518_0.7490106845286442"},"_hasShrinkwrap":false}},"time":{"created":"2026-09-05T10:34:57.019Z","modified":"2026-09-08T18:38:01.171Z","0.1.0":"2026-09-05T10:34:57.311Z","0.2.0":"2026-09-08T18:38:00.681Z"},"bugs":{"url":"https://github.com/0-auth/zero-auth/issues"},"author":{"name":"Darshan Kinge"},"license":"MIT","homepage":"https://zero-auth.netlify.app/","keywords":["oauth","oauth2","authorization-server","openid-connect","oidc","pkce","express","authentication"],"repository":{"type":"git","url":"git+https://github.com/0-auth/zero-auth.git"},"description":"Minimal self-hosted OAuth/OIDC authorization server with a backend-hosted UI","maintainers":[{"name":"wtfdrshn","email":"itsmedarshan8@gmail.com"}],"readme":"# @0-auth/zero-auth-idp\n\n> [!NOTE]\n> This is an OAuth 2.0 authorization server with an optional OpenID Connect extension.\n> OAuth remains the default. Configure the optional `oidc` extension when\n> clients need standardized sign-in and identity claims.\n\nA small, self-hosted OAuth authorization server for Express applications. It\nprovides a backend-hosted login and consent UI, Authorization Code + PKCE, and\nopaque access tokens without requiring a second server.\n\n## Install\n\n```bash\nnpm install @0-auth/zero-auth-idp express\n```\n\n## Quick start\n\nThe application owns users and verifies credentials. The package owns the\nOAuth flow and receives a small user object after successful authentication.\n\n```ts\nimport express from \"express\";\nimport { createIdentityProvider } from \"@0-auth/zero-auth-idp\";\n\nconst idp = createIdentityProvider({\n  issuer: \"http://localhost:3000/auth\",\n  logoutRedirectUri: \"http://localhost:4000/signed-out\",\n  clients: [\n    {\n      clientId: \"demo-app\",\n      name: \"Demo app\",\n      clientType: \"public\",\n      redirectUris: [\"http://localhost:4000/callback\"],\n      allowedScopes: [\"profile\", \"projects:read\"],\n    },\n  ],\n  authenticateUser: async ({ email, password }) => {\n    // Replace this with your application's user lookup and password check.\n    if (email !== \"user@example.com\" || password !== \"change-me\") return null;\n    return { id: \"user-123\", email };\n  },\n});\n\nconst app = express();\napp.use(\"/auth\", idp.router());\n\napp.get(\"/api/projects\", idp.authenticateBearer([\"projects:read\"]), (req, res) => {\n  res.json({ userId: req.idpUser?.id, projects: [] });\n});\n\napp.listen(3000);\n```\n\nThe hosted flow is:\n\n```text\n/authorize -> /login -> /consent -> client callback\n                         |\n                         +-> /token (code + PKCE verifier)\n```\n\nThe client must use `response_type=code`, `code_challenge_method=S256`, and a\nregistered redirect URI. The authorization code is short-lived and single-use.\n\nFor a runnable MongoDB application with Docker Compose, hashed users, and a complete PKCE callback, see\n[`examples/express-idp`](../../examples/express-idp).\n\n## Endpoints\n\nWhen mounted at `/auth`:\n\n| Endpoint                                           | Purpose                                    |\n| -------------------------------------------------- | ------------------------------------------ |\n| `GET /auth/.well-known/oauth-authorization-server` | OAuth metadata                             |\n| `GET /auth/authorize`                              | Start Authorization Code + PKCE            |\n| `GET/POST /auth/login`                             | Hosted login UI                            |\n| `GET/POST /auth/consent`                           | Hosted consent UI                          |\n| `POST /auth/token`                                 | Exchange a code for an access token        |\n| `POST /auth/introspect`                            | Inspect a token with a confidential client |\n| `POST /auth/revoke`                                | Revoke an access token                     |\n| `POST /auth/logout`                                | End the browser session                    |\n| `GET /auth/.well-known/openid-configuration`       | OIDC metadata when enabled                 |\n| `GET /auth/.well-known/jwks.json`                  | OIDC signing keys when enabled             |\n| `GET /auth/userinfo`                                | OIDC user claims when enabled              |\n\n## OIDC (opt-in)\n\nPass `oidc` to enable OpenID Connect discovery, signed ID tokens, JWKS, and\n`/userinfo`. Add `openid` to a client's allowed scopes; authorization requests\nusing that scope must include a `nonce`.\n\n```ts\nconst idp = createIdentityProvider({\n  // ...OAuth configuration\n  oidc: { signingKey: privateKey, keyId: \"idp-key-2026-01\" },\n});\n```\n\nProduction deployments must provide a stable RSA `signingKey`; development\ninstances generate a temporary key. The ID token contains `iss`, `sub`, `aud`,\n`iat`, `exp`, `auth_time`, `nonce`, and available `email` or `name` claims.\n\nAccess tokens are opaque, short-lived Bearer tokens. Use the returned token\nwith `Authorization: Bearer <token>` and protect resource routes with\n`idp.authenticateBearer()`.\nRoutes that require missing scopes return `403` with a `WWW-Authenticate`\nchallenge that identifies the required scope.\n\nPass `onEvent` to receive redacted login, denial, token, and logout events for\naudit logs or metrics. Event handlers are best effort; handler failures do not\nfail an authentication request, and raw passwords, codes, and tokens are never\nincluded.\n\nWhen `logoutRedirectUri` is configured, a successful logout returns `303` to\nthat fixed HTTP(S) URL. Without it, logout returns `204`. The fixed server-side\nvalue prevents callers from turning logout into an open redirect.\n\n## Hosted UI\n\nPass `ui.renderLogin`, `ui.renderConsent`, or `ui.renderError` to replace a\nhosted page. Login renderers receive the client name and the previously entered\nemail after a failed attempt; passwords are never returned. Import `escapeHtml`\nfor every context value inserted into HTML. Same-origin stylesheets are allowed\nby the package's Content Security Policy.\n\n```ts\nimport { createIdentityProvider, escapeHtml } from \"@0-auth/zero-auth-idp\";\n\nconst ui = {\n  renderLogin: ({ action, transactionId, csrfToken, clientName, email = \"\", error }) => `\n    <link rel=\"stylesheet\" href=\"/auth.css\">\n    <h1>Continue to ${escapeHtml(clientName)}</h1>\n    ${error ? `<p role=\"alert\">${escapeHtml(error)}</p>` : \"\"}\n    <form method=\"post\" action=\"${escapeHtml(action)}\">\n      <input type=\"hidden\" name=\"transaction\" value=\"${escapeHtml(transactionId)}\">\n      <input type=\"hidden\" name=\"csrf_token\" value=\"${escapeHtml(csrfToken)}\">\n      <input type=\"email\" name=\"email\" value=\"${escapeHtml(email)}\" required>\n      <input type=\"password\" name=\"password\" required>\n      <button>Continue</button>\n    </form>`,\n};\n```\n\n## Storage\n\nThe default in-memory storage is useful for local development and tests. It is\nnot durable and must not be used for a multi-instance deployment.\n\nImplement `OAuthStorage` for a database or shared cache, then pass it as\n`storage`. The adapter must store hashes for session, authorization-code, and\naccess-token values, and `consumeAuthorizationCode` must be atomic.\n\n## Security defaults\n\n- PKCE with `S256` is mandatory.\n- Redirect URIs must match exactly and cannot contain fragments.\n- Issuers must be HTTP(S) URLs without credentials, queries, or fragments; redirect\n  URIs reject credential-bearing and active-content schemes.\n- Authorization codes are short-lived and single-use.\n- Sessions and transactions are rejected by the provider when expired, even if a\n  storage adapter returns stale records.\n- Sessions use HTTP-only cookies; HTTPS issuers and production environments use\n  secure cookies by default.\n- Browser forms use CSRF tokens.\n- Logout requires a same-origin `Origin`, `Referer`, or browser fetch metadata.\n- Authorization responses include the server issuer (`iss`) and metadata advertises\n  that support for mix-up protection.\n- OIDC is disabled unless explicitly configured; ID tokens are signed with RS256.\n- Hosted pages may load stylesheets from the issuer origin; scripts remain blocked.\n- HTML, token, and error responses use no-store and basic security headers.\n- Raw passwords, codes, sessions, and access tokens are never stored by the\n  built-in storage.\n\nRate-limit `authenticateUser`, use HTTPS in production, and replace the memory\nstorage before deploying more than one process.\n\n## Deliberate V1 limits\n\nThis package does not currently implement refresh tokens, registration, password\nreset, email verification, dynamic client registration, or a client-management\nUI. Add those only after the OAuth and OIDC flows are stable.\n\n## License\n\n[MIT](./LICENSE)\n","readmeFilename":"README.md"}