# Identity environments

Your app runs in more than one place: on a laptop, in CI, on a staging server and in production. An identity environment gives each of those places its own directory of app users. A person who signs in to your local build is a different user from the same person in production, and a key made for development cannot sign anyone in to production.

Each environment has its own:

- **Users**, with their sessions and linked social accounts. An email address is unique within an environment, so the same address can exist in two environments as two separate users.
- **Keys**. A key with `identity:auth` belongs to one environment.
- **Web origins and redirect URIs**, where a browser may call Mocco from and where social sign-in may return to.
- **Social provider apps**, the client IDs and secrets for Google, Apple and the others.
- **Sign-in access**, the rule for who may sign in.
- **Token audience**, the `aud` claim of the access tokens it issues.

The issuer, the signing key and the JWKS are shared by every environment of a project, and so are the provider callback URLs.

## The two defaults

A project gets two environments the first time it needs one, for example when you open the **App users** page or create a key with `identity:auth`:

| Environment | Who may sign in | Audience | Web origins and redirect URIs | Use it for |
| --- | --- | --- | --- | --- |
| `production` | Anyone | The project ID | Copied from the project's apps when it was created | Your real users |
| `development` | Workspace members | Its own ID | None | Local development and other work by your team |

`production` is the project's first environment. Keys, console actions and agent tools that don't name an environment use it. Its audience is the project ID, so verifiers that expected the project ID before environments existed keep working.

If your project used sign-in before environments existed, it has only `production`, which holds your existing users. Add a `development` environment yourself, as below.

## Add an environment

Add one environment for each place your app runs that should have users of its own, such as `staging` or `ci`. A project can have up to ten.

In the console, open the project's **App users** page, then use **Add environment** next to the environment switcher. Give it a key (lower-case letters, digits and hyphens, up to 32 characters) and a name. The key can't be changed later. A new environment has its own audience and only workspace members can sign in to it; set its web origins and redirect URIs on its **Environment settings** tab.

An agent can do the same over MCP:

- `mocco_identity_environments_search` lists a project's environments with their IDs, audiences and settings, and the provider callback URLs.
- `mocco_identity_environments_create` adds one, with a `key`, a `name`, and optionally `webOrigins` and `redirectUris`.
- `mocco_identity_environments_update` changes the name, origins, redirect URIs, sign-in access, allowed emails and test addresses.
- `mocco_identity_environments_remove` removes one.

Changes need the `identity:write` scope, the workspace's agent opt-in and a confirmed round trip. Any workspace member can read environments; owners and administrators can change them.

The **Overview** tab shows every environment's settings side by side and marks the ones that differ, so you can check that staging matches production where it should.

Removing an environment deletes its users, their sessions and linked accounts, and its provider apps. It can't be undone. Revoke the keys bound to it first; the project's only environment can't be removed.

## Get a key for an environment

A key's environment is chosen when the key is created and doesn't change. To move an app to another environment, create a new key.

- **Console**: on the **API keys** page, tick `identity:auth`. An **Identity environment** select appears; choose the environment the key signs people in to.
- **MCP**: call `mocco_api_keys_create` with `identityEnvironmentId` set to an ID from `mocco_identity_environments_search`. Without it, the key belongs to the project's first environment.

Use a publishable key (`mk_pub_…`) in a web or React Native app. See [API keys](https://www.mocco.club/docs/en/articles/avn27u-api-keys) for the kinds of key and where each one goes.

## Web origins and redirect URIs

A publishable key works from a browser only on its project's web origins or its environment's web origins. Social sign-in may return only to an exact redirect URI of the environment, or to a URL on one of its web origins. See [where sign-in may return](https://www.mocco.club/docs/en/articles/yhw2j7-social-sign-in#2-allow-where-sign-in-may-return) for the rules.

Put each address on the environment that uses it:

- `production`: `https://app.example.com`, `myapp://auth/callback`
- `staging`: `https://staging.example.com`
- `development`: `http://localhost:5173`, `myapp-dev://auth/callback`

Add `localhost` and other loopback addresses only to `development` (or another environment that real users never reach). `production` was seeded from your project's apps, so check that it doesn't carry a local address from before.

## Social providers

Each environment has its own provider apps. Set them on the **Sign-in methods** tab with the environment you want selected in the switcher, or with `mocco_identity_providers_set` and an `environmentId`.

We recommend a separate OAuth app at each provider for each environment, such as "Example (development)" and "Example (production)". A development app can then stay in the provider's testing mode, and a leaked development secret doesn't touch production.

Every environment uses the same callback URL, so register the same address with each provider app:

```text
<IDENTITY_ISSUER>/api/ext/v1/identity/oauth/<provider>/callback
```

The **Environment settings** tab lists the callback URL for each provider. Mocco knows which environment a callback belongs to from the sign-in it started, so the shared URL never mixes environments.

If you import users who signed in with social accounts, configure the environment they go into with the provider apps your current system uses. A provider gives a person a different ID in each app. See [move existing users in](https://www.mocco.club/docs/en/articles/yhw2j7-social-sign-in#7-move-existing-users-in).

## Sign-in access

Each environment says who may sign in. Choose it under **Who can sign in** on the **Environment settings** tab, or with `signInAccess` in `mocco_identity_environments_update`.

| Setting | Value | Who may sign in |
| --- | --- | --- |
| Anyone | `open` | Anyone with an email address or a social account |
| Workspace members | `members` | An address equal to the Mocco account email of an accepted member of the workspace |
| Listed addresses | `allowlist` | An address under **Allowed emails**, or any address at a listed `@domain` (not its subdomains) |

Addresses are compared without case. With social sign-in, the rule is checked against the email the provider has verified; under `members` or `allowlist`, a provider account without a verified email is refused.

The rule is checked at sign-in. Someone who is already signed in keeps refreshing their session after you tighten the rule; revoke their sessions on the **Users** tab to sign them out now.

A staging environment that your QA team or a client should reach usually works best as `allowlist` with `@example.com`.

## Test addresses for end-to-end tests

End-to-end tests can't read an inbox. With **Test addresses** turned on, an address whose local part ends in `+mocco_test` signs in with the fixed code `424242` and gets no email. It skips the sign-in access rule, so it works even where only workspace members may sign in.

**Test addresses only work in an environment where sign-in isn't open.** Because the code is public, Mocco refuses to turn test addresses on while sign-in is **Anyone**, and refuses to open sign-in while they are on. Never use test addresses in `production`.

Turn them on in the environment's **Environment settings** tab, or with `testAddresses: true` in `mocco_identity_environments_update`. Then sign in from a test the same way a person would:

```ts
import { createIdentityClient } from '@mocco/js/identity';

// `storage` keeps the session, as in the social sign-in guide; an in-memory Map is enough in a test.
const identity = createIdentityClient({ publishableKey: process.env.MOCCO_KEY!, storage });

const { challengeId } = await identity.emailStart('someone+mocco_test@example.com');
const session = await identity.emailVerify(challengeId, '424242');
```

Or with plain HTTP:

```sh
curl -s https://api.mocco.club/v1/identity/email/start \
  -H "X-Mocco-Key: $MOCCO_KEY" -H 'Content-Type: application/json' \
  -d '{"email":"someone+mocco_test@example.com"}'
# → {"challengeId":"…"}

curl -s https://api.mocco.club/v1/identity/email/verify \
  -H "X-Mocco-Key: $MOCCO_KEY" -H 'Content-Type: application/json' \
  -d '{"challengeId":"…","code":"424242"}'
```

In a browser test, type the test address and `424242` into your app's own sign-in form.

The usual limits still apply: the code expires after ten minutes, a wrong code counts toward the five attempts, and starting a sign-in is rate-limited. Use a different test address per test run (for example `run-1042+mocco_test@example.com`) when tests run in parallel. Test addresses apply to email codes only; social sign-in always follows the access rule. Once the setting is off, `424242` signs no one in.

## Token audience

Access tokens carry the environment's audience as `aud`, and a token from one environment fails verification in another. Verify each token with the audience of the environment your key belongs to:

- **Read it from `/config`.** `GET /v1/identity/config` with the environment's key returns its `audience` and `environmentKey`. A backend can compare them with the values it expects before it enables sign-in.
- **Pass it to the SDK.** `createIdentityVerifier({ projectId, audience })` in Node, or `VerifierConfig { audience: Some(…), … }` in Rust. See [verify identity tokens](https://www.mocco.club/docs/en/articles/jhr84i-verify-tokens).

`production` keeps the project ID as its audience, which is also the verifiers' default, so a production backend needs no change. Every other environment's audience is its own ID. Keep it in the same configuration as the key, for example `MOCCO_IDENTITY_AUDIENCE` next to `MOCCO_KEY`.

## Local development setup

1. Open **App users**, switch to `development`, and on **Environment settings** add your dev server's origin under **Web origins**, for example `http://localhost:5173`. Add a redirect URI such as `myapp-dev://auth/callback` if a native build uses social sign-in.
2. On **API keys**, create a publishable key with `identity:auth` and choose `development` under **Identity environment**.
3. Put the key and the audience in a git-ignored `.env` file. Read the audience from `/config` or from the environment's settings.

   ```sh
   # .env (listed in .gitignore)
   MOCCO_KEY=mk_pub_…
   MOCCO_IDENTITY_AUDIENCE=…
   ```

4. Start your app and sign in as yourself, with the email address of your Mocco account: `development` admits workspace members. Teammates who aren't members can be added under **Listed addresses** instead.
5. To sign in without an inbox, turn on **Test addresses** and use `you+mocco_test@example.com` with the code `424242`.

Production deploys use a different key from the `production` environment, set in your hosting provider's secrets. The code stays the same; only the key and the audience differ.

## Troubleshooting

| What you see | Why | What to do |
| --- | --- | --- |
| `403 insufficient_scope` | The key doesn't hold `identity:auth` | Create a key with `identity:auth` and choose its environment |
| `403 identity_environment_required` | The key isn't bound to an identity environment, so there is no directory to sign anyone in to | Create a new key with `identity:auth`, choose its environment, and revoke the old key |
| `403 sign_in_not_allowed`, or `error=sign_in_not_allowed` on your social redirect | The environment's sign-in access rule doesn't admit the address | Use a member's account email, add the address or its domain under **Allowed emails**, or use a test address where they are on. Under `members` or `allowlist`, a social account needs a verified email |
| `403 origin_not_allowed` | The page's origin isn't on the project's web origins or the key's environment's | Add the origin to the environment that the key belongs to. A key of another environment is refused from that environment's origins |
| `400 invalid_redirect_uri` | The social redirect isn't on the environment's redirect URIs or web origins | Add it to the key's environment |
| The verifier rejects every token (Node `authenticate` returns `null`, Rust returns `InvalidToken`) | The verifier expects another environment's audience, often the project ID while the key belongs to `development` | Pass the `audience` from `/config` for that key |
| `424242` is refused | Test addresses are off, the address doesn't end in `+mocco_test`, or the challenge expired or ran out of attempts | Check the environment's **Test addresses** setting and start a new sign-in |
| Turning on test addresses is refused | Sign-in is **Anyone** | Choose **Workspace members** or **Listed addresses** first |
