
Security News
CVE Volume Surges Past 48,000 in 2025 as WordPress Plugin Ecosystem Drives Growth
CVE disclosures hit a record 48,185 in 2025, driven largely by vulnerabilities in third-party WordPress plugins.
@ascorbic/atproto-oauth-provider
Advanced tools
OAuth 2.1 Provider with AT Protocol extensions for Cloudflare Workers
🚨 This package has been renamed to
@getcirrus/oauth-providerThis package is deprecated and will no longer receive updates. Please migrate to
@getcirrus/oauth-providerfor the latest features and bug fixes.
AT Protocol OAuth 2.1 Authorization Server for Cloudflare Workers.
A complete OAuth 2.1 provider implementation that enables "Login with Bluesky" functionality for your PDS. Built specifically for Cloudflare Workers with Durable Objects.
client_id URL resolutionnpm install @ascorbic/atproto-oauth-provider
# or
pnpm add @ascorbic/atproto-oauth-provider
import { OAuthProvider } from "@ascorbic/atproto-oauth-provider";
import { OAuthStorage } from "./your-storage-implementation";
// Initialize the provider
const provider = new OAuthProvider({
issuer: "https://your-pds.example.com",
storage: new OAuthStorage(),
});
// Handle OAuth endpoints in your Worker
app.post("/oauth/par", async (c) => {
const result = await provider.handlePAR(await c.req.formData());
return c.json(result);
});
app.get("/oauth/authorize", async (c) => {
const result = await provider.handleAuthorize(c.req.url);
// Show authorization UI to user
return c.html(renderAuthUI(result));
});
app.post("/oauth/token", async (c) => {
const result = await provider.handleToken(
await c.req.formData(),
c.req.header("DPoP"),
);
return c.json(result);
});
The OAuthProvider class is the main entry point. It handles:
The provider uses a storage interface that you implement for your backend:
export interface OAuthProviderStorage {
// Authorization codes
saveAuthCode(code: string, data: AuthCodeData): Promise<void>;
getAuthCode(code: string): Promise<AuthCodeData | null>;
deleteAuthCode(code: string): Promise<void>;
// Access/refresh tokens
saveTokens(data: TokenData): Promise<void>;
getTokenByAccess(accessToken: string): Promise<TokenData | null>;
getTokenByRefresh(refreshToken: string): Promise<TokenData | null>;
revokeToken(accessToken: string): Promise<void>;
revokeAllTokens(sub: string): Promise<void>;
// Client metadata cache
saveClient(clientId: string, metadata: ClientMetadata): Promise<void>;
getClient(clientId: string): Promise<ClientMetadata | null>;
// PAR (Pushed Authorization Requests)
savePAR(requestUri: string, data: PARData): Promise<void>;
getPAR(requestUri: string): Promise<PARData | null>;
deletePAR(requestUri: string): Promise<void>;
// DPoP nonce tracking
checkAndSaveNonce(nonce: string): Promise<boolean>;
}
A SQLite implementation for Durable Objects is included in the @ascorbic/pds package.
Client initiates the flow by pushing authorization parameters to the server:
POST /oauth/par
Content-Type: application/x-www-form-urlencoded
client_id=https://client.example.com/client-metadata.json
&code_challenge=XXXXXX
&code_challenge_method=S256
&redirect_uri=https://client.example.com/callback
&scope=atproto
&state=random-state
Response:
{
"request_uri": "urn:ietf:params:oauth:request_uri:XXXXXX",
"expires_in": 90
}
User is redirected to authorize the client:
GET /oauth/authorize?request_uri=urn:ietf:params:oauth:request_uri:XXXXXX
After user approves, they're redirected back with an authorization code:
HTTP/1.1 302 Found
Location: https://client.example.com/callback?code=XXXXXX&state=random-state
Client exchanges the authorization code for tokens:
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
DPoP: <dpop-proof-jwt>
grant_type=authorization_code
&code=XXXXXX
&redirect_uri=https://client.example.com/callback
&code_verifier=YYYYYY
&client_id=https://client.example.com/client-metadata.json
Response:
{
"access_token": "XXXXXX",
"token_type": "DPoP",
"expires_in": 3600,
"refresh_token": "YYYYYY",
"scope": "atproto",
"sub": "did:plc:abc123"
}
All authorization flows require PKCE to prevent authorization code interception attacks:
code_verifier (random string)code_challengecode_verifier matches during token exchangeBinds tokens to specific clients using cryptographic proofs:
Clients are identified by a URL pointing to their metadata document:
{
"client_id": "https://client.example.com/client-metadata.json",
"client_name": "Example App",
"redirect_uris": ["https://client.example.com/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"scope": "atproto",
"token_endpoint_auth_method": "none",
"application_type": "web"
}
The provider automatically fetches and validates client metadata from the client_id URL.
This provider is designed to work seamlessly with @atproto/oauth-client:
// Client side
import { OAuthClient } from "@atproto/oauth-client";
const client = new OAuthClient({
clientMetadata: {
client_id: "https://my-app.example.com/client-metadata.json",
redirect_uris: ["https://my-app.example.com/callback"],
},
});
// Initiate login
const authUrl = await client.authorize("https://user-pds.example.com", {
scope: "atproto",
});
// Handle callback
const { session } = await client.callback(callbackParams);
The provider returns standard OAuth 2.1 error responses:
{
"error": "invalid_request",
"error_description": "Missing required parameter: code_challenge"
}
Common error codes:
invalid_request - Malformed requestinvalid_client - Client authentication failedinvalid_grant - Invalid authorization code or refresh tokenunauthorized_client - Client not authorized for this grant typeunsupported_grant_type - Grant type not supportedinvalid_scope - Requested scope is invalidpnpm test
The package includes comprehensive tests for:
MIT
@ascorbic/pds - AT Protocol PDS implementation using this OAuth provider@atproto/oauth-client - Official AT Protocol OAuth client@atproto/oauth-types - TypeScript types for AT Protocol OAuthFAQs
OAuth 2.1 Provider with AT Protocol extensions for Cloudflare Workers
We found that @ascorbic/atproto-oauth-provider demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
CVE disclosures hit a record 48,185 in 2025, driven largely by vulnerabilities in third-party WordPress plugins.

Security News
Socket CEO Feross Aboukhadijeh joins Insecure Agents to discuss CVE remediation and why supply chain attacks require a different security approach.

Security News
Tailwind Labs laid off 75% of its engineering team after revenue dropped 80%, as LLMs redirect traffic away from documentation where developers discover paid products.