# Content Verification Verification proves that you own the content you've published to ATProto. This is done through `.well-known` endpoints and `` tags. ## Why Verify? Verification allows platforms like Leaflet and WhiteWind to: 1. Confirm you control the content you claim to have published 2. Prevent impersonation 3. Enable features that require ownership proof 4. Build trust in the federated ecosystem ## Quick Start ### 1. Create .well-known Endpoint Create a SvelteKit endpoint at `.well-known/site.standard.publication`: ```typescript // src/routes/.well-known/site.standard.publication/+server.ts import { text } from '@sveltejs/kit'; import { generatePublicationWellKnown } from 'svelte-standard-site/verification'; export function GET() { return text( generatePublicationWellKnown({ did: 'did:plc:your-did-here', publicationRkey: '3abc123xyz789' // From publication creation }) ); } ``` ### 2. Verify It Works ```bash curl https://yourblog.com/.well-known/site.standard.publication # Should output: at://did:plc:xxx/site.standard.publication/3abc123xyz789 ``` ### 3. Add Link Tag (Optional) Add verification to individual documents: ```svelte {@html generateDocumentLinkTag({ did: 'did:plc:xxx', documentRkey: data.rkey })} ``` ## Functions ### generatePublicationWellKnown Generate content for the `.well-known` endpoint: ```typescript import { generatePublicationWellKnown } from 'svelte-standard-site/verification'; const content = generatePublicationWellKnown({ did: 'did:plc:xxx', publicationRkey: '3abc123xyz' }); // Returns: "at://did:plc:xxx/site.standard.publication/3abc123xyz" ``` ### generateDocumentLinkTag Generate a `` tag for a specific document: ```typescript import { generateDocumentLinkTag } from 'svelte-standard-site/verification'; const tag = generateDocumentLinkTag({ did: 'did:plc:xxx', documentRkey: '3xyz789abc' }); // Returns: '' ``` ### generatePublicationLinkTag Generate a `` tag for your publication: ```typescript import { generatePublicationLinkTag } from 'svelte-standard-site/verification'; const tag = generatePublicationLinkTag({ did: 'did:plc:xxx', publicationRkey: '3abc123xyz' }); ``` ### verifyPublicationWellKnown Programmatically verify a site's `.well-known` endpoint: ```typescript import { verifyPublicationWellKnown } from 'svelte-standard-site/verification'; const isValid = await verifyPublicationWellKnown( 'https://example.com', 'did:plc:xxx', '3abc123xyz' ); if (isValid) { console.log('Site is verified!'); } ``` ## Complete Setup ### Get Your Publication Rkey When you create a publication, save the rkey: ```typescript import { StandardSitePublisher } from 'svelte-standard-site/publisher'; const publisher = new StandardSitePublisher({ identifier: 'you.bsky.social', password: process.env.ATPROTO_APP_PASSWORD! }); await publisher.login(); const result = await publisher.publishPublication({ name: 'My Blog', url: 'https://yourblog.com' }); // Save these! console.log('DID:', publisher.getDid()); console.log('Rkey:', result.uri.split('/').pop()); ``` ### Store in Environment Variables ```env # .env PUBLIC_ATPROTO_DID=did:plc:xxx PUBLIC_PUBLICATION_RKEY=3abc123xyz ``` ### Create Endpoint ```typescript // src/routes/.well-known/site.standard.publication/+server.ts import { text } from '@sveltejs/kit'; import { generatePublicationWellKnown } from 'svelte-standard-site/verification'; import { PUBLIC_ATPROTO_DID, PUBLIC_PUBLICATION_RKEY } from '$env/static/public'; export function GET() { return text( generatePublicationWellKnown({ did: PUBLIC_ATPROTO_DID, publicationRkey: PUBLIC_PUBLICATION_RKEY }), { headers: { 'Content-Type': 'text/plain', 'Cache-Control': 'public, max-age=3600' } } ); } ``` ### Add to Site Header ```svelte {@html generatePublicationLinkTag({ did: PUBLIC_ATPROTO_DID, publicationRkey: PUBLIC_PUBLICATION_RKEY })} ``` ## Document Verification For individual blog posts: ### Store Document Rkeys When publishing, save the mapping: ```typescript // In your publish script const result = await publisher.publishDocument({ // ... document data }); // Save mapping: slug -> rkey const mapping = { 'my-first-post': result.uri.split('/').pop(), 'another-post': '3xyz789abc' // etc. }; fs.writeFileSync('document-rkeys.json', JSON.stringify(mapping)); ``` ### Add to Document Pages ```svelte {#if rkey} {@html generateDocumentLinkTag({ did: 'did:plc:xxx', documentRkey: rkey })} {/if} ``` ## AT-URI Utilities ### Build AT-URIs ```typescript import { getDocumentAtUri, getPublicationAtUri } from 'svelte-standard-site/verification'; const docUri = getDocumentAtUri('did:plc:xxx', '3xyz789abc'); // "at://did:plc:xxx/site.standard.document/3xyz789abc" const pubUri = getPublicationAtUri('did:plc:xxx', '3abc123xyz'); // "at://did:plc:xxx/site.standard.publication/3abc123xyz" ``` ### Parse AT-URIs ```typescript import { parseAtUri } from 'svelte-standard-site/verification'; const parsed = parseAtUri('at://did:plc:xxx/site.standard.document/3xyz'); if (parsed) { console.log(parsed.did); // "did:plc:xxx" console.log(parsed.collection); // "site.standard.document" console.log(parsed.rkey); // "3xyz" } ``` ### Extract from HTML ```typescript import { extractDocumentLinkFromHtml, extractPublicationLinkFromHtml } from 'svelte-standard-site/verification'; const html = await fetch('https://example.com/post').then((r) => r.text()); const docUri = extractDocumentLinkFromHtml(html); // "at://did:plc:xxx/site.standard.document/3xyz" const pubUri = extractPublicationLinkFromHtml(html); // "at://did:plc:xxx/site.standard.publication/3abc" ``` ## Verification Flow 1. **Publish** a publication to ATProto 2. **Save** the DID and rkey 3. **Create** `.well-known` endpoint returning the AT-URI 4. **Optionally** add `` tags to your HTML 5. **Platforms** fetch your `.well-known` endpoint to verify ownership ```mermaid sequenceDiagram participant You participant ATProto participant Platform You->>ATProto: Publish publication ATProto->>You: Return AT-URI You->>Your Site: Add .well-known endpoint Platform->>Your Site: Fetch .well-known Your Site->>Platform: Return AT-URI Platform->>ATProto: Verify AT-URI exists ATProto->>Platform: Confirmed Platform->>Platform: Mark as verified ✓ ``` ## Troubleshooting ### .well-known Returning 404 SvelteKit requires special handling for `.well-known`: ```typescript // Option 1: Create the exact path // src/routes/.well-known/site.standard.publication/+server.ts // Option 2: Use static files // static/.well-known/site.standard.publication ``` If using static files, make sure your hosting platform allows `.well-known`. ### Wrong MIME Type Ensure you're returning `text/plain`: ```typescript export function GET() { return text(content, { headers: { 'Content-Type': 'text/plain' } }); } ``` ### CORS Issues If platforms can't access your endpoint: ```typescript export function GET() { return text(content, { headers: { 'Content-Type': 'text/plain', 'Access-Control-Allow-Origin': '*' } }); } ``` ### Verification Failing Use the verification utility to test: ```typescript const isValid = await verifyPublicationWellKnown( 'https://yourblog.com', 'did:plc:xxx', '3abc123xyz' ); if (!isValid) { // Check: // 1. .well-known endpoint is accessible // 2. Returns exact AT-URI // 3. No extra whitespace // 4. Correct MIME type } ``` ## Best Practices 1. **Use environment variables** - Don't hardcode DIDs/rkeys 2. **Add caching headers** - `.well-known` content doesn't change often 3. **Test before deploying** - Verify the endpoint works 4. **Keep rkeys secure** - Don't expose in client code unnecessarily 5. **Monitor** - Check that verification keeps working after deploys ## Hosting Platform Notes ### Vercel Works out of the box. No special configuration needed. ### Netlify Add to `netlify.toml`: ```toml [[redirects]] from = "/.well-known/site.standard.publication" to = "/.well-known/site.standard.publication/index.html" status = 200 ``` ### Cloudflare Pages Works by default. Consider adding a cache rule for `.well-known`. ### GitHub Pages Static files work, but SvelteKit endpoints don't. Use the static file approach. ## Next Steps - [Publishing](./publishing.md) - [Content Transformation](./content-transformation.md) - [Comments](./comments.md)