Mock authentication#

Each workspace has its own mock auth server. It issues RS256 test tokens under its own issuer URL, and any mock you protect accepts only tokens signed by that workspace — so you can build and test the “my app has to send a bearer token” path without wiring up a real identity provider.

Authorization code, PKCE, a hosted login page and tokens from other providers (Auth0, Cognito, Keycloak) are not supported. This issues test tokens for the client credentials, password and refresh_token grants only — enough to exercise the paths your code takes when it holds a token, not a login UI.

How do I set up test clients and users?#

  1. 1Open the workspace's Auth tab.
  2. 2Create a test client: a name, the scopes it is allowed to request, access and refresh token lifetimes, and whether it is public or confidential. A confidential client's secret is shown once, right after creation — copy it then, because the dashboard never shows it again.
  3. 3Add a test user: a username and password for the password grant, plus any roles and custom claims you want baked into its tokens.
The workspace Auth tab with the issuer URLs, a test client and a test user
Each workspace is its own issuer, with test clients and test users for your mock APIs.

A workspace can have at most 5 test clients and 20 test users. Use Edit on a client to change its allowed scopes and token lifetimes, or on a test user to change its roles, custom claims or password.

Scope and role names follow RFC 6749's scope-token rule: printable ASCII with no spaces, double quotes or backslashes, such as orders:read. Anything else is refused when you save it.


How do I get a token?#

A confidential client authenticates at the workspace's token endpoint with HTTP Basic auth (client_id:client_secret), or with client_id and client_secret as form fields in the request body. A public client has no secret and sends only client_id in the body. Three grants are supported.

bash·client_credentials — a token for the client itself
curl -u mlc_CLIENT:mls_SECRET -d grant_type=client_credentials -d scope=orders:read \
  https://api.mocklane.com/oauth/abc-xyz/token
bash·password — a token for one of your test users
curl -u mlc_CLIENT:mls_SECRET -d grant_type=password -d username=ada -d password=pa55word \
  https://api.mocklane.com/oauth/abc-xyz/token
bash·refresh_token — exchange a refresh token for a new pair
curl -u mlc_CLIENT:mls_SECRET -d grant_type=refresh_token -d refresh_token=mlrt_… \
  https://api.mocklane.com/oauth/abc-xyz/token

A successful response looks like this.

json·200 OK
{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 900,
  "scope": "orders:read",
  "refresh_token": "mlrt_..."
}

refresh_token is only present for grants that started from a test user (password, and the refresh that follows it). An error response uses the same error / error_description shape, with one of these codes:

  • invalid_request — a required parameter, such as grant_type, is missing, or a parameter was sent as a file instead of a text field.
  • invalid_client — the client id or secret is wrong, or the client does not exist.
  • invalid_grant — the username or password is wrong, or the refresh token is invalid, revoked, expired or already used.
  • unauthorized_client — a public client tried client_credentials, which needs a client secret.
  • unsupported_grant_type — the grant type is not one of the three above.
  • invalid_scope — a requested scope is not in the client's allowed scopes, or a refresh tried to widen the original scope or asked for a scope the client is no longer allowed.

How do I protect a mock?#

In the mock editor, switch on Require a bearer token, then set required scopes (a request needs all of them) and required roles (a request needs any one of them).

The mock editor with Require a bearer token switched on and a required scope
A protected mock refuses requests without a valid token from this workspace.

Every refusal follows RFC 6750: a WWW-Authenticate header plus a JSON body with error and error_description.

  • No token — 401, WWW-Authenticate: Bearer realm="mocklane", body {"error": "invalid_request", "error_description": "A bearer token is required"}.
  • Bad or expired token — 401 with error="invalid_token" and an error_description naming the reason (expired, not yet valid, malformed, wrong issuer, unknown key, revoked, and so on).
  • Missing scope — 403, Bearer realm="mocklane", error="insufficient_scope", scope="…". This header carries no error_description.
  • Missing role — 403 with error="insufficient_scope" and error_description="Requires one of the roles: …".

Every refusal is also recorded on the mock's Logs tab, in the Auth column, so you can see why a call was turned away without reproducing it. Mocks send Access-Control-Expose-Headers: WWW-Authenticate, so browser code can read the refusal header too.

A protected mock's response body can read the token's claims with {{request.auth.sub}} and {{request.auth.claims.email}}. Response rules can branch on the same data: a condition's field can be auth.roles or auth.claims.<name>, for example:

json·Rule condition on a custom claim
{ "field": "auth.claims.email", "operator": "equals", "value": "ada@acme.test" }
Don't use contains on auth.roles as a role check. It matches as a substring against the list's string form, so a check for admin also matches a user whose only role is superadmin. Use equals against a single role, or the mock's own required roles field instead.

How do I test expiry, refresh and revocation?#

  • Access and refresh token lifetimes are set per client, from 10 seconds up to one year — short enough to watch a token expire without waiting an hour.
  • Refresh tokens rotate: each use returns a new refresh token, and reusing an already-used one revokes its whole token family, not just that one token. A refresh can ask for a narrower scope; that narrows only the new access token, and the new refresh token keeps the original scope (RFC 6749 section 6).
  • POST /oauth/{workspace}/revoke revokes a specific refresh token (RFC 7009: it always returns 200, whether or not the token was known).
  • Revoke all sessions on a test user invalidates every access token already issued to them, not just their refresh tokens.
  • Rotating the signing key starts signing new tokens with the new key, while old keys stay in JWKS — and valid — until you remove them.

How do I make an expired or invalid token on purpose?#

The Mint a token panel signs a token directly, bypassing any grant, so you can hand your app exactly the failure case you want to test: already expired, not yet valid, signed with a key that is not in JWKS, or carrying whatever scopes, roles and custom claims (a JSON object) you type in. Leave roles blank to use the test user's own. A minted token always carries mocklane_minted: true, so it is easy to spot in a log.

The Mint a token panel showing a signed token with its decoded header and claims
Mint a token with any scopes and timing to test the paths your client must handle.

How do I use tokens in a scenario?#

A scenario's Get token step takes a client and a grant. For the password grant it only needs a test user's username, never a password — the step runs inside your own workspace, so it can mint the user a token without you handing it a secret to store in the scenario definition.

By default it captures access_token, plus refresh_token when the grant returned one. Checks on the step read the token response, for example access_token exists or scope == "orders:read"; a failing check, or a capture of a field the response does not have, fails the step. Use the captured value in a later step:

json·Get token, then call a protected mock
{ "type": "get_token", "client_id": "mlc_CLIENT", "grant": "password", "username": "ada" },
{
  "type": "http_request",
  "method": "GET",
  "url": "https://mocklane.com/m/{workspace}/api/me",
  "headers": { "Authorization": "Bearer {{access_token}}" },
  "assert": ["status == 200"]
}

How does my backend verify these tokens?#

If your own service also needs to verify these tokens — rather than only your test client sending them to a mock — it can treat the workspace like any other OIDC issuer.

  • Discovery: GET /oauth/{workspace}/.well-known/openid-configuration. It lists the token, JWKS, userinfo and revocation endpoints, the supported grants and scopes — there is no response_types_supported, since there is no authorization endpoint to redirect to.
  • Keys: GET /oauth/{workspace}/jwks.json.
  • Check the issuer claim against the workspace's issuer URL, and accept only RS256 — verifying a token by its own alg header without pinning the algorithm is how a forged unsigned or HMAC-“signed” token gets accepted.

MockLane © 2026 · Built for developers

Go to Dashboard