oliverandrich
I have released the first version of Ithibati, a passkey-authentication library for Elixir. It came out of my own blog engine, where the accounts context had grown into the one part I did not want to write a third time.
Swahili, ithibati: proof, evidence. In WebAuthn’s own vocabulary, attestation.
Why another one
Most authentication libraries answer two questions at once: Who is this, and what may they do? The second answer is different in every application - sites, tenants, teams, roles, invitations - which is why the first one so rarely gets reused. Ithibati answers only the first. It knows the account row, its passkeys, its recovery codes, and its sessions, and it knows nothing about what that account may then do.
You keep your own users table. The library contributes a schema macro and the changeset pieces that validate the field an account is known by; it never assumes a column the macro did not put there.
What using it looks like
Five touchpoints, and they are close to the whole surface.
# mix.exs
{:ithibati, "~> 0.1"}
One line in your account schema names the field an account is known by:
use Ithibati.Schema.User, identifier: :username, format: Identifier.username_format()
One line in your router mounts the ceremony endpoints:
ithibati_routes handler: MyAppWeb.Auth, rp_name: "MyApp"
Your handler decides what a verified assertion is worth. Ithibati hands you the account and issues nothing of its own:
@impl true
def authenticate(conn, account),
do: {:ok, conn |> Gate.log_in(account) |> json(%{redirect: "/"})}
And a page starts a ceremony by pushing an event to the browser hook:
def handle_event("sign-in", _params, socket),
do: {:noreply, push_event(socket, "ithibati:authenticate", %{})}
There is no password field anywhere and no sign-in form either. A sign-in challenge names no credentials, so the browser offers whichever passkeys it holds for your site, and the person picks one. Registering is the half that still needs a form because the name does not exist yet.
What is in the box
- Passkey registration and authentication through
wax_, including the first account on an empty instance. - Single-use recovery codes, twelve by default, which refill themselves when the last one is spent. There is an endpoint for signing in with one.
- Revocable server-side sessions. The cookie carries the secret, the row carries its sha256, and signing out revokes the row rather than forgetting it. It also ends the LiveViews that session opened, when your endpoint has a pubsub server.
- Invitations, optional: Ithibati owns the token, its expiry, and the redemption, and you own the table and whatever the invitation grants.
- Composable
Ecto.Multifragments, so creating an account, its first passkey and its recovery codes is one transaction that you hang your steps on. mix ithibati.doctor, which asks twelve questions about a setup and says which one is wrong.- Three Credo checks a consuming project can switch on, if you want the boundaries enforced rather than merely written down.
The Phoenix half - the routes, a gate, a LiveView hook - is optional, and phoenix,phoenix_live_view and plug are optional together. Without them you still get a core that compiles, and a ceremony is a handful of function calls. priv/static/ithibati.js exportsregister and authenticate as plain functions, so a browser extension or a native client can use the same path.
What it deliberately does not do
- API tokens. What makes one is the scopes, the rotation, the expiry chosen per token, the page where somebody sees and revokes theirs, and the audit trail. None of that is here. A session is the only credential the table holds.
- Send mail. An application that mails an invitation link itself has proved the address by the time the account exists, because accepting an invitation refuses an account created under any other identifier. That is the whole of the address verification on offer.
- Decide what an account may do. Roles, tenancy, and memberships stay yours.
The small print
It is a first release, and Elixir 1.17 and up.
It needs Postgres today. Two places read the database catalogue directly, which is what lets the migration and mix ithibati.doctor tell you which column or index your own table is missing rather than failing later with an undefined_column. The races are settled with a FOR NO KEY UPDATE row lock. Whether either of those has to stay a requirement is a question I have not finished answering, and a single-user instance on SQLite is the case I keep coming back to.
There are two example applications in the repository, one for open registration and one for invitation-only, and CI drives both in a real browser so they cannot quietly rot. Read those when the guides and your editor disagree.
- Docs: Ithibati v0.1.0 — Documentation
- Source:
I would rather hear that a decision is wrong now than after somebody has built on it, so criticism of the shape is as welcome as a bug report.
Trending in Announcing
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #ai
- #elixirconf-us
- #blog-post
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #elixirconf-eu
- #api
- #forms
- #metaprogramming
- #hex










Showing Posts 1 to 5- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
wintermeyer
Great work! Thanks!
type1fool
Nice work, and thank you for sharing! It is exciting to see new open source packages, particularly for Passkey support in Elixir apps.
Choices around scope are what I find most interesting when comparing packages in the authentication/authorization space. I think you’ve made a good choice in leaving authorization, OAuth, and most tangential concerns out of Ithibati.
I am preparing updates to WebauthnComponents, which is focused on adding Passkey support for LiveView apps, and I have been narrowing scope a bit further this week.
IMHO, it was a slight mistake to position WebauthnComponents primarily as an alternative to
phx.gen.auth. This week, I have removed the mix task for generating LiveViews, schemas, migrations, etc. So WC will have no session management, nor many of the features of Ithibati - it will simply abstract the client-server coordination during registration and authentication and send messages up to the LiveView.This will also make the package less opinionated about the persistence layer. The documentation may eventually include guidance for CRUD and Event Sourcing/CQRS patterns.
It will be interesting to hear feedback. Cheers!
asianfilm
Love this direction.
I’m using event sourcing and want to add passkeys as 2FA for compliance with the April 2026 edition of the UK’s CyberEssentials standard, which is needed for many government contracts. Previously only admin accounts required 2FA. CE2026 also demands that you log people out after 15 minutes of inactivity.
oliverandrich
Oh, this sounds fascinating. I am curious to see what changes you apply and how you sharpen the profile of your library. I think I gave it a try in the beginning, but I can’t remember where I struggled with it. I ended up with wax_ and wrapped it with my own code.
And I strongly agree; we need to have good Passkey support for Elixir and good solutions for different scopes. I came from a hobbyist view on the topic. I earn my money with Python/Django development, and Elixir/Phoenix is my stack for all my site projects. And I have a “customer one” that is not a fan of strong password policies, and loves all the Passkey stuff she gets on her iPhone.
oliverandrich
Just release 0.2.0 of Ithibati that closed some documentation and API issues.