I've been fighting my way through this same set of issues. I'm not all the way there yet, but here's a summary of what I've found so far. Depending on what language you use, there are some working examples out there. Two that helped me were: https://github.com/bluesky-social/cookbook/tree/main/python-oauth-web-app and https://pkg.go.dev/github.com/potproject/atproto-oauth2-go-example.
Authenticating with Bluesky using OAuth is significantly more complex than traditional OAuth implementations due to Bluesky's use of advanced security mechanisms like DPoP (Demonstrating Proof of Possession) and their multi-tiered API architecture. This guide outlines the key requirements and steps for successfully implementing OAuth authentication with Bluesky.
Bluesky's API consists of two main components:
Important: OAuth tokens can only be used with PDS endpoints, not with AppView endpoints.
Authorization: DPoP <token>
(not Bearer <token>
)/xrpc/com.atproto.*
)/xrpc/app.bsky.*
)/xrpc/com.atproto.repo.getRecord
) with appropriate parametersCreate a client metadata JSON file available at a public HTTPS URL:
{
"client_id": "https://your-app.com/.well-known/bluesky-oauth.json",
"application_type": "web",
"client_name": "Your App Name",
"client_uri": "https://your-app.com",
"dpop_bound_access_tokens": true,
"require_pkce": true,
"grant_types": [
"authorization_code",
"refresh_token"
],
"redirect_uris": [
"https://your-app.com/auth/bluesky/callback"
],
"response_types": [
"code"
],
"scope": "atproto transition:generic transition:chat.bsky",
"token_endpoint_auth_method": "none"
}
client_id
(URL to your metadata JSON)redirect_uri
response_type
= "code"scope
= "atproto transition:generic transition:chat.bsky"code_challenge
and code_challenge_method
= "S256"state
(security token)grant_type
= "authorization_code"code
= received code from authorizationredirect_uri
= same as in authorization requestcode_verifier
= PKCE verifier from step 2client_id
= URL to your metadata JSONDPoP
header with the DPoP tokenAuthorization: DPoP <access_token>
in the header/xrpc/com.atproto.*
)To get user profile info:
GET /xrpc/com.atproto.repo.getRecord?repo=<user-did>&collection=app.bsky.actor.profile&rkey=self
The response structure is complex, with nested objects:
{
"uri": "at://did:plc:abc123/app.bsky.actor.profile/self",
"cid": "bafyreiabc123...",
"value": {
"$type": "app.bsky.actor.profile",
"displayName": "User Name",
"description": "Bio text",
"avatar": {
"$type": "blob",
"ref": {
"$link": "bafkreiabc123..."
},
"mimeType": "image/jpeg",
"size": 12345
}
}
}
"Bad token scope" error:
DPoP
(not Bearer
) in the Authorization headerDPoP nonce issues:
Avatar image access:
Avatar URLs must be constructed from the response:
<endpoint>/xrpc/com.atproto.sync.getBlob?did=<user-did>&cid=<avatar-cid>
Handle retrieval:
The OAuth implementation in Bluesky currently has these limitations:
For full API access, you may need to implement a hybrid approach using both OAuth for PDS endpoints and other authentication methods for AppView endpoints.