OAuth is a standard, but every platform has its quirks. When Bluesky released their OAuth implementation, I decided to integrate it into my Next.js app to offer users a seamless login experience with their decentralized identity.
In this post, I’ll walk you through how I set up OAuth with Bluesky using @atproto/oauth-client-node, Prisma, and a clean abstraction layer. The full working example is available on GitHub.
pirmax
/
bluesky-oauth-nextjs
A modern, full-stack boilerplate for building web applications with Bluesky OAuth authentication using Next.js, Prisma, and PostgreSQL.
Bluesky OAuth Next.js Boilerplate
A modern, full-stack boilerplate for building web applications with Bluesky OAuth authentication using Next.js, Prisma, and PostgreSQL.
Features
- 🔐 Bluesky OAuth Authentication - Secure authentication using AT Protocol OAuth
- 🗄️ PostgreSQL Database - Robust database with Prisma ORM
- 🍪 Persistent Sessions - Session management with iron-session
- ⚡ Bun Runtime - Fast package management and development with Bun
- 🎨 Modern UI - Built with Tailwind CSS and custom components
- 🔧 TypeScript - Full type safety throughout the application
- 📦 Prisma Models - Pre-configured models for auth states and sessions
Tech Stack
- Framework: Next.js 15 with App Router
- Runtime: Bun
- Database: PostgreSQL with Prisma ORM
- Authentication: AT Protocol OAuth (@atproto/oauth-client-node)
- Session Management: iron-session
- Styling: Tailwind CSS
- Language: TypeScript
- Code Quality: Biome (ESLint + Prettier alternative)
Prerequisites
- Bun installed on your machine
- PostgreSQL database
- Environment variables configured
Getting Started
1. Clone
…Why Bluesky OAuth?
Bluesky’s AT Protocol (atproto) promotes portability and user autonomy. OAuth is their recommended way to authenticate users while preserving these values. Implementing it properly means integrating with their decentralized ID system while handling tokens securely.
What You’ll Need
- A Next.js project (preferably with the App Router)
- Prisma set up with a database
-
@atproto/oauth-client-nodeinstalled - Environment variable
NEXT_PUBLIC_URLset to your base app URL (e.g.,https://myapp.com)
Step 1: Create Client Metadata
Bluesky expects a client_metadata.json file, or programmatic client metadata, to describe your app. I chose to keep this in a utility function:
// lib/bluesky.ts
import { OAuthClientMetadataInput } from '@atproto/oauth-client-node'
export function blueskyClientMetadata(): OAuthClientMetadataInput {
const baseUrl: string = process.env.NEXT_PUBLIC_URL as string
return {
client_name: 'Project Name',
client_id: `${baseUrl}/client-metadata.json`,
client_uri: `${baseUrl}`,
redirect_uris: [`${baseUrl}/oauth/callback`],
policy_uri: `${baseUrl}/policy`,
tos_uri: `${baseUrl}/tos`,
scope: 'atproto transition:generic',
grant_types: ['authorization_code', 'refresh_token'],
response_types: ['code'],
application_type: 'web',
token_endpoint_auth_method: 'none',
dpop_bound_access_tokens: true,
}
}
This metadata is passed to the OAuth client during initialization.
Step 2: Create the OAuth Client
To persist sessions and states securely, I implemented custom stores using Prisma. Here’s how I wrapped it all into a createBlueskyClient function:
// lib/bluesky.ts
import { SessionStore, StateStore } from '@/lib/storage'
import {
NodeOAuthClient,
OAuthClientMetadataInput,
} from '@atproto/oauth-client-node'
import { PrismaClient } from '@prisma/client'
const createBlueskyClient = async (
prisma: PrismaClient
): Promise<NodeOAuthClient> =>
new NodeOAuthClient({
clientMetadata: blueskyClientMetadata(),
stateStore: new StateStore(prisma),
sessionStore: new SessionStore(prisma),
})
export default createBlueskyClient
The StateStore and SessionStore are classes that handle how you persist auth state (typically during the authorization flow) and session data (for refresh tokens, etc.). You can find example implementations in the GitHub repo.
Step 3: Set Up the Callback Route
In Next.js (App Router), create an API route to handle the callback logic:
// app/oauth/callback/route.ts
import createBlueskyClient from '@/lib/bluesky'
export async function GET(request: Request) {
const prisma = new PrismaClient()
const client = await createBlueskyClient(prisma)
const url = new URL(request.url)
const response = await client.handleCallback(url)
// Do something with the session (store user, start session, etc.)
console.log('User DID:', response.session.did)
return new Response('Login successful. You may close this tab.')
}
Conclusion
Bluesky OAuth is still relatively new and evolving, but the @atproto/oauth-client-node package abstracts a lot of the complexity. With Prisma as a backend for session management and a clean architecture, it’s straightforward to build a secure, standards-compliant OAuth flow for Bluesky.
You can find the full working project on GitHub here:
👉 https://github.com/pirmax/bluesky-oauth-nextjs
Feel free to fork, use it in your projects, or contribute improvements. Happy hacking!
Top comments (1)
If you would like to follow me on Bluesky, you can do so here: @pirmax.fr