···11-# Astro Starter Kit: Minimal
11+# Astro ATProto OAuth Starter
22+33+A minimal [Astro](https://astro.build) starter template demonstrating OAuth authentication with AT Protocol (ATProto), the decentralized social networking protocol used by Bluesky and other services.
44+55+This starter includes:
66+- Complete OAuth authentication flow using `@atproto/oauth-client-node`
77+- Cookie-based session management
88+- Profile display after authentication
99+- Login/logout endpoints
1010+- Tailwind CSS and DaisyUI styling
1111+1212+## 🚀 Getting Started
1313+1414+1. **Install dependencies:**
1515+ ```sh
1616+ npm install
1717+ ```
21833-```sh
44-npm create astro@latest -- --template minimal
55-```
1919+2. **Configure environment variables:**
2020+ ```sh
2121+ cp .env.template .env
2222+ ```
2323+ Edit `.env` if you need to change the port (default: 4321) or set a public URL.
62477-> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
2525+3. **Start the development server:**
2626+ ```sh
2727+ npm run dev
2828+ ```
2929+ The app will be available at `http://localhost:4321`
83099-## 🚀 Project Structure
3131+4. **Try logging in:**
3232+ Enter your AT Protocol handle (e.g., `alice.bsky.social`) to authenticate.
10331111-Inside of your Astro project, you'll see the following folders and files:
3434+## 📁 Project Structure
12351336```text
1437/
1538├── public/
1639├── src/
1717-│ └── pages/
1818-│ └── index.astro
4040+│ ├── lib/
4141+│ │ ├── context.ts # OAuth client singleton
4242+│ │ ├── oauth.ts # OAuth client configuration
4343+│ │ ├── session.ts # Session management
4444+│ │ └── storage.ts # Cookie-based stores
4545+│ ├── pages/
4646+│ │ ├── api/
4747+│ │ │ ├── login.ts # Login endpoint
4848+│ │ │ ├── logout.ts # Logout endpoint
4949+│ │ │ └── oauth/
5050+│ │ │ └── callback.ts # OAuth callback handler
5151+│ │ └── index.astro # Main page with login UI
5252+│ └── styles.css
1953└── package.json
2054```
2121-2222-Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
2323-2424-There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
2525-2626-Any static assets, like images, can be placed in the `public/` directory.
27552856## 🧞 Commands
2957···3866| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
3967| `npm run astro -- --help` | Get help using the Astro CLI |
40684141-## 👀 Want to learn more?
6969+## 📚 Learn More
42704343-Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
7171+- [Astro Documentation](https://docs.astro.build)
7272+- [AT Protocol Documentation](https://atproto.com)
7373+- [@atproto/oauth-client-node](https://github.com/bluesky-social/atproto/tree/main/packages/oauth/oauth-client-node)
7474+- [Bluesky](https://bsky.app)
+4-14
src/lib/context.ts
···11-import { NodeOAuthClient } from '@atproto/oauth-client-node'
11+import type { AstroCookies } from 'astro'
22import { createOAuthClient } from './oauth'
3344-export type AppContext = {
55- oauthClient: NodeOAuthClient
66-}
77-88-let _ctx: AppContext | null = null
99-1010-export async function getAppContext(): Promise<AppContext> {
1111- if (_ctx) return _ctx
1212-1313- const oauthClient = await createOAuthClient()
1414-1515- _ctx = { oauthClient }
1616- return _ctx
44+// Create a request-scoped OAuth client with cookie-based storage
55+export function getOAuthClient(cookies: AstroCookies) {
66+ return createOAuthClient(cookies)
177}
+5-4
src/lib/oauth.ts
···11+import type { AstroCookies } from 'astro'
12import {
23 atprotoLoopbackClientMetadata,
34 NodeOAuthClient,
45} from "@atproto/oauth-client-node";
56import { env } from "./env";
66-import { SessionStore, StateStore } from "./storage";
77+import { CookieSessionStore, CookieStateStore } from "./storage";
7888-export async function createOAuthClient() {
99+export function createOAuthClient(cookies: AstroCookies) {
910 const clientMetadata = atprotoLoopbackClientMetadata(
1011 `http://localhost?${new URLSearchParams([
1112 ["redirect_uri", `http://127.0.0.1:${env.PORT}/api/oauth/callback`],
···15161617 return new NodeOAuthClient({
1718 clientMetadata,
1818- stateStore: new StateStore(),
1919- sessionStore: new SessionStore(),
1919+ stateStore: new CookieStateStore(cookies),
2020+ sessionStore: new CookieSessionStore(cookies),
2021 });
2122}