AuthTo.it

Documentation

Hosted sign-in for your website or app. People sign in with a one-time code sent by email or text message; there are no passwords to store. Your site gets a signed ID token it can trust, through standard OpenID Connect, so any OpenID Connect library connects with one setting: the discovery URL.

Getting access

Sign in with your ScaledApps account and pick your organization: its authto.it account starts on the Free plan (one site). Under Add site, give the site's name and the addresses it should return to after sign-in (and, optionally, after sign-out). Return addresses must be https; http is accepted only for localhost and 127.0.0.1, for development. You receive a client ID and, for a server-side site, a client secret (shown once; keep it on your server). Browser and mobile apps that can't keep a secret are set up as public clients and use PKCE alone. Each site has its own people: the same email on two sites is two unrelated accounts, and each gets its own permanent ID.

A site is either invite-only (only people added to it can get a code, for staff areas) or open (anyone who proves their email or phone becomes one of the site's people on first sign-in). People on a site can be given a name and a role, which your site receives as claims, and can be suspended: a suspended or removed person is signed out everywhere and their refresh tokens stop working.

Per site you also choose:

  • Sign-in methods: email, text message, or both.
  • Branding for the sign-in page: the name shown, a logo, an accent colour, a support email, and links to your privacy policy and terms.
  • How long people stay signed in on authto.it (a week unless you ask otherwise) and how long refresh tokens last (30 days unless you ask otherwise).

A new client secret can be issued at any time. The old one can keep working for up to 30 days alongside it, so you can deploy the new one without an outage.

Connect

Discoveryhttps://letme.authto.it/.well-known/openid-configuration
Issuerhttps://letme.authto.it
Endpoints/authorize, /token, /userinfo, /logout, /jwks.json on the issuer
FlowAuthorization code (response_type=code, query response) with PKCE (S256); refresh tokens. No implicit or hybrid flow.
Scopesopenid (required), email, phone, profile (name and role), offline_access (refresh token)
Client authenticationclient_secret_basic or client_secret_post; browser and mobile apps use PKCE alone
SigningRS256, keys at https://letme.authto.it/jwks.json. Pick the key by the token's kid; keys are rotated, with the previous one published until the tokens it signed have expired, so fetch the set rather than copying a key.

1. Send the person to sign in

GET https://letme.authto.it/authorize
  ?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https://your.site/auth/callback
  &scope=openid%20email%20profile
  &state=RANDOM_PER_BROWSER
  &nonce=RANDOM_PER_BROWSER
  &code_challenge=BASE64URL(SHA256(VERIFIER))
  &code_challenge_method=S256

redirect_uri must match one you registered exactly; an unknown client ID or return address shows an error page and never redirects. code_challenge is required for public clients and recommended for everyone. Add login_hint= an email or phone number to fill it in on the sign-in page. Add prompt=login to always ask for a code, or prompt=none to check silently (you get error=login_required back if they aren't signed in). Someone already signed in to your site on authto.it comes straight back without a code.

Other problems come back to your redirect_uri as error=…&error_description=…&state=…: unsupported_response_type, invalid_scope (no openid) or invalid_request. A sign-in left unfinished for 30 minutes, or continued in a different browser from the one that started it, has to be started again from your site.

2. Exchange the code

They return to redirect_uri?code=…&state=…&iss=https://letme.authto.it. Check state, then within 60 seconds:

POST https://letme.authto.it/token
Authorization: Basic BASE64(CLIENT_ID:CLIENT_SECRET)
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=CODE&redirect_uri=https://your.site/auth/callback&code_verifier=VERIFIER

Server-side sites may send client_id and client_secret in the form body instead; public clients send client_id and the code_verifier with no secret. The answer has id_token and access_token (each valid an hour), token_type, expires_in, scope, and with offline_access a refresh_token. A code works once. Errors are invalid_client (401), invalid_grant or unsupported_grant_type.

3. Check the ID token

Verify the RS256 signature with the published keys, and that iss is the issuer above, aud is your client ID, exp is in the future and nonce is the one you sent. Its claims:

  • sub: the person's permanent ID on your site
  • email, email_verified or phone_number, phone_number_verified
  • name and role (with profile), when set for them
  • auth_time, sid, amr (["otp"] for an emailed code, ["sms"] for a texted one)

A person signs in with either an email address or a phone number, so they have one or the other, not both. GET or POST /userinfo with the access token as a bearer token returns the same person claims; it stops working once the person is suspended.

Refresh and sign-out

A refresh token is replaced every time it is used (grant_type=refresh_token). Using one twice ends that sign-in, since it means it was copied. To sign someone out of authto.it too, send them to https://letme.authto.it/logout?client_id=…&post_logout_redirect_uri=…&state=…; an id_token_hint can stand in for client_id. They return to post_logout_redirect_uri (with your state) only if it is a registered sign-out address; otherwise authto.it shows a signed-out page.

The sign-in page

People sign in on letme.authto.it, on a page showing your site's name, logo and colour. They choose email or text (if your site offers both), enter their address or number, and type the code they receive. Phone numbers are stored in international format; a ten-digit number is read as US/Canada. Texted codes end with a line that lets phones offer to fill in the code automatically.

Sign in with Google or Microsoft

A site can also offer Continue with Google and Continue with Microsoft (site settings, "Also sign in with"). The account must have an email address the provider vouches for: Google's verified addresses, personal Microsoft accounts, and work or school accounts whose organization has verified the address's domain. That address is then treated exactly like one proven with a code — on an invite-only site it must be on the People list — and the site receives the same person (sub) whichever way they sign in. The ID token's amr is fed for these sign-ins, otp or sms for codes.

Codes and limits

  • Six digits, valid ten minutes, five tries each, usable once. A new code replaces the previous one.
  • Sending is limited per address and per network; wrong codes are limited per address per day.
  • Emails come from noreply@scaledapps.com as "Your Site via authto.it". A number agrees to receive texts from your site once, before its first code.
  • Replying STOP to a text stops all texts from authto.it's number, for every site; replying START turns them back on. While a number is opted out, asking for a code by text says so instead of pretending it was sent.
  • On an invite-only site, an address that isn't listed is told the same thing as one that is, and nothing is sent.

Plans

  • Free: 1 site, 1,000 people signing in a month, email codes.
  • Starter: 3 sites, 10,000 people signing in a month, email codes, your logo and colours.
  • Pro: 10 sites, 50,000 people signing in a month, 200 text-message codes a month, your logo and colours.

More people than the plan includes are never turned away; the dashboard asks you to change plan. Plans are changed in ScaledApps.

Management API

Issue a key on the dashboard's API keys page (owners and admins) and send it as Authorization: Bearer as_…. Scopes: sites:read, sites:write, people:read, people:write, events:read. Base address https://authto.it/api/v1.

  • GET /sites, GET /sites/{id}; POST /sites (name, redirect_uris, optional client_type, signup, methods, …) returns the client_secret once; PATCH /sites/{id} changes what you send; POST /sites/{id}/secret issues a new secret (grace_days for the old one, default 7).
  • GET /sites/{id}/people?q=, POST /sites/{id}/people (identifier, optional channel, name, role), PATCH /sites/{id}/people/{person} (name, role, status: active or suspended), DELETE /sites/{id}/people/{person}. A person's id is the sub your site receives.
  • GET /sites/{id}/events?type=&limit=: recent activity, addresses masked.

Errors: {"error": {"code": "…", "message": "…"}} — invalid_key, rate_limited, not_found, invalid_request, site_limit, public_client.

AI tools (MCP)

Assistants that speak the Model Context Protocol can manage sites and people at https://authto.it/mcp with the same key. Client secrets are never given to AI tools: a server-side site made there gets its secret on the dashboard.

claude mcp add --transport http authto https://authto.it/mcp --header "Authorization: Bearer as_…"