{"_id":"@ascorbic/atproto-oauth-provider","_rev":"4-50b30fb8ea632260fe8b6be6145e9b42","name":"@ascorbic/atproto-oauth-provider","dist-tags":{"latest":"0.1.1"},"versions":{"0.0.0":{"name":"@ascorbic/atproto-oauth-provider","version":"0.0.0","keywords":["atproto","bluesky","oauth","oauth2.1","dpop","pkce","cloudflare-workers"],"author":{"name":"Matt Kane"},"license":"MIT","_id":"@ascorbic/atproto-oauth-provider@0.0.0","maintainers":[{"name":"ascorbic","email":"m@mk.gg"}],"homepage":"https://github.com/ascorbic/atproto-worker","bugs":{"url":"https://github.com/ascorbic/atproto-worker/issues"},"dist":{"shasum":"97f1ad811e3268fe4e481eba6050361fc44350ac","tarball":"https://registry.npmjs.org/@ascorbic/atproto-oauth-provider/-/atproto-oauth-provider-0.0.0.tgz","fileCount":5,"integrity":"sha512-Dbj8Vpv5Ms/tWeptWhJZo3daf0gwEQ9PC3ygIWyELrZpUOJQtdVohTpsQmWHCyCz3z2OcDFGSnKa/9/q1GXM9w==","signatures":[{"sig":"MEUCIC/rVRerjRMot3sRrGf7QnmZQh6jKFFh5Oa+aI+5Qs79AiEAr0NjrzXjOy7GNCqjZ5XChj5fGJF5+t0kdZ5r0PNXiI8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":156363},"main":"dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"gitHead":"c1619fee3cb623390a5a117d67df190599505e90","scripts":{"dev":"tsdown --watch","test":"vitest run","build":"tsdown","check":"publint && attw --pack --ignore-rules=cjs-resolves-to-esm"},"_npmUser":{"name":"ascorbic","email":"m@mk.gg"},"deprecated":"This package has been renamed to @getcirrus/oauth-provider. Please migrate to the new package.","repository":{"url":"git+https://github.com/ascorbic/atproto-worker.git","type":"git","directory":"packages/oauth-provider"},"_npmVersion":"10.9.2","description":"OAuth 2.1 Provider with AT Protocol extensions for Cloudflare Workers","directories":{},"_nodeVersion":"22.17.0","dependencies":{"jose":"^6.1.3","@atproto/syntax":"^0.4.2","@atproto/oauth-types":"^0.5.2"},"_hasShrinkwrap":false,"devDependencies":{"tsdown":"^0.18.3","vitest":"^4.0.0","publint":"^0.3.16","typescript":"^5.9.3","@arethetypeswrong/cli":"^0.18.2","@cloudflare/workers-types":"^4.20251225.0"},"_npmOperationalInternal":{"tmp":"tmp/atproto-oauth-provider_0.0.0_1767132658054_0.3537903609045969","host":"s3://npm-registry-packages-npm-production"}},"0.1.0":{"name":"@ascorbic/atproto-oauth-provider","version":"0.1.0","keywords":["atproto","bluesky","oauth","oauth2.1","dpop","pkce","cloudflare-workers"],"author":{"name":"Matt Kane"},"license":"MIT","_id":"@ascorbic/atproto-oauth-provider@0.1.0","maintainers":[{"name":"ascorbic","email":"m@mk.gg"}],"homepage":"https://github.com/ascorbic/atproto-worker","bugs":{"url":"https://github.com/ascorbic/atproto-worker/issues"},"dist":{"shasum":"b448455900037985522f86350528d78212770bc4","tarball":"https://registry.npmjs.org/@ascorbic/atproto-oauth-provider/-/atproto-oauth-provider-0.1.0.tgz","fileCount":6,"integrity":"sha512-5yReauuIOha0/bfsZf9W9WySYK36Ra2N2s+frYtYLrmlBSMmdX3IKrl7CAl5p6GvtEKFNb7VRx80drdK7kS+Bw==","signatures":[{"sig":"MEYCIQCK5b7bShgQ4fU2JKOcDguoUyDGsr/TZpW38UoavaArmAIhAO3DtUcUrl6IDjcKj8uEV5bZm93FB1ob+DqD0n4Zen/J","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@ascorbic%2fatproto-oauth-provider@0.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":163948},"main":"dist/index.js","type":"module","_from":"file:ascorbic-atproto-oauth-provider-0.1.0.tgz","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"scripts":{"dev":"tsdown --watch","test":"vitest run","build":"tsdown","check":"publint && attw --pack --ignore-rules=cjs-resolves-to-esm"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:8d5304da-139d-4285-a875-83546adc444a"}},"_resolved":"/tmp/d44e186061a4dc078d56a81627f12c38/ascorbic-atproto-oauth-provider-0.1.0.tgz","_integrity":"sha512-5yReauuIOha0/bfsZf9W9WySYK36Ra2N2s+frYtYLrmlBSMmdX3IKrl7CAl5p6GvtEKFNb7VRx80drdK7kS+Bw==","deprecated":"This package has been renamed to @getcirrus/oauth-provider. Please migrate to the new package.","repository":{"url":"git+https://github.com/ascorbic/atproto-worker.git","type":"git","directory":"packages/oauth-provider"},"_npmVersion":"11.7.0","description":"OAuth 2.1 Provider with AT Protocol extensions for Cloudflare Workers","directories":{},"_nodeVersion":"25.2.1","dependencies":{"jose":"^6.1.3","@atproto/syntax":"^0.4.2","@atproto/oauth-types":"^0.5.2"},"_hasShrinkwrap":false,"devDependencies":{"tsdown":"^0.18.3","vitest":"^4.0.0","publint":"^0.3.16","typescript":"^5.9.3","@arethetypeswrong/cli":"^0.18.2","@cloudflare/workers-types":"^4.20251225.0"},"_npmOperationalInternal":{"tmp":"tmp/atproto-oauth-provider_0.1.0_1767267111317_0.8724380855261331","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@ascorbic/atproto-oauth-provider","version":"0.1.1","description":"OAuth 2.1 Provider with AT Protocol extensions for Cloudflare Workers","type":"module","main":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"dependencies":{"@atproto/oauth-types":"^0.5.2","@atproto/syntax":"^0.4.2","jose":"^6.1.3"},"devDependencies":{"@arethetypeswrong/cli":"^0.18.2","@cloudflare/workers-types":"^4.20251225.0","publint":"^0.3.16","tsdown":"^0.18.3","typescript":"^5.9.3","vitest":"^4.0.0"},"repository":{"type":"git","url":"git+https://github.com/ascorbic/cirrus.git","directory":"packages/oauth-provider"},"homepage":"https://github.com/ascorbic/cirrus","keywords":["atproto","bluesky","oauth","oauth2.1","dpop","pkce","cloudflare-workers"],"author":{"name":"Matt Kane"},"license":"MIT","scripts":{"build":"tsdown","dev":"tsdown --watch","test":"vitest run","check":"publint && attw --pack --ignore-rules=cjs-resolves-to-esm"},"types":"./dist/index.d.ts","_id":"@ascorbic/atproto-oauth-provider@0.1.1","bugs":{"url":"https://github.com/ascorbic/cirrus/issues"},"_integrity":"sha512-u5hRfzdLOlcwNWRDd+3daIpJqBnKkDtIkhkLwdy84l2uu8tCiHd636mKnhp9js+scrJQCEbHixTR70lfqmm9RQ==","_resolved":"/tmp/bc618f780def0078fafb5526a8339472/ascorbic-atproto-oauth-provider-0.1.1.tgz","_from":"file:ascorbic-atproto-oauth-provider-0.1.1.tgz","_nodeVersion":"25.2.1","_npmVersion":"11.7.0","dist":{"integrity":"sha512-u5hRfzdLOlcwNWRDd+3daIpJqBnKkDtIkhkLwdy84l2uu8tCiHd636mKnhp9js+scrJQCEbHixTR70lfqmm9RQ==","shasum":"82cec876f6c88f9c94324dae0ba970a81baf1946","tarball":"https://registry.npmjs.org/@ascorbic/atproto-oauth-provider/-/atproto-oauth-provider-0.1.1.tgz","fileCount":6,"unpackedSize":164216,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@ascorbic%2fatproto-oauth-provider@0.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCaadqPsFlL6KIy7BUidS8Pc7uULLsgglg8gxyB5V6nUAIhAMWKvsgBlerXFpCmUHw+WBkn9fyad5BXpJpwzDt+6pdI"}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:9ee2b970-028b-4622-9088-cdab6925e011"}},"directories":{},"maintainers":[{"name":"ascorbic","email":"m@mk.gg"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/atproto-oauth-provider_0.1.1_1767464066043_0.877574164479751"},"_hasShrinkwrap":false}},"time":{"created":"2025-12-30T22:10:57.889Z","modified":"2026-01-03T18:14:26.589Z","0.0.0":"2025-12-30T22:10:58.193Z","0.1.0":"2026-01-01T11:31:51.567Z","0.1.1":"2026-01-03T18:14:26.184Z"},"bugs":{"url":"https://github.com/ascorbic/cirrus/issues"},"author":{"name":"Matt Kane"},"license":"MIT","homepage":"https://github.com/ascorbic/cirrus","keywords":["atproto","bluesky","oauth","oauth2.1","dpop","pkce","cloudflare-workers"],"repository":{"type":"git","url":"git+https://github.com/ascorbic/cirrus.git","directory":"packages/oauth-provider"},"description":"OAuth 2.1 Provider with AT Protocol extensions for Cloudflare Workers","maintainers":[{"name":"ascorbic","email":"m@mk.gg"}],"readme":"# @ascorbic/atproto-oauth-provider\n\n> **🚨 This package has been renamed to `@getcirrus/oauth-provider`**\n>\n> This package is deprecated and will no longer receive updates. Please migrate to [`@getcirrus/oauth-provider`](https://www.npmjs.com/package/@getcirrus/oauth-provider) for the latest features and bug fixes.\n\nAT Protocol OAuth 2.1 Authorization Server for Cloudflare Workers.\n\nA complete OAuth 2.1 provider implementation that enables \"Login with Bluesky\" functionality for your PDS. Built specifically for Cloudflare Workers with Durable Objects.\n\n## Features\n\n- **OAuth 2.1 Authorization Code Flow** with PKCE (Proof Key for Code Exchange)\n- **DPoP (Demonstrating Proof of Possession)** for token binding and enhanced security\n- **PAR (Pushed Authorization Requests)** for secure authorization request initiation\n- **Client Metadata Discovery** via `client_id` URL resolution\n- **Token Management** - generation, rotation, and revocation\n- **Storage Interface** - pluggable storage backend (SQLite adapter included)\n\n## Installation\n\n```bash\nnpm install @ascorbic/atproto-oauth-provider\n# or\npnpm add @ascorbic/atproto-oauth-provider\n```\n\n## Quick Start\n\n```typescript\nimport { OAuthProvider } from \"@ascorbic/atproto-oauth-provider\";\nimport { OAuthStorage } from \"./your-storage-implementation\";\n\n// Initialize the provider\nconst provider = new OAuthProvider({\n  issuer: \"https://your-pds.example.com\",\n  storage: new OAuthStorage(),\n});\n\n// Handle OAuth endpoints in your Worker\napp.post(\"/oauth/par\", async (c) => {\n  const result = await provider.handlePAR(await c.req.formData());\n  return c.json(result);\n});\n\napp.get(\"/oauth/authorize\", async (c) => {\n  const result = await provider.handleAuthorize(c.req.url);\n  // Show authorization UI to user\n  return c.html(renderAuthUI(result));\n});\n\napp.post(\"/oauth/token\", async (c) => {\n  const result = await provider.handleToken(\n    await c.req.formData(),\n    c.req.header(\"DPoP\"),\n  );\n  return c.json(result);\n});\n```\n\n## Architecture\n\n### Provider\n\nThe `OAuthProvider` class is the main entry point. It handles:\n\n- Client metadata validation and discovery\n- Authorization request processing (with PAR support)\n- Token generation and validation\n- DPoP proof verification\n- PKCE challenge verification\n\n### Storage Interface\n\nThe provider uses a storage interface that you implement for your backend:\n\n```typescript\nexport interface OAuthProviderStorage {\n  // Authorization codes\n  saveAuthCode(code: string, data: AuthCodeData): Promise<void>;\n  getAuthCode(code: string): Promise<AuthCodeData | null>;\n  deleteAuthCode(code: string): Promise<void>;\n\n  // Access/refresh tokens\n  saveTokens(data: TokenData): Promise<void>;\n  getTokenByAccess(accessToken: string): Promise<TokenData | null>;\n  getTokenByRefresh(refreshToken: string): Promise<TokenData | null>;\n  revokeToken(accessToken: string): Promise<void>;\n  revokeAllTokens(sub: string): Promise<void>;\n\n  // Client metadata cache\n  saveClient(clientId: string, metadata: ClientMetadata): Promise<void>;\n  getClient(clientId: string): Promise<ClientMetadata | null>;\n\n  // PAR (Pushed Authorization Requests)\n  savePAR(requestUri: string, data: PARData): Promise<void>;\n  getPAR(requestUri: string): Promise<PARData | null>;\n  deletePAR(requestUri: string): Promise<void>;\n\n  // DPoP nonce tracking\n  checkAndSaveNonce(nonce: string): Promise<boolean>;\n}\n```\n\nA SQLite implementation for Durable Objects is included in the `@ascorbic/pds` package.\n\n## OAuth 2.1 Flow\n\n### 1. Pushed Authorization Request (PAR)\n\nClient initiates the flow by pushing authorization parameters to the server:\n\n```http\nPOST /oauth/par\nContent-Type: application/x-www-form-urlencoded\n\nclient_id=https://client.example.com/client-metadata.json\n&code_challenge=XXXXXX\n&code_challenge_method=S256\n&redirect_uri=https://client.example.com/callback\n&scope=atproto\n&state=random-state\n```\n\nResponse:\n\n```json\n{\n  \"request_uri\": \"urn:ietf:params:oauth:request_uri:XXXXXX\",\n  \"expires_in\": 90\n}\n```\n\n### 2. Authorization\n\nUser is redirected to authorize the client:\n\n```http\nGET /oauth/authorize?request_uri=urn:ietf:params:oauth:request_uri:XXXXXX\n```\n\nAfter user approves, they're redirected back with an authorization code:\n\n```http\nHTTP/1.1 302 Found\nLocation: https://client.example.com/callback?code=XXXXXX&state=random-state\n```\n\n### 3. Token Exchange\n\nClient exchanges the authorization code for tokens:\n\n```http\nPOST /oauth/token\nContent-Type: application/x-www-form-urlencoded\nDPoP: <dpop-proof-jwt>\n\ngrant_type=authorization_code\n&code=XXXXXX\n&redirect_uri=https://client.example.com/callback\n&code_verifier=YYYYYY\n&client_id=https://client.example.com/client-metadata.json\n```\n\nResponse:\n\n```json\n{\n  \"access_token\": \"XXXXXX\",\n  \"token_type\": \"DPoP\",\n  \"expires_in\": 3600,\n  \"refresh_token\": \"YYYYYY\",\n  \"scope\": \"atproto\",\n  \"sub\": \"did:plc:abc123\"\n}\n```\n\n## Security Features\n\n### PKCE (Proof Key for Code Exchange)\n\nAll authorization flows require PKCE to prevent authorization code interception attacks:\n\n- Client generates `code_verifier` (random string)\n- Client sends SHA-256 hash as `code_challenge`\n- Server verifies `code_verifier` matches during token exchange\n\n### DPoP (Demonstrating Proof of Possession)\n\nBinds tokens to specific clients using cryptographic proofs:\n\n- Client generates a key pair\n- Client includes DPoP proof JWT with each token request\n- Tokens are bound to the client's public key\n- Prevents token theft and replay attacks\n\n### Replay Protection\n\n- DPoP nonces are tracked to prevent replay attacks\n- Authorization codes are single-use\n- Refresh tokens can be rotated on each use\n\n## Client Metadata Discovery\n\nClients are identified by a URL pointing to their metadata document:\n\n```json\n{\n  \"client_id\": \"https://client.example.com/client-metadata.json\",\n  \"client_name\": \"Example App\",\n  \"redirect_uris\": [\"https://client.example.com/callback\"],\n  \"grant_types\": [\"authorization_code\", \"refresh_token\"],\n  \"response_types\": [\"code\"],\n  \"scope\": \"atproto\",\n  \"token_endpoint_auth_method\": \"none\",\n  \"application_type\": \"web\"\n}\n```\n\nThe provider automatically fetches and validates client metadata from the `client_id` URL.\n\n## Integration with @atproto/oauth-client\n\nThis provider is designed to work seamlessly with `@atproto/oauth-client`:\n\n```typescript\n// Client side\nimport { OAuthClient } from \"@atproto/oauth-client\";\n\nconst client = new OAuthClient({\n  clientMetadata: {\n    client_id: \"https://my-app.example.com/client-metadata.json\",\n    redirect_uris: [\"https://my-app.example.com/callback\"],\n  },\n});\n\n// Initiate login\nconst authUrl = await client.authorize(\"https://user-pds.example.com\", {\n  scope: \"atproto\",\n});\n\n// Handle callback\nconst { session } = await client.callback(callbackParams);\n```\n\n## Error Handling\n\nThe provider returns standard OAuth 2.1 error responses:\n\n```json\n{\n  \"error\": \"invalid_request\",\n  \"error_description\": \"Missing required parameter: code_challenge\"\n}\n```\n\nCommon error codes:\n\n- `invalid_request` - Malformed request\n- `invalid_client` - Client authentication failed\n- `invalid_grant` - Invalid authorization code or refresh token\n- `unauthorized_client` - Client not authorized for this grant type\n- `unsupported_grant_type` - Grant type not supported\n- `invalid_scope` - Requested scope is invalid\n\n## Testing\n\n```bash\npnpm test\n```\n\nThe package includes comprehensive tests for:\n\n- Complete OAuth flows (PAR → authorize → token → refresh)\n- PKCE verification\n- DPoP proof validation\n- Client metadata discovery\n- Token rotation and revocation\n\n## License\n\nMIT\n\n## Related Packages\n\n- `@ascorbic/pds` - AT Protocol PDS implementation using this OAuth provider\n- `@atproto/oauth-client` - Official AT Protocol OAuth client\n- `@atproto/oauth-types` - TypeScript types for AT Protocol OAuth\n","readmeFilename":"README.md"}