a standard.site publication renderer for SvelteKit.
1# Content Verification
2
3Verification proves that you own the content you've published to ATProto. This is done through `.well-known` endpoints and `<link>` tags.
4
5## Why Verify?
6
7Verification allows platforms like Leaflet and WhiteWind to:
8
91. Confirm you control the content you claim to have published
102. Prevent impersonation
113. Enable features that require ownership proof
124. Build trust in the federated ecosystem
13
14## Quick Start
15
16### 1. Create .well-known Endpoint
17
18Create a SvelteKit endpoint at `.well-known/site.standard.publication`:
19
20```typescript
21// src/routes/.well-known/site.standard.publication/+server.ts
22import { text } from '@sveltejs/kit';
23import { generatePublicationWellKnown } from 'svelte-standard-site/verification';
24
25export function GET() {
26 return text(
27 generatePublicationWellKnown({
28 did: 'did:plc:your-did-here',
29 publicationRkey: '3abc123xyz789' // From publication creation
30 })
31 );
32}
33```
34
35### 2. Verify It Works
36
37```bash
38curl https://yourblog.com/.well-known/site.standard.publication
39# Should output: at://did:plc:xxx/site.standard.publication/3abc123xyz789
40```
41
42### 3. Add Link Tag (Optional)
43
44Add verification to individual documents:
45
46```svelte
47<!-- src/routes/blog/[slug]/+page.svelte -->
48<script lang="ts">
49 import { generateDocumentLinkTag } from 'svelte-standard-site/verification';
50
51 const { data } = $props();
52</script>
53
54<svelte:head>
55 {@html generateDocumentLinkTag({
56 did: 'did:plc:xxx',
57 documentRkey: data.rkey
58 })}
59</svelte:head>
60```
61
62## Functions
63
64### generatePublicationWellKnown
65
66Generate content for the `.well-known` endpoint:
67
68```typescript
69import { generatePublicationWellKnown } from 'svelte-standard-site/verification';
70
71const content = generatePublicationWellKnown({
72 did: 'did:plc:xxx',
73 publicationRkey: '3abc123xyz'
74});
75// Returns: "at://did:plc:xxx/site.standard.publication/3abc123xyz"
76```
77
78### generateDocumentLinkTag
79
80Generate a `<link>` tag for a specific document:
81
82```typescript
83import { generateDocumentLinkTag } from 'svelte-standard-site/verification';
84
85const tag = generateDocumentLinkTag({
86 did: 'did:plc:xxx',
87 documentRkey: '3xyz789abc'
88});
89// Returns: '<link rel="site.standard.document" href="at://...">'
90```
91
92### generatePublicationLinkTag
93
94Generate a `<link>` tag for your publication:
95
96```typescript
97import { generatePublicationLinkTag } from 'svelte-standard-site/verification';
98
99const tag = generatePublicationLinkTag({
100 did: 'did:plc:xxx',
101 publicationRkey: '3abc123xyz'
102});
103```
104
105### verifyPublicationWellKnown
106
107Programmatically verify a site's `.well-known` endpoint:
108
109```typescript
110import { verifyPublicationWellKnown } from 'svelte-standard-site/verification';
111
112const isValid = await verifyPublicationWellKnown(
113 'https://example.com',
114 'did:plc:xxx',
115 '3abc123xyz'
116);
117
118if (isValid) {
119 console.log('Site is verified!');
120}
121```
122
123## Complete Setup
124
125### Get Your Publication Rkey
126
127When you create a publication, save the rkey:
128
129```typescript
130import { StandardSitePublisher } from 'svelte-standard-site/publisher';
131
132const publisher = new StandardSitePublisher({
133 identifier: 'you.bsky.social',
134 password: process.env.ATPROTO_APP_PASSWORD!
135});
136
137await publisher.login();
138
139const result = await publisher.publishPublication({
140 name: 'My Blog',
141 url: 'https://yourblog.com'
142});
143
144// Save these!
145console.log('DID:', publisher.getDid());
146console.log('Rkey:', result.uri.split('/').pop());
147```
148
149### Store in Environment Variables
150
151```env
152# .env
153PUBLIC_ATPROTO_DID=did:plc:xxx
154PUBLIC_PUBLICATION_RKEY=3abc123xyz
155```
156
157### Create Endpoint
158
159```typescript
160// src/routes/.well-known/site.standard.publication/+server.ts
161import { text } from '@sveltejs/kit';
162import { generatePublicationWellKnown } from 'svelte-standard-site/verification';
163import { PUBLIC_ATPROTO_DID, PUBLIC_PUBLICATION_RKEY } from '$env/static/public';
164
165export function GET() {
166 return text(
167 generatePublicationWellKnown({
168 did: PUBLIC_ATPROTO_DID,
169 publicationRkey: PUBLIC_PUBLICATION_RKEY
170 }),
171 {
172 headers: {
173 'Content-Type': 'text/plain',
174 'Cache-Control': 'public, max-age=3600'
175 }
176 }
177 );
178}
179```
180
181### Add to Site Header
182
183```svelte
184<!-- src/routes/+layout.svelte -->
185<script lang="ts">
186 import { generatePublicationLinkTag } from 'svelte-standard-site/verification';
187 import { PUBLIC_ATPROTO_DID, PUBLIC_PUBLICATION_RKEY } from '$env/static/public';
188</script>
189
190<svelte:head>
191 {@html generatePublicationLinkTag({
192 did: PUBLIC_ATPROTO_DID,
193 publicationRkey: PUBLIC_PUBLICATION_RKEY
194 })}
195</svelte:head>
196```
197
198## Document Verification
199
200For individual blog posts:
201
202### Store Document Rkeys
203
204When publishing, save the mapping:
205
206```typescript
207// In your publish script
208const result = await publisher.publishDocument({
209 // ... document data
210});
211
212// Save mapping: slug -> rkey
213const mapping = {
214 'my-first-post': result.uri.split('/').pop(),
215 'another-post': '3xyz789abc'
216 // etc.
217};
218
219fs.writeFileSync('document-rkeys.json', JSON.stringify(mapping));
220```
221
222### Add to Document Pages
223
224```svelte
225<!-- src/routes/blog/[slug]/+page.svelte -->
226<script lang="ts">
227 import { generateDocumentLinkTag } from 'svelte-standard-site/verification';
228 import documentRkeys from '$lib/document-rkeys.json';
229
230 const { data } = $props();
231 const rkey = documentRkeys[data.slug];
232</script>
233
234<svelte:head>
235 {#if rkey}
236 {@html generateDocumentLinkTag({
237 did: 'did:plc:xxx',
238 documentRkey: rkey
239 })}
240 {/if}
241</svelte:head>
242```
243
244## AT-URI Utilities
245
246### Build AT-URIs
247
248```typescript
249import { getDocumentAtUri, getPublicationAtUri } from 'svelte-standard-site/verification';
250
251const docUri = getDocumentAtUri('did:plc:xxx', '3xyz789abc');
252// "at://did:plc:xxx/site.standard.document/3xyz789abc"
253
254const pubUri = getPublicationAtUri('did:plc:xxx', '3abc123xyz');
255// "at://did:plc:xxx/site.standard.publication/3abc123xyz"
256```
257
258### Parse AT-URIs
259
260```typescript
261import { parseAtUri } from 'svelte-standard-site/verification';
262
263const parsed = parseAtUri('at://did:plc:xxx/site.standard.document/3xyz');
264
265if (parsed) {
266 console.log(parsed.did); // "did:plc:xxx"
267 console.log(parsed.collection); // "site.standard.document"
268 console.log(parsed.rkey); // "3xyz"
269}
270```
271
272### Extract from HTML
273
274```typescript
275import { extractDocumentLinkFromHtml, extractPublicationLinkFromHtml } from 'svelte-standard-site/verification';
276
277const html = await fetch('https://example.com/post').then((r) => r.text());
278
279const docUri = extractDocumentLinkFromHtml(html);
280// "at://did:plc:xxx/site.standard.document/3xyz"
281
282const pubUri = extractPublicationLinkFromHtml(html);
283// "at://did:plc:xxx/site.standard.publication/3abc"
284```
285
286## Verification Flow
287
2881. **Publish** a publication to ATProto
2892. **Save** the DID and rkey
2903. **Create** `.well-known` endpoint returning the AT-URI
2914. **Optionally** add `<link>` tags to your HTML
2925. **Platforms** fetch your `.well-known` endpoint to verify ownership
293
294```mermaid
295sequenceDiagram
296 participant You
297 participant ATProto
298 participant Platform
299
300 You->>ATProto: Publish publication
301 ATProto->>You: Return AT-URI
302 You->>Your Site: Add .well-known endpoint
303 Platform->>Your Site: Fetch .well-known
304 Your Site->>Platform: Return AT-URI
305 Platform->>ATProto: Verify AT-URI exists
306 ATProto->>Platform: Confirmed
307 Platform->>Platform: Mark as verified ✓
308```
309
310## Troubleshooting
311
312### .well-known Returning 404
313
314SvelteKit requires special handling for `.well-known`:
315
316```typescript
317// Option 1: Create the exact path
318// src/routes/.well-known/site.standard.publication/+server.ts
319
320// Option 2: Use static files
321// static/.well-known/site.standard.publication
322```
323
324If using static files, make sure your hosting platform allows `.well-known`.
325
326### Wrong MIME Type
327
328Ensure you're returning `text/plain`:
329
330```typescript
331export function GET() {
332 return text(content, {
333 headers: {
334 'Content-Type': 'text/plain'
335 }
336 });
337}
338```
339
340### CORS Issues
341
342If platforms can't access your endpoint:
343
344```typescript
345export function GET() {
346 return text(content, {
347 headers: {
348 'Content-Type': 'text/plain',
349 'Access-Control-Allow-Origin': '*'
350 }
351 });
352}
353```
354
355### Verification Failing
356
357Use the verification utility to test:
358
359```typescript
360const isValid = await verifyPublicationWellKnown(
361 'https://yourblog.com',
362 'did:plc:xxx',
363 '3abc123xyz'
364);
365
366if (!isValid) {
367 // Check:
368 // 1. .well-known endpoint is accessible
369 // 2. Returns exact AT-URI
370 // 3. No extra whitespace
371 // 4. Correct MIME type
372}
373```
374
375## Best Practices
376
3771. **Use environment variables** - Don't hardcode DIDs/rkeys
3782. **Add caching headers** - `.well-known` content doesn't change often
3793. **Test before deploying** - Verify the endpoint works
3804. **Keep rkeys secure** - Don't expose in client code unnecessarily
3815. **Monitor** - Check that verification keeps working after deploys
382
383## Hosting Platform Notes
384
385### Vercel
386
387Works out of the box. No special configuration needed.
388
389### Netlify
390
391Add to `netlify.toml`:
392
393```toml
394[[redirects]]
395 from = "/.well-known/site.standard.publication"
396 to = "/.well-known/site.standard.publication/index.html"
397 status = 200
398```
399
400### Cloudflare Pages
401
402Works by default. Consider adding a cache rule for `.well-known`.
403
404### GitHub Pages
405
406Static files work, but SvelteKit endpoints don't. Use the static file approach.
407
408## Next Steps
409
410- [Publishing](./publishing.md)
411- [Content Transformation](./content-transformation.md)
412- [Comments](./comments.md)