Skip to Content
DocumentationHellō Mockin

Hellō Mockin

A mock login server for Hellō

Overview

Registration and login are one of the first interactions your users will have with your app. It is usually the start of their user journey. Flowing through a production provider during development is tedious, and is impractical in automated testing.

Mockin is a mock of the Hellō OpenID Connect Login Service and implements the authorization, token, introspection, and userinfo endpoints.

  • Development - speeds up development as you won’t be redirecting through the Hellō production server.
  • Testing - simplifies creating end to end tests, and with the /mock APIs, can simulate expired and invalid responses allowing you to ensure your app properly handles all exceptions, improving your security posture.

Mockin simplifies development. Start the login flow by clicking on the [ ō Continue with Hellō ] button. Your browser will redirect to Mockin and then back to your app which will then complete the login flow.

You can call the /mock APIs while testing to change the user or claims returned, simulate errors, and provide invalid or expired tokens, allowing you to ensure your app properly handles all exceptions, improving your security posture.

Mock API

The Mock API provides endpoints to control the behavior of the mock server during testing:

Available Endpoints

  • GET /mock - Get current mock configuration
  • GET /mock/users - List available mock users
  • PUT /mock/user/:user - Set active user (0-4)
  • PUT /mock/:mock - Configure mock behavior
  • DELETE /mock - Reset all mock settings

Mock Users

Mockin provides 5 pre-configured users for testing:

Supported Scopes

Mockin supports all standard OpenID Connect scopes plus Hellō-specific scopes:

Standard Scopes:

  • openid (required)
  • profile (name, nickname, given_name, family_name, picture)
  • email (email, email_verified)
  • phone (phone, phone_verified)

Hellō-Specific Scopes:

  • ethereum (Ethereum wallet address)
  • discord (Discord username and ID)
  • github (GitHub username and ID)
  • gitlab (GitLab username and ID)
  • twitter (Twitter username and ID)
  • banner (Profile banner image)

Error Simulation

You can simulate various OAuth/OpenID Connect errors:

Supported Error Codes:

  • access_denied - User denied access
  • invalid_client - Invalid client credentials
  • invalid_grant - Invalid authorization code
  • invalid_request - Malformed request
  • invalid_scope - Invalid scope requested
  • server_error - Internal server error
  • temporarily_unavailable - Service temporarily unavailable
  • unauthorized_client - Client not authorized
  • unsupported_grant_type - Unsupported grant type
  • unsupported_response_type - Unsupported response type

Supported Status Codes:

  • 200 - Success
  • 202 - Accepted
  • 400 - Bad Request
  • 401 - Unauthorized
  • 403 - Forbidden
  • 404 - Not Found
  • 405 - Method Not Allowed
  • 500 - Internal Server Error
  • 503 - Service Unavailable

Nodejs Usage

Add mockin as a development dependency:

npm i -D @hellocoop/mockin

Run mockin

npx @hellocoop/mockin

Your app does not need to be a Nodejs app. If you have Nodejs 22+ installed, you can run
npx @hellocoop/mockin

Docker Usage

docker run -d -p 3333:3333 hellocoop/mockin:latest

If you are running Mockin with docker-compose or Kubernetes, you will need to set the environment variables for the Mockin service to the hostname the service is running at, and similarly will need to configure your app to redirect to that service when the the [ ō Continue with Hellō ] button is pressed. See https://github.com/hellocoop/packages-js/tree/main/express/tests  for a docker-compose example using Playwright.

Playing with Mockin

You can use Mocking with the Hellō Playground . Run Mockin with either:

  • npx @hellocoop/mockin, or
  • docker run -d -p 3333:3333 hellocoop/mockin:latest

and then in https://playground.hello.dev  add http://127.0.0.1:3333/authorize as an Authorization Server. You can then play with the different parameters and see the responses returned.

You can make curl commands to the /mock API to set mock return values. For example:

Basic Examples

Simulate access denied error:

curl -X PUT "http://127.0.0.1:3333/mock/authorize?error=access_denied"

Switch to a different user:

curl -X PUT "http://127.0.0.1:3333/mock/user/1"

Simulate server error:

curl -X PUT "http://127.0.0.1:3333/mock/token?error=server_error&status=500"

Reset all mock settings:

curl -X DELETE "http://127.0.0.1:3333/mock"

Advanced Examples

Simulate expired token:

curl -X PUT "http://127.0.0.1:3333/mock/token?expired=true"

Simulate invalid client:

curl -X PUT "http://127.0.0.1:3333/mock/authorize?error=invalid_client&status=401"

Get current mock configuration:

curl "http://127.0.0.1:3333/mock"

List available users:

curl "http://127.0.0.1:3333/mock/users"

OpenID Connect Endpoints

Mockin implements the standard OpenID Connect endpoints:

  • GET /authorize - Authorization endpoint
  • POST /oauth/token - Token endpoint
  • POST /oauth/introspect - Token introspection endpoint
  • GET /oauth/userinfo - User info endpoint
  • POST /oauth/userinfo - User info endpoint (POST)
  • GET /.well-known/openid-configuration - OpenID Connect discovery
  • GET /jwks - JSON Web Key Set

AAuth

Mockin also acts as a mock Person Server (PS) for the AAuth protocol  — letting you exercise agent client code (bootstrap, token issuance, R3, governance) without standing up a real wallet. All consent and approval steps auto-approve by default, so the flows are non-interactive and scriptable.

The AAuth surface tracks an in-progress IETF draft. See draft-hardt-aauth-protocol , draft-hardt-aauth-bootstrap , and draft-hardt-aauth-r3  for protocol semantics.

Discovery

  • GET /.well-known/aauth-person.json — PS metadata (endpoints + jwks_uri)
  • GET /aauth/jwks.json — PS public keys (Ed25519, EdDSA)

Endpoints

EndpointMethodPurpose
/aauth/bootstrapPOSTBind agent to user. sig=hwk for the initial ceremony, sig=jwt for the completion announcement.
/aauth/pending/:idGET / POST / DELETEPoll a deferred response, submit clarification, or cancel.
/aauth/consentGETBrowser-facing consent endpoint. Mockin auto-approves and redirects to the supplied callback.
/aauth/tokenPOSTExchange a resource_token for an auth_token. Supports R3 (r3_uri + r3_s256).
/aauth/permissionPOSTAgent asks the PS for go/no-go on an action.
/aauth/auditPOSTFire-and-forget activity record.
/aauth/interactionPOSTForward an interaction (question, payment, interaction, completion) to the user via the PS.

All agent-facing endpoints require an HTTP Message Signature (RFC 9421) under the AAuth profile. /aauth/bootstrap (initial) accepts sig=hwk; the rest require sig=jwt with an aa-agent+jwt agent token in the Signature-Key header.

R3 (Rich Resource Requests)

When a resource_token carries r3_uri + r3_s256, mockin:

  1. Fetches the document from r3_uri,
  2. Verifies the SHA-256 of the raw bytes matches r3_s256 (rejects invalid_resource_token on mismatch),
  3. Auto-grants every operation in the document by default,
  4. Embeds r3_uri, r3_s256, and r3_granted on the issued auth_token.

Override the granted/conditional split via mock.r3_grants. Pre-register an R3 doc by URI in mock.trusted_servers[<rs_url>].r3_documents to avoid network in tests.

AAuth Mock API

Configure mock behaviour at runtime:

  • GET /mock/aauth — current AAuth mock config
  • PUT /mock/aauth — patch the config

Recognized fields (all optional):

FieldDefaultEffect
auto_approvetruePre-mark deferred entries as approved so the first poll resolves immediately
requirementnullForce a deferred path on /aauth/token: interaction, approval, or clarification
clarificationnullMarkdown question returned in clarification mode
errornullInject an error code
error_endpointnullRestrict error to a specific endpoint (token, bootstrap, permission)
token_lifetime3600exp - iat for issued auth_tokens
claimsnullOverride identity claim release on the auth_token
r3_grantsnullOverride { granted, conditional } for R3 flows
permissiongrantedFlip /aauth/permission to denied
permission_reasonnullReason returned with permission: 'denied'
trusted_servers{}Preload { metadata, jwks, r3_documents } per server URL to skip JWKS fetches in offline tests

DELETE /mock resets AAuth config along with the OIDC mock state.

Identity claim release

/aauth/token releases identity claims onto the auth_token from the active mock user (/mock/user/:user). Scopes follow Hellō conventions:

  • profile expands to name, email (+ email_verified), picture
  • email releases email, email_verified
  • phone releases phone_number, phone_number_verified
  • Per-claim scopes (nickname, given_name, picture, github, discord, gitlab, twitter, ethereum, tenant_sub, etc.) release the matching field from the mock user

Resource scopes pass through unchanged as the scope claim.

Worked examples

The canonical end-to-end examples are the AAuth specs in test/aauth/  — they stand up a fake agent server and resource server, mint signed tokens, and drive every endpoint via fastify.inject(). Use those as the reference for HTTPSig signing, agent_token / resource_token shape, and the R3 hash flow.

For a manual smoke test, switch /aauth/token to deferred mode and run any AAuth client against it:

curl -X PUT http://127.0.0.1:3333/mock/aauth \ -H 'content-type: application/json' \ -d '{"requirement":"interaction"}'

The next /aauth/token call will return 202 with an AAuth-Requirement header and a pending URL. With auto_approve=true (the default) the first poll will resolve and return the auth_token.

Limitations

  • Auto-approve only — no real consent UI; the user is always the active mock user.
  • No durable state across restarts; pending requests and bindings live in memory.
  • Token signature checks are real (RFC 9421 + JWT signature against the issuer’s JWKS) but no policy/scope-allowlist validation is enforced beyond the spec’s structural checks.

Invite

Mockin mirrors Hellō’s invite flow so you can test how your app handles the events_uri Security Event Token (SET) JWT and the initiate_login_uri redirect for newly invited users — without standing up a real wallet, mailer, or database.

Endpoints

EndpointMethodPurpose
/inviteGETEntry redirect from your app. Mockin creates the invitation from query params and redirects to return_uri; with auto_accept it also fires the SET inline.
/invitePOSTCreate an invitation (body: email, prompt, client_id, events_uri?, initiate_login_uri?, role?, tenant?, state?, inviter_sub?, inviter_email?).
/invite/:idPUTResend (refresh lastEmailedAt).
/invite/:idDELETERetract.
/user/inviteGETList outgoing invitations. Filter with ?inviter_sub=<sub>.
/invitation/:idGETInvitee view (sanitized).
/invitation/:idPUTAccept. Returns { initiate_login_url }; SET JWT is POSTed to events_uri.
/invitation/:idDELETEDecline.
/invitation/:id/reportPOSTAbuse report.

SET JWT

When an invitation is accepted, mockin POSTs an RS256-signed Security Event Token to the events_uri. Same shape as production:

{ "iss": "<mockin issuer>", "aud": "<client_id>", "jti": "...", "iat": 1234567890, "exp": 1234568190, "https://hello.coop/invite/created": { "inviter": "<inviter sub>", "invitee": { "sub": "<invitee sub>", "email": "..." }, "role": "...", "tenant": "...", "state": "..." } }

The token is signed with the OIDC RS256 key, so it verifies against the same /jwks your app already uses for id_token. Posted with Content-Type: application/jwt; mockin expects a 202 response from the receiver.

Mock API

EndpointMethodPurpose
/mock/inviteGETCurrent config + stored invitations
/mock/invitePUTPatch config
FieldDefaultEffect
errornullError code returned by the next request
error_endpointnullRestrict the error to one endpoint: create, accept, decline, retract, resend, invitation, or entry
auto_acceptfalseGET /invite accepts inline + fires SET (single-redirect demo)
expires_in604800Override the 7-day default expiry

DELETE /mock resets the invite state along with the rest of the mock config.

Curl examples

Create an invitation:

curl -X POST http://127.0.0.1:3333/invite \ -H 'content-type: application/json' \ -d '{ "email": "alice@example.com", "prompt": "Join the team", "client_id": "demo-client", "events_uri": "http://127.0.0.1:9999/events", "initiate_login_uri": "http://app.local/init", "role": "admin", "tenant": "acme", "state": "opaque-state" }'

Accept (fires the SET, returns initiate_login_url):

curl -X PUT http://127.0.0.1:3333/invitation/<id>

Single-redirect demo with auto_accept:

curl -X PUT http://127.0.0.1:3333/mock/invite \ -H 'content-type: application/json' \ -d '{"auto_accept": true}' curl -i "http://127.0.0.1:3333/invite?inviter=00000000-0000-0000-0000-00000000&client_id=demo-client&prompt=Join&invitee_email=bob@example.com&events_uri=http://127.0.0.1:9999/events&return_uri=http://app.local/back" # 302 to http://app.local/back; SET fired to events_uri inline.

Inject a scoped error:

curl -X PUT http://127.0.0.1:3333/mock/invite \ -H 'content-type: application/json' \ -d '{"error":"invitation_expired","error_endpoint":"invitation"}'

Inspect mock state:

curl http://127.0.0.1:3333/mock/invite

Reset:

curl -X DELETE http://127.0.0.1:3333/mock

Configuring Your App

You will need to have your app configured to use the URL where Mockin is running. For example, your app will need to redirect the user to http://127.0.0.1:3333/authorize if you are using the Mockin defaults instead of https://wallet.hello.coop/authorize.

Environment Variables

Set these environment variables to change where Mockin listens and what iss is set to in the ID Token:

IP

  • IP that Mockin will listen on. Defaults to 127.0.0.1

PORT

  • port that Mockin will listen on. Defaults to 3333

ISSUER

  • Issuer that Mockin will use as the iss value. Defaults to http:${IP}:${PORT} which is http://127.0.0.1:3333 if other defaults are used. Note that the ISSUER value does not need to be the same as IP:PORT, and can be any value except the production ISSUER value (setting it to https://issuer.hello.coop will generate an error). It needs to be an address that will resolve to Mockin if your app is fetching up ISSUER/.well-known/openid-configuration

If you are using one of the SDKs from https://github.com/hellocoop/packages  you set HELLO_WALLET to be where the browser and server can reach Mockin which is http://127.0.0.1:3333 by default.

  • client_id You can use any string as the client_id parameter. The same vale must be used in the authorization request when calling the token endpoint. The aud value in the ID Token will be the same value

  • redirect_uri There is no check on a valid redirect_uri.

Testing with Playwright

Playwright  is a popular end-to-end testing tool for modern web apps that uses Nodejs, and works well with Mockin. Playwright allows you to start one or more local servers when running Playwright tests, which makes it easy to start your app and Mockin at the same time.

In your playwright.config.ts

playwright.config.ts
export default defineConfig({ /* ... other settings */ webServer: [{ // adjust to be command to start your web app command: 'npm start', // adjust to be URL Playwright can call to confirm your app is running url: 'http://127.0.0.1:3000', },{ // Mockin default config command: 'npx @hellocoop/mockin', url: 'http://127.0.0.1:3333/', }] });

See https://github.com/hellocoop/packages/tree/main/express  for an example

Mock API

All Mock APIs return 200 (302 for authorization requests) along with the current value of MOCK if successful, a 404 response with an explanation if an invalid request.

GET /authorize

See Auth Request for the query parameters of an authorization request.

Mockin supports the login_hint and domain_hint query parameters:

  • login_hint can be either the email or the sub of a mock user. Mockin returns the user whose email or sub matches the provided login_hint.
  • domain_hint should be the domain of an email (e.g., example.net). Mockin returns users whose email belongs to the specified domain.

See the list of builtin mock users here .

If both login_hint and domain_hint are passsed, login_hint takes precedence.

PUT /mock/token

Changes the ID Token to be invalid in some way. Pass one or more of the following query parameters:

  • iss=<value> sets the value of the iss claim to be the passed value
  • aud=<value> sets the value of the aud claim to be the passed value
  • expired=true returns an ID token that has expired
  • wrong_key=true signs the ID token with the wrong key

Example: PUT /mock/token?expired=true

PUT /mock/authorize

Changes the response from /authorize, the authorization_endpoint:

  • error=<error_code> sets the OAuth error code to be returned.
  • wildcard_domain=<domain> sets the wild_card_domain parameter, part of the Hellō Auto Config process 
  • state=<value> sets the state parameter to be returned. Use to test getting back the wrong state parameter.

Example: PUT /mock/authorize?error=access_denied will direct Mockin to simulate the user canceling requests in subsequent flows.

PUT /mock/oauth/token

Changes the response from /oauth/token, the token_endpoint, to return an error:

  • error=<error_code> sets the OAuth error code to be returned. The status code will be changed as well unless set separately.
  • status=<code> sets the 3 digit HTTP status code returned.

PUT /mock/oauth/introspect

Changes the response from /oauth/introspection, the introspection_endpoint, to return an error:

  • error=<error_code> sets the OAuth error code to be returned. The status code will be changed as well unless set separately.
  • status=<code> sets the 3 digit HTTP status code returned.

PUT /mock/oauth/userinfo

Changes the response from /oauth/userinfo, the userinfo_endpoint, to return an error:

  • error=<error_code> sets the OAuth error code to be returned. The status code will be changed as well unless set separately.
  • status=<code> sets the 3 digit HTTP status code returned.

PUT /mock/user/:user

Change which user is returned.:user can be 0, 1 or 2 per user array in src/users.js. 0 is default.

PUT /mock/claims

Change a claim to be returned in the ID Token.

Example: PUT /mock/claims?email=mock@mock.example will then have "email"="mock@mock.example" in subsequent ID Token responses. Useful to test a user changing a value such as their email address.

DELETE /mock

Deletes all mock values that have been set. Call before / after tests to clear any mock values that have been set

Mock Limitations

The Mock data is stored in memory and stateful. All requests will get the same result given the state. Keep this in mind if running multiple tests at the same time.

When using response_mode=code the state of the result is stored in memory for providing the subsequent call to the token endpoint. This prevents Mockin from using this flow in a serverless deployment.

Contributions

We welcome contributions, feedback, and filing issues.
See the Mockin repo https://github.com/hellocoop/mockin