Marty McGuire

Posts Tagged indieauth

2022
Sun Jul 31

Switching costs for an IndieAuth server

One of the things I love about building with IndieWeb building blocks is that (sometimes through more work than anticipated) you can swap out pieces of your site without (much) disruption because the seams between building blocks are well specified.

So, this is me documenting how I replaced my IndieAuth setup to stop leaning on Aaron’s IndieAuth.com (which has been on the verge of retiring any day now for some years).

Please excuse this long and rambling post. Feel free to skip around!

What is IndieAuth?

At a high-level, IndieAuth is a way to sign in using your website as an identity.

Without digging too deeply into the plumbing, you start by updating your website’s homepage with some extra header info that says “my IndieAuth service is over there”. From there, you can sign into services that support IndieAuth (like the IndieWeb wiki, the social feed reader service Aperture, and more. And you can use your IndieAuth server to protect your own services, such as a Micropub server that can create new posts on your site.

Why switch?

I’ve been using indieauth.com as my IndieAuth setup since late 2016 because it was easy to set up, because it uses something called RelMeAuth to let me sign in using services I already trust (like GitHub).

However, indieauth.com has been growing stale as the IndieAuth spec has evolved. indieauth.com’s maintainer has been discussing replacing it since at least 2017.

The inciting incident for my switch was looking at OwnCast - a self-hostable video streaming service with attached chatroom. OwnCast’s chat allows using IndieAuth to sign in, which sounded great to me, but OwnCast’s implementation wasn’t expecting indieauth.com’s old-style response format.

Why set up my own?

There are a bunch of IndieAuth server implementations listed on the IndieWeb wiki. However: simplest of them (selfauth + mintoken) are now out of date with the spec and haven’t been replaced, yet. Others tend to be built into other CMSes like WordPress. A couple of standalone servers exist but are in languages I am not comfortable working in (hello Rust and Go) or have deployment requirements I wasn’t thrilled about supporting (hello Rails).

I found Taproot/IndieAuth on this page and that looked promising - a PHP library intended to be deployed within a fairly standard PHP web app style (“any PSR-7 compatible app”).

I knew this would be some work but it sounded promising and so I began the week-ish long process of actually writing and deploying that “PSR-7 compatible app” built on taproot/indieauth.

tl;dr say hello to Belding

Belding is an “PSR-7 compatible” PHP web app that provides a standalone IndieAuth endpoint for a single user with a simple password form for authentication.

I would love to go into the process and pitfalls of putting it together, but instead I’ll link to the README where you can learn more about how it works, how to use it, its limitations, etc.

Switching costs for an IndieAuth server

1. Tell the World

First up, you’ll need to update the headers on your site. I switched my authorization_endpoint and token_endpoint to my new server from indieauth.com. Since I’m updating to support the latest spec, I also added the indieauth-metadata header (which should eventually replace the other two).

Now that your site is advertising the new IndieAuth server, you will likely experience logouts or weird access denied reponses everywhere that your site has been used with IndieAuth.

2. Tell your own services

I needed to configure my own “relying apps” so they know to talk to the new server when checking that a request is allowed. This list thankfully wasn’t too long.

Beyond the effort of getting my server working as an indieauth.com replacement, I also took steps to try and support the latest in the IndieAuth spec. That meant updating these micropub servers to use the new “token introspection” feature which has some tighter security requirements.

(Note: I initially made the same change for my self-hosted copy of Aperture, but found it would be too many changes for me to take on at the moment. Instead, I updated by IndieAuth server to allow the older and less secure token verification method used by Aperture.)

3. Sign-in to all the things again \o|

Once all my relying apps were all talking to the new IndieAuth server, it was time to re-sign-in to all the things:

Takeaways

There are a lot of improvements I’d like to make to Belding, but in general I am happy that it seems to work and, outside of the time to develop the server itself, my website and the tools I use to manage it were only broken for about a day.

I think it’d also be really nice to wrap up Belding a bit so it’s easy to configure and deploy on free-and-cheap platforms like fly.io. I believe it should be easier for folks to spin up and control their own IndieWeb building blocks where possible!

It’s also become clear to me that there are some user- and developer-experience holes around setting up relying apps. The auth requirements for token introspection, for example, means you need a way to manage access for each “backend” that you have that relies on IndieAuth to protect itself!

Long story short (too late) I am finally able to sign into OwnCast server chat using my domain. 😂😅

2019
Fri Mar 29

Loving the Adafruit PyPortal as an IndieWeb-powered photo device.

Code to come!

2018
Mon Mar 12

Micropub for a static Neocities website

This post gives more technical detail for the recent talk that I gave at Bring-a-Hack NYC. In it, I describe a system that copies posts from Ghost Party's Instagram automatically to the Ghost Party Website at ghostparty.today.

This roughly works like so:

  • OwnYourGram periodically checks our Instagram for new images
  • For each new post, it makes a Micropub request to a service that I wrote for this purpose.
  • The endpoint accepts the image files and uploads them to our site using the Neocities API. Then it creates some new HTML from the micropub post data, edits the site's main index.html page to include the content, then uploads it back to Neocities.

Authorization with IndieAuth

One missing step in that rough outline is the bit where we tell OwnYourGram *how* to post to our site via Micropub, as well as making sure that OwnYourGram can prove that we have given it permission to post there. Many Micropub clients support IndieAuth as a way to do this permission-granting step (known as authorization). With a typical personal website, I'd delegate this process to indieauth.com, which lets you offload the step of "proving" that you own the website where you're trying to post by instead letting you log into an existing social network account.

ghostparty.today is a shared website, so I thought a simpler way to approach this would be to create a custom authorization endpoint that accepts a password. I could give this password to other GHOST PARTY folks and they can post whatever they like.

Some nice folks in the IndieWeb community had already created an authorization endpoint that works this way, called selfauth. I created a pretty simplistic port of selfauth from PHP to JavaScript using NodeJS on the awesome Glitch social code hosting platform. It needs some more robust error checking, and some micropub clients seem to have trouble with it, but it works with OwnYourGram and Quill, so it's Good Enough for now!

Interface displaying text 'You are attempting to login as https://ghostparty.today/ with client https://quill.p3k.io/' along with a password prompt.
Enter the password to get authorized!

If you're interested, you can find the source for this endpoint here: https://glitch.com/edit/#!/befitting-price

Micropub Endpoint

Finally, there's the Micropub endpoint itself. I based mine off of this Glitch micropub project, created by Adam Bachman at IndieWebCamp Baltimore this year. Specfically, I took the bits that verify the access token. From there, it was a matter of stumbling through various bits of handling a Micropub post, pulling in extra libraries as I needed them. I slowly added support for saving uploaded files to our Neocities site via the Neocities API, then for generating an HTML snippet for the new post, and finally for adding the post to the site's main page.

The process for adding the generated post content to the site itself is extremely retro. The Micropub endpoint actually downloads the HTML from https://ghostparty.today/index.html. Inside there is a comment that looks like:

<!-- NEW POSTS BELOW -->

So the new contents for index.html is everything before the comment, the comment, the new content, and then everything after the comment. Once that's constructed, the endpoint uploads index.html back to Neocities with the new contents.

You can find the source for the Micropub endpoint here: https://glitch.com/edit/#!/prism-dirt

It should be possible to fork the project on Glitch and configure it to work with your own Neocities site! You'll need to use cURL with the Neocities API to generate an authorization token to configure the app, rather than saving username and password info in the project!

Making ghostparty.today "IndieWeb-Readable"

The GHOST PARTY site follows an extremely old-school publishing model where each post only exists as a small section of HTML on a single long page. This complicates things a bit because interacting with posts on the IndieWeb typically requires three things:

  • A machine-readable "identity" h-card that says "who" this website is, including a logo, the site name, canonical URL.
  • A machine-readable feed that contains new posts, allowing people to follow our updates in their favorite indie reader.
  • A separate permalink URL for each post, containing machine-readable info about that post, allowing other IndieWeb sites to "understand" the content when they want to RSVP to events, repost or like photos, and more.

Microformats2 parsers like X-Ray do a great job at parsing HTML page into meaningful data, but the programs that use the parsed data usually stop at the first thing they find. So, if I ask X-Ray to parse https://ghostparty.today/, this is what it sees:

{
  "data":{
    "type":"card",
    "name":"GHOST PARTY",
    "url":"https://ghostparty.today/",
    "photo":"https://ghostparty.neocities.org/ghost-party-logo.png"
  },
  "url":"https://ghostparty.today/",
  "code":200
}

This is great in that it contains the "identity" information that we want the page to have. However, that page also has a feed! How can I get at that? By telling X-Ray to only look at a fragment of the page! Here's how X-Ray sees https://ghostparty.today/#feed: 

{
  "data":{
    "type":"feed",
    "items":[
      {
        "type":"entry",
        "published":"2018-03-07T16:44:16+00:00",
        "url":"https://ghostparty.today/#2018-03-07-181020",
        "photo":["https://ghostparty.today/uploads/2018-03-07/181020-ig0LN1JG.jpg"],
        "content":{"text":"snow day selfie #snowday #snowfie #ghoststories"} 
      },
      {
        "type":"entry",
        ...
      },
      ...
    ],
    "url":"https://ghostparty.today/#feed",
    "code":200,
    "info":{"found_fragment":true}
  }
}

Data! Nice! So if we want to follow ghostparty.today with an indie reader, we use https://ghostparty.today/#feed as the URL.

Looking at the content of the feed, you'll notice that the individual items have a url property which also has a fragment ID in the URL. This allows IndieWeb sites to interact with a particular post, even though it's one of many on the same page. For example, I was able to create an RSVP post to the most recent Ghost Party show, and (by looking just at the content in the fragment at https://ghostparty.today/#show-2018-02-14) my site was able to pick up the machine-readable details about the event to display them on my own site.

The microformats markup for the h-card and h-feed are built into the index.html file itself and don't change very often. However, for each new micropub post we need to convert those properties into a chunk of HTML for the index.html page that also contains the microformats h-entry markup. To do this, I made this Handlebars template:

{% raw %}<div class="h-entry" id="{{ mp-slug }}">
  <a class="u-author" href="/"></a>
  <a class="u-url" href="#{{ mp-slug }}">{{#if name}}<h2 class="p-name">{{ name }}</h2>{{/if}}</a>
  <time class="dt-published" datetime="{{ published }}"></time>
  {{#if video}}
    {{#each video}}
      <video controls loop class="u-video" src="{{ . }}"></video>
    {{/each}}
  {{ else }}
    {{#each photo}}
      <img class="u-photo" src="{{ . }}" />
    {{/each}}
    {{#each audio}}
      <audio class="u-audio" src="{{ . }}"></audio>
    {{/each}}
  {{/if}}
  <div class="e-content">{{ content }}</div>
</div>
<img src="ghosts.gif" />
{% endraw %}

Not so bad!

Resources

Here's a collection of some of the other libraries and docs I used to pull this thing together:

  • Neocities API
    • Includes details on using cURL to get an authorization token.
    • There’s a Node module for the Neocities API, but it uses username and passwords, so I just used regular HTTP requests to handle talking to Neocities.
  • Node Request HTTP Client
    • Verifies the authorization token by querying the token endpoint (I used tokens.indieauth.com for this)
    • Handles uploading files like images to Neocities via their API.
    • Used to fetch index.html from the site and later to writes it back via Neocities API.
  • ES6 Promises
    • There's a lot of asynchronous code that waits for network requests. This code is very Promise-heavy to handle that.
  • Handlebars templating
    • Made it easy to take the Micropub properties for a new post and rendered them into HTML content with microformats.
  • Express 4 API
    • Glitch uses Node and Express 4 by default, and I got to learn lots about it!
  • Multer Express Middleware
    • For handling uploaded files
  • Body Parser Express Middleware
    • For handling regular Micropub properties
  • Validator.js
    • Half-using this for the authorization endpoint, need to clean it up and finish it up!
  • OwnYourGram API docs
    • Shows what properties are sent over Micropub, so you know exactly the kind of data your Micropub endpoint will need to handle.
2017
Thu Jan 26

Flask-IndieAuth - A Python Library for Micropub Servers

One of the things I like about the IndieWeb community is that while they are building tools for themselves, they also tend to release useful parts under Free Software licenses. This helps other developers join the community more quickly, but it also tends to help improve the quality and feature sets of these projects as others use and add to the source.

One of my favorite things to come from the IndieWeb folks is the Micropub API standard, which defines some simple protocols for clients to send post data (the kinds of things you'd share on a blog or social media: images, short plain text, long articles, tags, and more) to servers for posting. One upshot is that if your server accepts Micropub, you can use one of many clients to put content on your site. I'm using a dedicated editor from Aaron Parecki's Quill to write this post, but there are lots of alternatives that are aimed at special use cases. For example, Kyle Mahan's Woodwind is an IndieWeb reader app that happens to include functionality for posting replies, favorites, reposts, and even RSVPs directly to my site via Micropub.

Another favorite is the idea of IndieAuth for web sign-in. At a high level, the idea is that you create two-way links between your website and your user profile on some other silo. For example, on your homepage you add a link to your Twitter profile and on your Twitter profile you link back to your homepage. For a client that supports IndieAuth, I can log in using my homepage URL by verifying that I can log in to my Twitter account.

My own personal Micropub implementation is a little pile of spaghetti Python code making use of the Flask framework. I use IndieAuth to handle authentication (i.e. - proving that a post comes from an app that I've logged into) and authorization (i.e. - proving that I gave that app permission to post to my site). As I've started improving my Micropub implementation, I found it useful to extract that portion of my code into a library that can be used with other Flask applications.

Introducing Flask-IndieAuth

Flask-IndieAuth is a Flask extension that adds the ability to require a client to send a valid IndieAuth token when making requests to any route. For example:

from flask_indieauth import requires_indieauth

@app.route('/micropub', methods=['GET','POST'])
@requires_indieauth
def handle_micropub():
    # ... handle the request

The @requires_indieauth decorator runs before the code for the route. It currently looks for an IndieAuth token in one of three places, in order:

  • HTTP Header (e.g. "Authorization: Bearer xxxx...xx")
  • HTTP form data or query string (e.g. "?access_token=xxxx...xx")
  • The body of a JSON-encoded POST (e.g. {"access_token": "xxxx...xx"})

If a token is found, it will be verified against the configured Token Endpoint to confirm that it is a valid token issued for your server's configured homepage with a sufficient scope.

For more information on how to install, configure, and use Flask-IndieAuth, please check out the README on GitHub.

Next Steps

I'll be using this extension to build my Micropub media endpoint (coming up in a future post) and so far it is working just fine. That said, I know there is a lot of room for improvement. Some things on my list:

  • "scope" can have many values, but only "post" is supported for now. It should probably be passed as an argument to @requires_indieauth so different routes can have different requirements.
  • The configured homepage ("ME") is currently expected in the Flask app's config. I'm not sure if that's "standard".
  • "TOKEN_ENDPOINT" is currently expected in the Flask app's configuration, but since it is required to be specified in the HTTP headers for or as a <link> in the content for the homepage, this could be fetched by the server.
  • Error handling isn't great - all failure conditions currently return HTTP 400 (Bad Request) but should probably be diversified a bit.

Feedback Welcome!

This is my first published Flask extension (heck, it's my first public Python package on PyPI), and I'd really appreciate comments, questions, pull requests, etc. Feel free to reach out on GitHub, or you can find me in the #indieweb chat on freenode IRC.