Auto-indexing service and GraphQL API for AT Protocol Records
quickslice.slices.network/
atproto
gleam
graphql
1# Authentication
2
3Quickslice proxies OAuth between your app and users' Personal Data Servers (PDS). Your app never handles AT Protocol credentials directly.
4
5## How It Works
6
71. User clicks login in your app
82. Your app redirects to Quickslice's `/oauth/authorize` endpoint
93. Quickslice redirects to the user's PDS for authorization
104. User enters credentials and approves your app
115. PDS redirects back to Quickslice with an auth code
126. Quickslice exchanges the code for tokens
137. Quickslice redirects back to your app with a code
148. Your app exchanges the code for an access token
15
16The access token authorizes mutations that write to the user's repository.
17
18## Setting Up OAuth
19
20### Generate a Signing Key
21
22Quickslice needs a private key to sign OAuth tokens. Generate one with `goat`:
23
24```bash
25brew install goat
26goat key generate -t p256
27```
28
29Set the output as your `OAUTH_SIGNING_KEY` environment variable.
30
31### Register an OAuth Client
32
331. Open your Quickslice instance and navigate to **Settings**
342. Scroll to **OAuth Clients** and click **Register New Client**
353. Fill in the form:
36 - **Client Name**: Your app's name
37 - **Client Type**: Public (browser apps) or Confidential (server apps)
38 - **Redirect URIs**: Where users return after auth (e.g., `http://localhost:3000`)
39 - **Scope**: Leave as `atproto transition:generic`
404. Copy the **Client ID**
41
42### Public vs Confidential Clients
43
44| Type | Use Case | Secret |
45|------|----------|--------|
46| **Public** | Browser apps, mobile apps | No secret (client cannot secure it) |
47| **Confidential** | Server-side apps, backend services | Secret (stored securely on server) |
48
49## Using the Client SDK
50
51The Quickslice client SDK handles OAuth, PKCE, DPoP, token refresh, and GraphQL requests.
52
53### Install
54
55```bash
56npm install quickslice-client-js
57```
58
59Or via CDN:
60
61```html
62<script src="https://unpkg.com/quickslice-client-js/dist/quickslice-client.min.js"></script>
63```
64
65### Initialize
66
67```javascript
68import { createQuicksliceClient } from 'quickslice-client';
69
70const client = await createQuicksliceClient({
71 server: "https://yourapp.slices.network",
72 clientId: "YOUR_CLIENT_ID",
73});
74```
75
76### Login
77
78```javascript
79await client.loginWithRedirect({
80 handle: "alice.bsky.social",
81});
82```
83
84### Handle the Callback
85
86After authentication, the user returns to your redirect URI:
87
88```javascript
89if (window.location.search.includes("code=")) {
90 await client.handleRedirectCallback();
91}
92```
93
94### Check Authentication State
95
96```javascript
97const isLoggedIn = await client.isAuthenticated();
98
99if (isLoggedIn) {
100 const user = client.getUser();
101 console.log(user.did); // "did:plc:abc123..."
102}
103```
104
105### Logout
106
107```javascript
108await client.logout();
109```
110
111## Making Authenticated Requests
112
113### With the SDK
114
115The SDK adds authentication headers automatically:
116
117```javascript
118// Public query (no auth needed)
119const data = await client.publicQuery(`
120 query { xyzStatusphereStatus { edges { node { status } } } }
121`);
122
123// Authenticated query
124const viewer = await client.query(`
125 query { viewer { did handle } }
126`);
127
128// Mutation (requires auth)
129const result = await client.mutate(`
130 mutation { createXyzStatusphereStatus(input: { status: "🎉", createdAt: "${new Date().toISOString()}" }) { uri } }
131`);
132```
133
134### Without the SDK
135
136Without the SDK, include headers based on your OAuth flow:
137
138**DPoP flow** (public clients):
139```
140Authorization: DPoP <access_token>
141DPoP: <dpop_proof>
142```
143
144**Bearer token flow** (confidential clients):
145```
146Authorization: Bearer <access_token>
147```
148
149## The Viewer Query
150
151The `viewer` query returns the authenticated user:
152
153```graphql
154query {
155 viewer {
156 did
157 handle
158 appBskyActorProfileByDid {
159 displayName
160 avatar { url }
161 }
162 }
163}
164```
165
166Returns `null` when not authenticated (no error thrown).
167
168## Security: PKCE and DPoP
169
170The SDK implements two security mechanisms for browser apps:
171
172**PKCE (Proof Key for Code Exchange)** prevents authorization code interception. Before redirecting, the SDK generates a random secret and sends only its hash to the server. When exchanging the code for tokens, the SDK proves it initiated the request.
173
174**DPoP (Demonstrating Proof-of-Possession)** binds tokens to a cryptographic key in your browser. Each request includes a signed proof. An attacker who steals your access token cannot use it without the key.
175
176## OAuth Endpoints
177
178- `GET /oauth/authorize` - Start the OAuth flow
179- `POST /oauth/token` - Exchange authorization code for tokens
180- `GET /.well-known/oauth-authorization-server` - Server metadata
181- `GET /oauth/oauth-client-metadata.json` - Client metadata