···11+# Deno
22+deno.lock
33+44+# IDE
55+.vscode/
66+.idea/
77+88+# OS
99+.DS_Store
+21
CHANGELOG.md
···11+# Changelog
22+33+All notable changes to this project will be documented in this file.
44+55+## [0.1.0] - 2025-11-27
66+77+### Added
88+99+- Initial release
1010+- `createATProtoOAuth()` factory function for complete OAuth integration
1111+- Framework-agnostic route handlers using standard Request/Response APIs:
1212+ - `handleLogin()` - Start OAuth flow
1313+ - `handleCallback()` - Complete OAuth flow
1414+ - `handleClientMetadata()` - Serve OAuth client metadata
1515+ - `handleLogout()` - Log out and clear session
1616+- `getSessionFromRequest()` for getting authenticated sessions with cookie
1717+ refresh
1818+- `OAuthSessions` class for direct session management
1919+- Support for both web (cookie) and mobile (Bearer token) authentication
2020+- Automatic token refresh via `@tijs/oauth-client-deno`
2121+- Type exports for `SessionInterface`, `ATProtoOAuthConfig`, etc.
+21
LICENSE
···11+MIT License
22+33+Copyright (c) 2025 Tijs Teulings
44+55+Permission is hereby granted, free of charge, to any person obtaining a copy
66+of this software and associated documentation files (the "Software"), to deal
77+in the Software without restriction, including without limitation the rights
88+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99+copies of the Software, and to permit persons to whom the Software is
1010+furnished to do so, subject to the following conditions:
1111+1212+The above copyright notice and this permission notice shall be included in all
1313+copies or substantial portions of the Software.
1414+1515+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1616+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1717+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1818+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1919+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2020+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121+SOFTWARE.
+200
README.md
···11+# @tijs/atproto-oauth
22+33+Framework-agnostic OAuth integration for AT Protocol (Bluesky) applications.
44+Works with standard Web Request/Response APIs - no framework dependencies.
55+66+## Installation
77+88+```typescript
99+import { createATProtoOAuth } from "jsr:@tijs/atproto-oauth";
1010+import { SQLiteStorage, valTownAdapter } from "jsr:@tijs/atproto-storage";
1111+```
1212+1313+## Usage
1414+1515+### Basic Setup
1616+1717+```typescript
1818+import { createATProtoOAuth } from "jsr:@tijs/atproto-oauth";
1919+import { SQLiteStorage, valTownAdapter } from "jsr:@tijs/atproto-storage";
2020+import { sqlite } from "https://esm.town/v/std/sqlite";
2121+2222+const oauth = createATProtoOAuth({
2323+ baseUrl: "https://myapp.example.com",
2424+ appName: "My App",
2525+ cookieSecret: Deno.env.get("COOKIE_SECRET")!,
2626+ storage: new SQLiteStorage(valTownAdapter(sqlite)),
2727+ sessionTtl: 60 * 60 * 24 * 14, // 14 days
2828+});
2929+```
3030+3131+### Hono Integration
3232+3333+```typescript
3434+import { Hono } from "hono";
3535+3636+const app = new Hono();
3737+3838+// Mount OAuth routes
3939+app.get("/login", (c) => oauth.handleLogin(c.req.raw));
4040+app.get("/oauth/callback", (c) => oauth.handleCallback(c.req.raw));
4141+app.get("/oauth-client-metadata.json", () => oauth.handleClientMetadata());
4242+app.post("/api/auth/logout", (c) => oauth.handleLogout(c.req.raw));
4343+4444+// Protected route example
4545+app.get("/api/profile", async (c) => {
4646+ const { session, setCookieHeader, error } = await oauth.getSessionFromRequest(
4747+ c.req.raw,
4848+ );
4949+5050+ if (!session) {
5151+ return c.json({ error: error?.message || "Not authenticated" }, 401);
5252+ }
5353+5454+ // Make authenticated API call
5555+ const response = await session.makeRequest(
5656+ "GET",
5757+ `${session.pdsUrl}/xrpc/app.bsky.actor.getProfile?actor=${session.did}`,
5858+ );
5959+6060+ const profile = await response.json();
6161+6262+ const res = c.json(profile);
6363+ if (setCookieHeader) {
6464+ res.headers.set("Set-Cookie", setCookieHeader);
6565+ }
6666+ return res;
6767+});
6868+```
6969+7070+### Fresh (Deno) Integration
7171+7272+```typescript
7373+// routes/login.ts
7474+export const handler = async (req: Request) => {
7575+ return oauth.handleLogin(req);
7676+};
7777+7878+// routes/oauth/callback.ts
7979+export const handler = async (req: Request) => {
8080+ return oauth.handleCallback(req);
8181+};
8282+```
8383+8484+### Direct Access to Sessions
8585+8686+For advanced use cases, you can access the sessions manager directly:
8787+8888+```typescript
8989+// Get session by DID
9090+const session = await oauth.sessions.getOAuthSession(did);
9191+if (session) {
9292+ const response = await session.makeRequest("GET", url);
9393+}
9494+9595+// Save session manually
9696+await oauth.sessions.saveOAuthSession(session);
9797+9898+// Delete session
9999+await oauth.sessions.deleteOAuthSession(did);
100100+```
101101+102102+## Configuration
103103+104104+```typescript
105105+interface ATProtoOAuthConfig {
106106+ /** Base URL of your application */
107107+ baseUrl: string;
108108+109109+ /** Display name for OAuth consent screen */
110110+ appName: string;
111111+112112+ /** Cookie signing secret (at least 32 characters) */
113113+ cookieSecret: string;
114114+115115+ /** Storage implementation for OAuth sessions */
116116+ storage: OAuthStorage;
117117+118118+ /** Session TTL in seconds (default: 7 days) */
119119+ sessionTtl?: number;
120120+121121+ /** URL to app logo for OAuth consent screen */
122122+ logoUri?: string;
123123+124124+ /** URL to privacy policy */
125125+ policyUri?: string;
126126+127127+ /** OAuth scope (default: "atproto transition:generic") */
128128+ scope?: string;
129129+130130+ /** Mobile app callback scheme (default: "app://auth-callback") */
131131+ mobileScheme?: string;
132132+133133+ /** Logger for debugging (default: no-op) */
134134+ logger?: Logger;
135135+}
136136+```
137137+138138+## API
139139+140140+### `createATProtoOAuth(config)`
141141+142142+Creates an OAuth instance with the following methods:
143143+144144+- `handleLogin(request)` - Start OAuth flow (redirect to provider)
145145+- `handleCallback(request)` - Complete OAuth flow (handle callback)
146146+- `handleClientMetadata()` - Return OAuth client metadata JSON
147147+- `handleLogout(request)` - Log out and clear session
148148+- `getSessionFromRequest(request)` - Get authenticated session from request
149149+- `getClientMetadata()` - Get client metadata object
150150+- `sessions` - Access to session management interface
151151+152152+### Session Result
153153+154154+`getSessionFromRequest()` returns:
155155+156156+```typescript
157157+{
158158+ session: SessionInterface | null;
159159+ setCookieHeader?: string; // Set this on response to refresh session
160160+ error?: {
161161+ type: "NO_COOKIE" | "INVALID_COOKIE" | "SESSION_EXPIRED" | "OAUTH_ERROR" | "UNKNOWN";
162162+ message: string;
163163+ details?: unknown;
164164+ };
165165+}
166166+```
167167+168168+### SessionInterface
169169+170170+The session object provides:
171171+172172+```typescript
173173+interface SessionInterface {
174174+ did: string; // User's DID
175175+ handle?: string; // User's handle
176176+ pdsUrl: string; // User's PDS URL
177177+ accessToken: string;
178178+ refreshToken?: string;
179179+180180+ // Make authenticated requests with automatic DPoP handling
181181+ makeRequest(
182182+ method: string,
183183+ url: string,
184184+ options?: RequestInit,
185185+ ): Promise<Response>;
186186+}
187187+```
188188+189189+## Related Packages
190190+191191+- [@tijs/atproto-storage](https://jsr.io/@tijs/atproto-storage) - Storage
192192+ implementations
193193+- [@tijs/atproto-sessions](https://jsr.io/@tijs/atproto-sessions) - Session
194194+ cookie management
195195+- [@tijs/oauth-client-deno](https://jsr.io/@tijs/oauth-client-deno) - AT
196196+ Protocol OAuth client
197197+198198+## License
199199+200200+MIT