{
"$type": "site.standard.document",
"canonicalUrl": "https:/finxol.eu/posts/extending-openauth",
"description": "I needed a self-hostable auth solution for the project I'm working on. OpenAuth's beautiful simplicity looked really promising. There were just a couple things I wanted adjusted, so I spent a weekend fixing then.",
"path": "/posts/extending-openauth",
"publishedAt": "2025-03-31T00:00:00.000Z",
"site": "at://did:plc:hpmpe3pzpdtxbmvhlwrevhju/site.standard.publication/3mndozltfas27",
"tags": [
"auth",
"open source"
],
"textContent": "I'm currently building Karr, an open-source federated carpool platform—it's still very early days, not much there yet.\nLike basically all apps nowadays, I need an auth system.\n\nThe only strong requirement I have is for it to be self-hostable.\nSince I'm building a federated platform for companies, I don't want instance admins to have to rely on some arbitrary external auth service.\n\nThis limited my options a fair bit.\nI considered a few other options, but I soon landed on OpenAuth.\n\nOpenAuth\n\nIf you haven't already heard of it, OpenAuth is a pretty new open-source authentication library by the authors of SST.\n\nThe intent is for it to be \"Universal, Self-hosted, Standards-based, and Customizable\".\nPromising stuff.<br/>\nThey also mention right on the home page that it can be \"embed it into an existing application\".\nPerfect.\n\nThere's more. OpenAuth is built with Hono, and so is my API.\nThat means I can integrate it directly into my existing API!\n\nThere is however a slight half-truth in there.\nRunning OpenAuth from anything other than the root path (/) isn't supported yet, but I really wanted to avoid making a whole other Docker container or crazy path rewrites with the reverse proxy, so I went and implemented it myself.\n\nSub-paths\n\nApparently, I'm not the first one to run into this issue.\nSome people have already been thinking of solutions for a couple months.\n\nHowever, their approach didn't seem very robust and maintainable to me.\nIt involved looking through the codebase and finding every redirect, and adding the base path to them.\n\nTo me that sounds: 1. super tedious, and 2. hardly maintainable.\nIt would involve all future contributors and maintainers remembering to add the base path to each local url.\nIt seems to me like a mistake could easily slip through.\n\nSo instead, I went for a middleware approach.\n\nIf a base path is specified, all local redirect reponses will be rewritten to include it.\nFor example, a redirect to /github/authorize will be rewritten as /auth/github/authorize if OpenAuth is mounted at /auth.\n\nAll that was left to do was include the base path in the issuer, and remove it when building well-known routes.\nI managed to get it working 45-ish lines of actual code—the rest is docs and tests—so it's a pretty minimal solution!\n\nSpec compliance\n\nThe first spec they mentioned in the issue, RFC 5785, states that all Well-Known URIs must be at the root, so at /.well-known/.\n\nRFC 8414 also states that the Well-Known URI is obtained by \"inserting a well-known URI string into the authorization server's issuer identifier between the host component and the path component, if any\", e.g. if the issuer is https://example.org/auth, the well-known paths would be under https://example.org/.well-known/*\n\nThis means there needs to be some sort of rewrite/redirect from the root well-known URIs to the path where OpenAuth is mounted.\n\nThis is an annoying caveat, but I don't see any way to manage it directly inside OpenAuth since the whole point of this base path stuff is to not have it manage the root path.\nIt needs to be handled externally by whatever manages the root path, whether it be by a reverse proxy, a router, or a switchboard operator.\n\nBest I could do is put a massive warning in the docs next to the basePath option.\n\nNow I just need to wait for feedback on my PR and hopefully a merge.\n\nStorage Adapters\n\nThe second annoyance I had with OpenAuth was the built-in storage adapters.\nAll they offered was either DynamoDB, Cloudflare KV or in-memory with a Map object.\n\nObviously, the latter isn't usable in a real production environment,\nbut the other 2 aren't any good to me either for the same reason as my self-hosted auth requirement.\nI can't have instance admins rely on AWS or Cloudflare just for an auth KV.\n\nSo again, I went and did an adapter myself.\n\nOf course, I didn't make a whole KV solution for Node.\nI simply made a wrapper around unstorage.\nAs of writing this, they have 21 drivers of all sorts.\nThe one I find particularly interesting is the SQL driver, although still experimental.\n\nBuilding the adapter was simply a case of copy-pasting OpenAuth's MemoryAdapter and replacing the Map function calls with those of unstorage.\nVery straightforward and automatable stuff.\n\nThere was however one slight snag.\n\nWhen calling .setItem(key, value), the key is used as it is.\nHowever, when calling .getKeys(base), the base is treated as a prefix and gets normalized, aka. it gets appended a semicolon.\n\nThis is a problem for use in OpenAuth.\nThe Storage API here has its own joinKey(key) method which joins the keys with String.fromCharCode(0x1f) as a separator, not a semicolon.\n\nThis difference means the keys won't be found when calling .getKeys(base), so for the purposes of OpenAuth, unstorage needs a small patch to remove the semicolon addition for its internal normalizeBaseKey(base) function.\n\nSince unstorage opens up a lot of possibilities for storage, I also opened a PR for this one.\n\nWhile I wait for my PRs to be merged, I'm just using both with pnpm patches.",
"title": "Extending OpenAuth"
}