How to build a website for the IndieWeb

A presentation at SFSCON 2024 in November 2024 in 39100 Bolzano, Autonomous Province of Bolzano – South Tyrol, Italy by Giacomo Debidda

Slide 1

Slide 1

How to build a website for the IndieWeb jackdbd/sfscon-2024-indieweb

Slide 2

Slide 2

Giacomo Debidda Freelance full stack developer / web performance consultant I write TypeScript / Clojure / Zig I like 🛹 and 🛼 BTW I use jackdbd giacomodebidda.com

Slide 3

Slide 3

🌟 The Golden Age of the Internet The web today is much more centralized than it was in the past. I miss when the internet felt like an endless ocean full of passion and creativity. It’s now full of corporations, tribalism and people who seek to earn money/push their political views. —wallakfir90 Source: I Miss Forums (And the Decentralized Internet) The web today is much more homogenous than it was in the past. Even in the days of MySpace, users enjoyed a level of control over their personal pages not seen today, with the ability to change the CSS and background image to their liking. Unfortunately, those times are largely behind us now —lost somewhere in the mid-2000s. In the Facebook era, everybody shares the same sterile profile, with the only avenues for self-expression being the photos and comments that they post. —Dave Heinemann Source: This is not the Web I’ve Known

Slide 4

Slide 4

🧱 Silos A silo (AKA walled garden) is a centralized web site (like most social media) that stakes some claim to content contributed to it and restricts access in some way (has walls). Source: silo, on indieweb.org require you to create an account specific to that site to use it (silo identity) allow you to interact only with accounts on that site (silo contacts) allow you to post only specific forms of content (e.g. text of 140 characters) claim some ownership or license to any content you create within the silo restrict your ability to import/export your content (e.g. posts, comments, tags) have an access wall that prevents indexing of (at least some of the) content you contribute have restrictive terms of service (TOS) can be shut down (acqui-hire , site-death ), taking content and permalinks with them

Slide 5

Slide 5

Perhaps you’re done with others owning your content, your identity, and your self

Slide 6

Slide 6

Reclaim your content and self host it

Slide 7

Slide 7

🏡 Own your domain A personal domain name is an inexpensive, internationally universal identifier which gives you more control over your space than other IDs (e.g. email address or phone number.) Source: IndieWebify.Me Why not just a subdomain? A subdomain (like example.wordpress.com or example.github.io ) is not something you own, rather you are at the mercy of the corporation that owns the actual domain name, who thus has ownership and control of all subdomains as well. Source: personal-domain, on indieweb.org

Slide 8

Slide 8

🌐 Web Sign-in Web sign-in is a mechanism for signing in to websites using your domain.

  1. A website presents a login form to the user. 2. The user enters their personal domain name into the login form. 3. The website verifies that the user has control of that domain using an authentication protocol like RelMeAuth or IndieAuth.

Slide 9

Slide 9

🔐 RelMeAuth RelMeAuth uses rel=”me” link(s) to match your domain name with existing identity(ies). In RelMeAuth an identity can be: The account you have with an OAuth 2.0 provider (e.g. your GitHub account) Your email Your OpenPGP key

Slide 10

Slide 10

website → provider Option A: <a rel=”me”> in the <body> <body> <a href=”https://github.com/jackdbd” rel=”me”>@jackdbd on Github</a> <a href=”https://fosstodon.org/jackdbd” rel=”me”>@jackdbd on Mastodon</a> <a href=”mailto:me@example.com” rel=”me”>me@example.com</a> <a href=”/assets/openpgp-pubkey.txt” rel=”pgpkey”>My OpenPGP public key</a> </body> Option B: <link rel=”me”> in the <head> <head> <link rel=”me” href=”https://github.com/jackdbd” /> <link rel=”me” href=”https://fosstodon.org/@jackdbd” /> <link rel=”me” href=”mailto:me@example.com” /> provider → website Add your website in your Github profile Verify your identity on Mastodon Receive a verification code via email Sign a challenge with your OpenPGP private key (e.g. echo <challenge> | gpg —clearsign - -armor ) Use indiewebify.me/validate-rel-me to validate that your domain name and profiles are linked together.

Slide 11

Slide 11

Example: IndieWeb.org On my site: <link rel=”authorization_endpoint” href=”https://indielogin.com/auth”> On IndieWeb.org:

Slide 12

Slide 12

Slide 13

Slide 13

Slide 14

Slide 14

🪪 Own your identity Instead of logging in to websites as “you on Twitter” or “you on Facebook”, you should be able to log in as just “you”. We should not be relying on Twitter or Facebook to provide our authenticated identities, we should be able to use our own domain names to log in to sites everywhere. Source: IndieAuth.com: Sign in with your domain name

Slide 15

Slide 15

🔐 IndieAuth Every service that spins up an OAuth-enabled API ends up being its own isolated system. For example, if I want to build an app that can read someone’s step count from FitBit, I have to first go register as a developer on FitBit’s website in order to get API keys to use with their OAuth API. —Aaron Parecki Source: OAuth for the Open Web IndieAuth is: an OAuth 2.0 extension that defines an identity layer an authentication protocol alternative to OpenID Connect typically used to obtain an OAuth 2.0 Bearer tokens for Micropub clients used on sites like indiebookclub.biz

Slide 16

Slide 16

IndieAuth OpenID Connect Unique user identifier: a URL No consistent unique user identifier across providers Identity is portable Identity is not portable → many identities Identity is tied to DNS Each identity is tied to an OpenID Connect provider OAuth 2.0 clients (aka OAuth apps) require no OAuth 2.0 clients require registration (e.g. you need registration, since client IDs are resolvable URLs to create a GitHub OAuth App, a LinkedIn app, etc.) Defines 2 scopes Defines 20 standard claims for the access token Your app can define additional scopes/claims OIDC provider can define additional scopes/claims access token, no ID token Your app can define additional scopes/claims User’s info available at the /userinfo endpoint access token and ID token Does not deal with session management, focuses on Claims returned in an ID Token and are also just returning the user’s identifier available through the /userinfo endpoint Deals with session management, ID tokens can be used as a session

Slide 17

Slide 17

No OAuth 2.0 client registration In IndieAuth the client ID is a resolvable URL GET https://indiebookclub.biz/id { } “client_id”: “https://indiebookclub.biz/id”, “client_name”: “indiebookclub”, “client_uri”: “https://indiebookclub.biz/”, “logo_uri”: “https://indiebookclub.biz/images/book.svg”, “redirect_uris”: [“https://indiebookclub.biz/auth/callback”]

Slide 18

Slide 18

Slide 19

Slide 19

How does IndieAuth work? An IndieAuth server is a set of endpoints: authorization endpoint, token endpoint, revocation endpoint, introspection endpoint, userinfo endpoint. A website/app that implements IndieAuth advertises these endpoints at the URL rel=indieauth-metadata , using IndieAuth Server Metadata . Link: https://giacomodebidda.com/.well-known/oauth-authorization-server; rel=”indieauth-metadata” <link rel=”indieauth-metadata” href=”https://giacomodebidda.com/.well-known/oauth-authorization-server”> IndieAuth clients fetch the URL at rel=indieauth-metadata and discover these endpoints. { } “authorization_endpoint”: “https://indieauth.com/auth”, “introspection_endpoint”: “https://micropub.fly.dev/introspect”, “issuer”: “https://giacomodebidda.com/”, “revocation_endpoint”: “https://micropub.fly.dev/revocation”, “scopes_supported”: [“email”, “profile”, “create”, “draft”, “update”, “delete”, “media”], “token_endpoint”: “https://micropub.fly.dev/token”, “userinfo_endpoint”: “https://micropub.fly.dev/userinfo”

Slide 20

Slide 20

📝 Own your content Publish On your own Site You own the canonical URL of your content…

<head> <link rel=”canonical” href=”https://www.giacomodebidda.com/articles/performance-audit-italian-news-website/”> </head> Syndicate Elsewhere …and publish a copy of your content on other platforms (e.g. social media).

Slide 21

Slide 21

Microformats2 Use semantically marked up HTML + microformats2 for your content. This allows other people’s software to easily read and understand your content. microformats2 parser: HTML → canonical MF2 JSON data structure

Slide 22

Slide 22

<div class=”h-card”> <img class=”u-photo” src=”/barryfrost.jpg” alt=”Photo of Barry” width=”28” height=”28”> <h1 class=”p-name”>Barry Frost</h1> <a class=”u-url hidden” href=”/” rel=”me”></a> <a class=”u-url” href=”acct:barry@barryfrost.com”></a> </div> Validate your h-card on indiewebify.me/validate-h-card.

Slide 23

Slide 23

<article class=”h-entry”> <header> <div class=”h-card u-author”> <img class=”u-photo” src=”/barryfrost.jpg” alt=”Photo of Barry” width=”28” height=”28”> <a href=”/” class=”p-name u-url”>Barry Frost</a> </div> <h1 class=”p-name”>Week 166 - Trophy</h1> </header> <div class=”e-content”>Barry’s article here…</div> <time class=”dt-updated hidden” datetime=”2024-10-28T19:05:11.976Z”></time> <ul> <li> <a href=”/categories/emoji- “>#<span class=”p-category”>emoji- </span></a> </li> <li> <a href=”/categories/weeknotes”>#<span class=”p-category”>weeknotes</span></a> </li> </ul> </article> 🏆 Validate your h-entry on indiewebify.me/validate-h-entry. 🏆

Slide 24

Slide 24

📰Micropub A protocol for creating, editing, deleting, undeleting a post on a website hosted on your own domain using third-party clients. And for syndicating that post elsewhere. A website/app advertises its micropub endpoint at the URL rel=micropub . A Micropub client: Discovers your IndieAuth endpoints and your Micropub endpoints. Authenticates you (e.g. using RelMeAuth or IndieAuth). Submits your posts to the appropriate Micropub endpoint. Link: https://micropub.fly.dev/micropub; rel=”micropub” <link rel=”micropub” href=”https://micropub.fly.dev/micropub”> A Micropub server is a set of endpoints: A /micropub endpoint for create / update / delete / undelete actions on a post (e.g. a note). A /media endpoint for actions on some media (e.g. an image, some audio).

Slide 25

Slide 25

User browser Micropub client User web server Auth endpoint Token endpoint Introspection endpoint Micropub endpoint Click “Login” Fetch homepage Serve homepage Discover IndieAuth & Micropub endpoints Redirect to auth endpoint Verify user Redirect to web application Load page Exchange auth code for access token Auth code verification request Auth code verification response Send access token Log user in Show page Create a post Submit Micropub request with access token Access token verification request Access token verification response Micropub endpoint creates post on website Success! User browser Micropub client User web server Auth endpoint Token endpoint Introspection endpoint Micropub endpoint

Slide 26

Slide 26

🔎 Endpoints discovery The micropub endpoint is discovered using either a HTTP Link header… Link: https://micropub.fly.dev/micropub; rel=”micropub” …or a <link rel=”me”> tag in the <head> . <link rel=”micropub” href=”https://micropub.fly.dev/micropub”> The media endpoint is discovered with a GET to the micropub endpoint. GET /micropub?q=config Authorization: Bearer xxxxxxxxx { } “media-endpoint”: “https://media.example.com/micropub”

Slide 27

Slide 27

Slide 28

Slide 28

📢 Syndication (cross-posting)

  1. Publish (on your) Own Site → Your site has the original post (i.e. the canonical URL) 2. Syndicate Elsewhere → syndication targets (e.g. social networks) get a copy of your post By posting first on your own site, you create a direct ownership chain that can be traced back to you without any intervening 3rd party services (silos) TOS’s getting in the way. Source: POSSE, on indieweb.org Your micropub endpoint should list all the syndication targets you want to send your post to. Your micropub client should have a way to publish to the syndication targets. For example, for posting on LinkedIn you will need to create a LinkedIn OAuth app. As you can read in Rethinking syndication #581 , implementing syndication is not trivial.

Slide 29

Slide 29

🔁 Backfeed (reverse syndication) A POSSE copy of your original content is published on a silo (e.g. a social network). People interact with that content. You want to capture these interactions so you can have cross-site conversations. How can you do it?

  1. Poll the silo API (or scrape the silo) to retrieve interactions like replies, likes, reposts, bookmarks 2. Link each interaction to the original content using original post discovery 3. Convert interactions into webmentions 4. Submit all webmentions to your webmention endpoint 5. Fetch your webmention endpoint and display the webmentions on your site This process is called backfeed. You can implement it yourself or use a backfeed proxy like Bridgy

Slide 30

Slide 30

Receiving webmentions You advertise your webmention endpoint in the <head> of your HTML pages. <link rel=”webmention” href=”https://webmention.io/giacomodebidda.com/webmention”> <link rel=”pingback” href=”https://webmention.io/giacomodebidda.com/xmlrpc”> Bridgy sends webmentions to your webmention endpoint. Webmentions appear on your webmention.io dashboard.

Slide 31

Slide 31

Fetching webmentions You can fetch webmentions at runtime, with client-side JS, or at build time. I fetch webmentions whenever I build my Eleventy site. import EleventyFetch from ‘@11ty/eleventy-fetch’ const format = ‘jf2’ // https://www.w3.org/wiki/JF2 const endpoint = https://webmention.io/api/mentions.${format} const response = await EleventyFetch( ${endpoint}?page=1&per-page=25&token=${process.env.WEBMENTION_IO_TOKEN}, { directory: ‘.cache-webmentions’, duration: ‘30d’, type: ‘json’, verbose: true } ) const { likes, replies, reposts } = responseToWebmentions(response) Don’t forget to sanitize the webmentions before displaying them on your site.

Slide 32

Slide 32

Sending webmentions You want to send me a webmention? You can cite my article on your site and either: Go to webmention.io/giacomodebidda.com/webmention and fill in the form. Fill in the form I include on my site. Let a service like webmention.app send your outgoing webmentions automatically.

Slide 33

Slide 33

Recap 📖 Read Getting Started 🏡 Buy a domain 🔨 Build a website and host it on your domain 📝 Write your content with microformats2 📰 Publish your content on your domain 📢 Syndicate content to other platforms 🔁 Backfeed (reverse syndicate) content from other platforms to your site 🪪 Use IndieAuth to have full control of your identity ✅ Use IndieWebify.me and IndieMark as checklists to verify your progress

Slide 34

Slide 34

The end jackdbd/sfscon-2024-indieweb Open an issue if you have tips / questions / feedback Thanks!