Identity for your apps, on open standards
Shows you in.
One account. Every door.
Usher signs people in to your apps with OpenID Connect and OAuth 2.1: passkeys, codes, social sign-in and organisations, with every environment kept apart from every other.
Free to start. Runs on your own servers too.
- OpenID Connect
- OAuth 2.1
- PKCE
- WebAuthn
- JWKS
- DPoP
- TOTP
The Lobby
A sign‑in page that welcomes, not interrogates.
The Lobby is the sign-in page Usher hosts for your app. It always says where the person is going, and it looks the same whichever product sent them, so people learn to trust it.
- Names the product you’re going to, every time.
- Passkeys, emailed codes and magic links, with passwords only if you want them.
- The same calm page, light or dark, in every product.
This is a working copy of the Lobby. It runs in this page, sends nothing, and signs nobody in.
Standards, not lock-in
Speaks the protocols your stack already knows.
Usher is an OpenID Connect provider first. Use our SDK when it suits you, or any OIDC library in any language. If you ever leave, your apps don’t have to change how they sign people in.
- OIDC Core · Discovery
OpenID Connect
A discovery document per environment, ID tokens and userinfo. Any certified client library works.
- S256 only
OAuth 2.1 with PKCE
Authorisation code with PKCE for every client, public or confidential. No implicit flow, no passwords in the token endpoint.
- RS256 · ES256 · EdDSA
JWKS and key rotation
Each environment signs with its own keys, published at its jwks_uri. Rotate them without breaking signed-in people.
- RFC 7662 · RFC 7009
Introspection and revocation
Ask whether a token is still good, or end it now. Usher checks its own sessions on every request, so ending one takes effect at once.
RP-initiated and back-channel logout
Sign out from your app, or have Usher tell every app when a session ends anywhere.
- RFC 9449
DPoP
Bind tokens to a key the client holds, so a stolen token is no use on its own.
- WebAuthn
Passkeys
First-class, not an add-on. People sign in with a fingerprint, a face or a security key, and there is nothing to phish.
- TOTP
Authenticator apps
Time-based codes from any authenticator, with backup codes. Make a second factor optional or required.
Magic links
One tap from the inbox. Short-lived, single-use, and bound to the environment that sent them.
Email codes
Six digits, for people who sign in on one device and read email on another.
Social sign-in
Google, GitHub, Microsoft and Apple. An account is only linked when its email has been proved.
Organisations and teams
Members, roles and invitations for the businesses that use your app.
- Coming soon
SAML
Single sign-on with your customers’ workforce identity providers.
- Coming soon
SCIM
Provisioning and deprovisioning from your customers’ directories.
How a sign-in works
Five steps, every one of them standard.
This is OpenID Connect’s authorisation code flow with PKCE, the same flow every serious identity provider speaks. Scroll through it.
- 01
Your app sends them to the Lobby.
When someone chooses Continue with Usher, your app redirects them to your environment’s authorisation endpoint, with a PKCE challenge and a state value it will check later.
GET acme.id.hallelo.net/api/auth/oauth2/authorize ?response_type=code &client_id=acme_web &redirect_uri=https://app.acme.example/callback &scope=openid profile email offline_access &state=k3Jx… &code_challenge=E9Mel…cM &code_challenge_method=S256
- 02
They sign in, their way.
A passkey, an emailed code, a magic link, a social account or a password: the Lobby offers only what your environment allows, and always says which app they’re going to.
UsherUsherContinuing to AcmeWelcome back.
Use a passkeyEmail me a code - 03
They agree to share.
The first time, people see what your app is asking for (their profile, their email, staying signed in) and agree once.
Acme would like to:
- See your name and picture
- See your email address
- Keep you signed in
CancelAllow - 04
Usher hands back a code.
The browser returns to your redirect URI with a single-use code that lasts five minutes, and the state you sent, which your app checks.
302 Found Location: https://app.acme.example/callback ?code=SplxlOBeZQQYbYS6WxSbIA &state=k3Jx…
- 05
Your app swaps it for tokens.
Your server sends the code and the PKCE verifier to the token endpoint. Back come an ID token that says who they are, an access token for your API, and a refresh token to stay signed in.
POST acme.id.hallelo.net/api/auth/oauth2/token grant_type=authorization_code&code=Splx… &code_verifier=dBjftJeZ4CVP… { "token_type": "Bearer", "access_token": "eyJhbGciOiJSUzI1NiIs…", "id_token": "eyJhbGciOiJSUzI1NiIs…", "refresh_token": "8xLOxBtZp8…", "expires_in": 600 }
Isolation by design
Every environment is a building of its own.
Your production and development environments, and every other customer’s, share nothing that matters. A mistake in one cannot read, sign for or sign in to another.
- Its own Postgres schema
- Not a tenant column in a shared table: a schema made for the environment, and nothing else in it.
- Its own signing keys
- Tokens from one environment can never verify against another’s keys.
- Its own sealed secrets
- Sealed with the environment’s own data key, which is itself wrapped by a master key.
- Its own host
- Issuers live on a separate domain from Usher’s site and Console, with host-only cookies.
usher.hallelo.net
The site, Usher Console, Usher accounts
*.id.hallelo.net
One issuer per environment, on a separate domain
Users, sessions and tokens live in a schema made for this environment. Queries run against that schema, never a shared table with a tenant column.
Copy Acme production’s sealed Google client secret into Northwind production.
For developers
Your first sign‑in, three ways.
Drop in the SDK when it arrives, use the OIDC library you already trust, or start by reading the discovery document. It’s all the same protocol underneath.
// app/layout.tsx
import { UsherProvider } from '@usher/nextjs'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<UsherProvider>{children}</UsherProvider>
</body>
</html>
)
}
// app/page.tsx
import { SignIn, UserButton, currentUser } from '@usher/nextjs'
export default async function Page() {
const user = await currentUser()
return user ? <UserButton /> : <SignIn />
}Run it yourself
Your servers, your keys.
Use Usher as a service, or run the same platform on your own servers, with your own keys and your own database.
Docker
One Compose file: the apps, Postgres, Valkey, the Caddy edge and the egress proxy. Containers run with no new privileges and with capabilities dropped.
Ubuntu
A plain Ubuntu server is enough. Caddy terminates TLS, and secrets are files handed to the containers, never baked into images.
Postgres
Postgres holds everything, one schema per environment. It and Valkey sit on a network with no route to the internet.
# on an Ubuntu server with Docker Engine and the Compose plugin
bash infra/scripts/setup-env.sh --prod \
--domain usher.example --tenant-domain usherid.example
docker compose -f compose.yaml -f compose.prod.yaml \
--profile core --profile apps --profile edge up -d --wait
pnpm db:migrate # applies the database migrationsThe full self-hosting guide is on its way. The commands above are the shape of it.
Show your users in.
Create a workspace and a project, and your first environment comes with its own issuer, keys and Lobby.
Sign in once. Welcome everywhere.