
# Signing in test users with keenauth test login

`keenauth test login` signs a test user in to a `development` environment and prints the tokens, so you can call your API or test your backend without clicking through the login page. It goes through the real hosted login, the same way a browser does, and uses the fixed development code `424242` (see [Test users and messages in non-production environments](/docs/guides/test-environments)).

The command needs no API key and no `keenauth login` session: it signs in the test user, not you.

## Before you start

- **A test user.** The address must carry the `+test` tag in its local part, for example `ada+test@example.com` or `ada+test_1@example.com`. Create the user first, for example with `keenauth users create --email ada+test@example.com`. The command refuses any other address before it sends a request, and exits with code 2.
- **A public application.** Pass the ID of a native or single-page application of the environment with `--client-id`. The application must allow sign-in with an email code (passwordless `email_otp`).
- **A redirect URI.** By default the command uses `http://127.0.0.1:<random port>/callback`. Register `http://127.0.0.1/callback` for the application: a loopback redirect URI accepts any port (RFC 8252). Nothing listens on that address; the command reads the redirect itself. To use another registered redirect URI, pass `--redirect-uri`.
- **The environment's issuer.** Pass it with `--issuer` or set `KEENAUTH_ISSUER`, for example `https://acme-dev.keenauth.app`.

## Usage

```sh
export KEENAUTH_ISSUER=https://acme-dev.keenauth.app
keenauth test login --user ada+test@example.com --client-id app_01j9z3k4m5n6p7q8r9s0t1v2w3
```

| Flag | Description |
|---|---|
| `--user` | Email address of the test user (required). |
| `--client-id` | ID of a public application of the environment (required). |
| `--issuer` | Issuer URL of the environment. Default: the `KEENAUTH_ISSUER` environment variable. |
| `--scope` | Scopes to request, separated by spaces. Default: `openid profile email`. Add `offline_access` to receive a refresh token. |
| `--resource` | Resource indicator of the API the access token is for (RFC 8707). |
| `--redirect-uri` | A redirect URI registered for the application. |
| `--output`, `-o` | `json`, `yaml` or `table`. Without the flag the CLI prints a table on a terminal and JSON otherwise. |

The output holds `token_type`, `access_token`, `id_token` (when `openid` is requested), `refresh_token` (only when `offline_access` is requested), `expires_in` and `scope`. Treat the tokens like passwords: they act as the test user until they expire. The command writes them to stdout and nowhere else, and it never prints the code.

```sh
ACCESS_TOKEN=$(keenauth test login --user ada+test@example.com --client-id app_01j9z3k4m5n6p7q8r9s0t1v2w3 -o json | jq -r .access_token)
curl -H "Authorization: Bearer $ACCESS_TOKEN" http://localhost:3000/api/orders
```

## What the command does

1. Reads the environment's discovery document and opens an authorization request with PKCE (`S256`), `state` and `nonce`.
2. On the hosted login, enters the address, asks for an email code and submits `424242`. If the environment then asks for an email code as a second factor, it asks for that code and submits `424242` again.
3. Follows the redirect back to the redirect URI, checks `state` and `iss`, and exchanges the authorization code at the token endpoint.
4. Checks that the ID token was issued by the issuer for your application with the request's `nonce`, and prints the tokens.

The command stops with an error when the sign-in needs anything else, for example a password, a passkey, an authenticator app, enterprise single sign-on for the address's domain, a consent page or an organization choice.

## Environments

Only `development` environments accept the fixed code. Use the command only with a development environment:

- In `staging`, nothing is sent to test addresses, but the fixed code is refused (`401 invalid_credentials`).
- In `production`, a `+test` address is an ordinary address: the email code is really sent to it, and the fixed code is refused.

When the server refuses a step, the CLI prints its problem response unchanged (`error`, `code`, `status`, `detail`, `request_id`), for example `code: test_feature_not_in_production`.

## Exit codes

| Code | Meaning |
|---|---|
| `0` | The test user is signed in; the tokens are printed. |
| `1` | The sign-in failed: an OAuth error, a `state`, `iss` or `nonce` mismatch, or a step the command cannot complete. |
| `2` | Usage error: the address has no `+test` tag, a required flag or the issuer is missing, or a value is invalid. No request was sent. |
| `3` | The server answered `401` or `403`, for example `invalid_credentials` or `test_feature_not_in_production`. |
