fork of apenwarr's atlogin
Go 91%
Nix 7%
Shell 1%
Dockerfile <1%

README.md

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