atlogin #
OIDC identity provider for AT Protocol handles. Lets users log in to any OIDC-compatible app using their AT Protocol identity.
how it works #
Users authenticate using an email-style login hint that maps to an AT Protocol handle:
| Login | Handle |
|---|---|
alice@example.com |
@alice.example.com |
alice@alice.bsky.social |
@alice.bsky.social (prefix match) |
alice.bsky.social@your-instance.example.com |
@alice.bsky.social (passthrough) |
The server's own hostname is a passthrough domain — the username part is used directly as the AT Protocol handle.
deployment #
env vars #
The service is configured entirely via two environment variables, both base64-encoded:
ATLOGIN_CONFIG — base64-encoded JSON config:
{
"addr": "127.0.0.1:9411",
"issuer": "https://your-instance.example.com",
"client_name": "your instance name",
"master_key": "<hex string, generate with: openssl rand -hex 32>",
"secrets": {
"your-client-id": "your-client-secret"
},
"redirect_uris": {
"your-client-id": ["https://yourapp.example.com/callback"]
}
}
Encode it:
printf '%s' '{"addr":"127.0.0.1:9411",...}' | base64 -w0
ATLOGIN_SIGNING_KEY — base64-encoded signing key JSON. Generate once and
keep stable — rotating this key invalidates all issued tokens:
atlogin -init -state-dir /tmp/init
cat /tmp/init/signing-key.json | base64 -w0
Both variables must be on a single line (no newlines in the base64 value).
client credentials #
To provision a client (e.g. Tailscale), generate the client ID and secret
using gen-client.sh:
./gen-client.sh <login-email> <app-name> <master-key>
# e.g.:
./gen-client.sh alice@example.com Tailscale abc123...
# Client ID: alice-at-example-com-Tailscale-v1
# Client Secret: <deterministic base64>
Add the output to the secrets (and optionally redirect_uris) in your
ATLOGIN_CONFIG.
The client secret is deterministic: base64(HMAC-SHA256(client_id, master_key)).
Running the script again with the same inputs produces the same secret.
flags #
-state-dir <path> State directory for signing key and config (default: ./state)
-init Initialize state directory and exit
-new-client <id> Add a new client to config.json and print the secret
webfinger #
atlogin serves WebFinger for its own hostname automatically. For users to log
in as user@otherdomain.com, otherdomain.com needs to proxy
/.well-known/webfinger to your atlogin instance's /helpers/webfinger:
location /.well-known/webfinger {
proxy_pass https://your-instance.example.com/helpers/webfinger;
}
This also works with a Cloudflare Worker or any static redirect.
NixOS #
A NixOS module is included in the flake:
{
inputs.atlogin.url = "git+https://tangled.org/anirudh.fi/atlogin";
# in your NixOS config:
imports = [ atlogin.nixosModules.default ];
services.atlogin = {
enable = true;
environmentFile = "/etc/secrets/atlogin.env"; # contains ATLOGIN_CONFIG and ATLOGIN_SIGNING_KEY
};
}
OIDC endpoints #
| Endpoint | Description |
|---|---|
/.well-known/openid-configuration |
OIDC discovery |
/.well-known/jwks.json |
Public keys |
/.well-known/webfinger |
WebFinger |
/helpers/webfinger |
WebFinger helper for reverse proxying |
/authorize |
Authorization |
/token |
Token exchange |
/userinfo |
User info |