OAuth Flow
The mock IDP implements the OAuth 2.0 authorization code flow, with optional PKCE (S256) and OpenID Connect ID tokens. Only the authorization_code grant is supported — there are no implicit, password, or client-credentials grants, and no refresh tokens.
All endpoints live under https://minutemail.co/idp:
| Endpoint | Method | Purpose |
|---|---|---|
/idp/oauth/authorize | GET | Show the consent screen and issue an authorization code |
/idp/oauth/token | POST | Exchange an authorization code for an access token |
/idp/oauth/userinfo | GET | Fetch the mock identity’s profile with an access token |
/idp/jwks | GET | Public RSA keys for verifying ID token signatures |
/idp/.well-known/openid-configuration | GET | OIDC discovery document |
1. Authorization request
Section titled “1. Authorization request”Redirect the user’s browser to the authorize endpoint:
GET https://minutemail.co/idp/oauth/authorize?client_id=mc_EXAMPLECLIENTID&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcallback&response_type=code&scope=openid%20email%20profile&state=xyz123| Parameter | Required | Description |
|---|---|---|
client_id | Yes | Client ID from the dashboard |
redirect_uri | Yes | Must exactly match one of the client’s registered redirect URIs |
response_type | — | Conventionally code; the flow is always authorization code |
scope | No | Space-separated scopes. Supported: openid, email, profile. Defaults to all three if omitted |
state | Recommended | Opaque value echoed back on redirect; use it for CSRF protection |
code_challenge | Recommended | PKCE challenge (S256), for public clients |
code_challenge_method | With code_challenge | Must be S256 |
Consent
Section titled “Consent”MinuteMail renders a consent screen showing the client’s provider logo, name, the requested scopes, and the mock identity that will be used — the client’s first active identity (oldest created).
- Authorize — the browser is redirected (302) to your
redirect_uriwith the code:
HTTP/1.1 302 FoundLocation: http://localhost:3000/callback?code=ac_EXAMPLECODE&state=xyz123- Deny — the redirect carries an error instead, with your
statepreserved:
Location: http://localhost:3000/callback?error=access_denied&error_description=The%20user%20denied%20the%20authorization%20request.&state=xyz123Authorization details:
- Codes are valid for 10 minutes and are single-use — a second exchange of the same code fails with
invalid_grant - If the client has no active identities, the consent screen shows an error instead of a code
2. Token exchange
Section titled “2. Token exchange”Exchange the code server-to-server with a form-encoded POST:
POST https://minutemail.co/idp/oauth/tokenContent-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=ac_EXAMPLECODE&client_id=mc_EXAMPLECLIENTID&client_secret=cs_EXAMPLESECRET&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcallback&code_verifier=EXAMPLEVERIFIER| Parameter | Required | Description |
|---|---|---|
grant_type | Yes | Must be authorization_code |
code | Yes | The authorization code from the redirect |
client_id | Yes | The client’s ID |
client_secret | Yes | The client’s secret — verified on every exchange, including PKCE clients |
redirect_uri | Yes | The same redirect_uri used in the authorize request |
code_verifier | If PKCE was used | The plaintext verifier matching the code_challenge |
Successful response:
{ "access_token": "at_EXAMPLEACCESSTOKEN", "token_type": "Bearer", "expires_in": 3600, "scope": "openid email profile", "id_token": "eyJhbGciOiJSUzI1NiIs..."}| Field | Description |
|---|---|
access_token | Opaque Bearer token, valid for 1 hour |
token_type | Always Bearer |
expires_in | Lifetime in seconds (3600) |
scope | The scopes granted |
id_token | Present only when the scope includes openid |
The ID token is a signed JWT (RS256) carrying the identity’s claims — verify its signature against the keys at https://minutemail.co/idp/jwks:
| Claim | Value |
|---|---|
iss | The issuer URL |
sub | The mock identity’s ID |
aud | Your client_id |
email | The linked mailbox address |
email_verified | Always true |
name, preferred_username, picture | The identity’s profile fields |
Token errors
Section titled “Token errors”Errors use the standard OAuth error format with Cache-Control: no-store:
{ "error": "invalid_grant", "error_description": "Authorization code has expired."}| HTTP | error | Cause |
|---|---|---|
| 400 | invalid_request | Missing code or client_id, or malformed form data |
| 400 | unsupported_grant_type | grant_type is not authorization_code |
| 400 | invalid_grant | Code not found, expired, already used, or redirect_uri mismatch |
| 401 | invalid_client | Bad client_id/client_secret, or PKCE verification failed |
| 500 | server_error | Unexpected server error |
3. Userinfo
Section titled “3. Userinfo”Call userinfo with the access token as a Bearer token:
GET https://minutemail.co/idp/oauth/userinfoAuthorization: Bearer at_EXAMPLEACCESSTOKENResponse:
{ "sub": "ident_01JEXAMPLE", "email": "tricia.minutemail.cc@exampledomain.com", "email_verified": true, "name": "tricia", "preferred_username": "tricia", "picture": "https://example.com/avatar.png"}sub matches the ID token’s sub; email is the linked MinuteMail mailbox address. An invalid, expired, or missing token returns 401 with error: invalid_token.
Scopes
Section titled “Scopes”| Scope | Grants |
|---|---|
openid | Issues an ID token at the token endpoint |
email | The email and email_verified claims |
profile | The name, preferred_username, and picture claims |
All three are granted by default when no scope is passed. There are no additional scopes to configure.
Notes for testing
Section titled “Notes for testing”- Access tokens are not refreshable — when one expires after an hour, restart the authorize flow to get a new one
- PKCE is strongly recommended even though a client secret is always required, so your integration exercises the same code path as a real public-client flow
- To test with a different user, make another identity the client’s first-created active identity (delete and recreate), since the consent screen always pre-selects the oldest active identity