Platform
Environments
Give each environment of your product its own tenant: one for production, one for staging, one for development. Each tenant has its own users, apps and sign-in address, so the environments stay apart however your domains are arranged.
The short version
- Create one tenant per environment, named for it:
Production,Staging,Development. Your users never see these names; they see the tenant's product name. - Create your app in each tenant. Each app lists the redirect URIs of its own environment only, and gets its own client ID and secret.
- Configure your code per environment. Your app reads three values from its environment's configuration: the tenant's issuer, the client ID and, for a server-side app, the client secret. The code is the same everywhere.
- Your domains don't need to be related. The sign-in happens on the tenant's address, and the browser is redirected back to your app. Authalla never needs your app and the sign-in page to share a domain or cookies, so
acme.comwithacme.devworks exactly likeacme.comwithdev.acme.com.
Example: environments on subdomains
Acme runs production on acme.com, staging on stage.acme.com and development on dev.acme.com, plus developers' own machines.
| Environment | Acme's app | Tenant | Sign-in address (issuer) | Redirect URIs on the tenant's app |
|---|---|---|---|---|
| Production | https://acme.com | Production | https://auth.acme.com | https://acme.com/auth/callback |
| Staging | https://stage.acme.com | Staging | https://auth.stage.acme.com | https://stage.acme.com/auth/callback |
| Development | https://dev.acme.com, localhost | Development | https://{tenant-id}.authalla.com | https://dev.acme.com/auth/callback, http://localhost/auth/callback |
Each environment's configuration then holds its own values:
# Production
AUTHALLA_ISSUER=https://auth.acme.com
AUTHALLA_CLIENT_ID=<the Production app's client ID>
AUTHALLA_CLIENT_SECRET=<the Production app's secret>
# Staging
AUTHALLA_ISSUER=https://auth.stage.acme.com
AUTHALLA_CLIENT_ID=<the Staging app's client ID>
AUTHALLA_CLIENT_SECRET=<the Staging app's secret>
Your OIDC library finds everything else from ${AUTHALLA_ISSUER}/.well-known/openid-configuration, so no endpoint URLs need to be configured.
A custom domain is optional in every environment. A tenant can have one, so production and staging can each have their own, as above. Development often doesn't need one: the tenant's own address on authalla.com works the same way.
Example: development on a separate domain
Now Acme runs production on acme.com and development on acme.dev, a domain of its own. Nothing changes:
| Environment | Acme's app | Sign-in address (issuer) | Redirect URI |
|---|---|---|---|
| Production | https://acme.com | https://auth.acme.com | https://acme.com/auth/callback |
| Development | https://acme.dev | https://auth.acme.dev or https://{tenant-id}.authalla.com | https://acme.dev/auth/callback |
This works because no part of the sign-in depends on the domains being related:
- The sign-in is a redirect. Your app sends the browser to the tenant's sign-in address, and Authalla sends it back to a redirect URI you registered. Any address you register works.
- Each side keeps its own session. Your app's session cookie stays on your app's domain. Authalla's sign-in session stays on the tenant's sign-in address. Neither reads the other's cookies.
- A custom domain goes on any domain you control. Each tenant's custom domain is set up separately, with its own DNS records, so
auth.acme.comandauth.acme.devdon't need to share anything.
The same holds for a production product spread over several domains, such as acme.com and acme.fi: one tenant can have apps, or redirect URIs, on any number of domains.
What each environment keeps to itself
Everything a tenant holds is separate per environment:
- Users. A user who signs up in staging doesn't exist in production, even with the same email address. Test sign-ups never reach your production users.
- Apps and secrets. A staging app's client ID and secret can't get tokens from the production tenant.
- Tokens. Each tenant's tokens carry its own issuer (
iss). An API that checksissagainst the production issuer turns staging tokens away. See Sessions & tokens. - Sign-in settings. Sign-in methods, connections, branding, hooks, organizations and resource servers are set per tenant, so you can try a change in staging before production has it.
- Social sign-in callbacks. Each tenant has its own redirect URI for social connections, on its own sign-in address. At the provider, add each tenant's redirect URI to one OAuth app, or give production an OAuth app of its own.
Some things belong to your account and are shared by every environment:
- Team members. Your team sees every tenant in the dashboard.
- Sender addresses. Verify
hi@acme.comonce and choose it in any tenant, or send staging's emails from Authalla's address. - Account API keys. A key can be limited to some tenants, for example so CI for staging can't change production.
Choose the production sign-in address before you launch
A tenant's sign-in address is part of what its users and apps hold on to:
- Passkeys belong to the address they were created on. A passkey created on
{tenant-id}.authalla.comisn't offered onauth.acme.com. If production moves to a custom domain after launch, users who signed in with passkeys sign in once with their email and create a new passkey. - Sessions belong to the address. Users signed in on the old address sign in again on the new one.
- The issuer changes. Apps and APIs need the new issuer in their configuration.
So set up production's custom domain before your first users sign in. In development and staging this rarely matters: test users can sign in again.
Local development
Point developers' machines at the development tenant, never at production.
- Redirect URIs on localhost match any port. Registering
http://localhost/auth/callbackcovershttp://localhost:3000/auth/callback,http://localhost:5173/auth/callbackand every other port.127.0.0.1and[::1]work the same way but are separate hosts, so add them only if your app uses them. - Allowed origins match exactly. A browser-based app (SPA) that calls Authalla from
http://localhost:5173needs that exact origin, port included, in the tenant's Allowed origins. Server-side apps don't need allowed origins.
Preview deployments
Redirect URIs match exactly, apart from the port on localhost, so Authalla refuses a wildcard such as https://*.preview.acme.dev/auth/callback when an app is saved. OAuth 2.1 requires exact matching: with a wildcard, anyone who can serve a page on a matching host can receive your users' authorization codes.
To sign in to short-lived preview deployments:
- Add each preview's redirect URI when it's deployed. Your CI updates the development tenant's app through the Management API, with an Account API key limited to that tenant, and removes the URI when the preview goes away.
PUT /management/v1/tenants/{tenant_id}/apps/{app_id}replaces the app's redirect URIs with the list you send, so read the app first and send its current URIs plus the new one. - Or give previews a fixed address. If your hosting gives each branch a stable address, register those instead.
A browser-based app also needs each preview's origin in the tenant's Allowed origins. Your CI adds it with POST /management/v1/tenants/{tenant_id}/allowed-origins when the preview is deployed and removes it at teardown, with the same key given tenants:write; see Allowed origins from a deployment pipeline. Adding an origin that is already allowed succeeds without changing anything, so the step can run on every deploy. A server-side app doesn't need one.
Keeping environments in step
Authalla doesn't copy configuration from one tenant to another. Set up each environment the same way, and change staging first:
- With an AI assistant: connect the MCP server and ask, for example, "Create a Staging tenant set up like Production, with an app for https://stage.acme.com".
- With a script: the Management API creates tenants, apps, custom domains, social connections and themes, so a script kept with your code can set up every environment from one description.
What not to do
- Don't share one tenant between production and test environments. Test sign-ups become production users, and a staging redirect URI on a production app lets staging receive production users' sign-ins.
- Don't make a business customer a tenant just for single sign-on. A customer whose staff sign in through their own identity provider is an Organization in each environment's tenant. A customer gets a tenant of its own when it needs its own users, brand and domain. See Tenant setup.