Skip to content

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:

EndpointMethodPurpose
/idp/oauth/authorizeGETShow the consent screen and issue an authorization code
/idp/oauth/tokenPOSTExchange an authorization code for an access token
/idp/oauth/userinfoGETFetch the mock identity’s profile with an access token
/idp/jwksGETPublic RSA keys for verifying ID token signatures
/idp/.well-known/openid-configurationGETOIDC discovery document

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
ParameterRequiredDescription
client_idYesClient ID from the dashboard
redirect_uriYesMust exactly match one of the client’s registered redirect URIs
response_typeConventionally code; the flow is always authorization code
scopeNoSpace-separated scopes. Supported: openid, email, profile. Defaults to all three if omitted
stateRecommendedOpaque value echoed back on redirect; use it for CSRF protection
code_challengeRecommendedPKCE challenge (S256), for public clients
code_challenge_methodWith code_challengeMust be S256

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_uri with the code:
HTTP/1.1 302 Found
Location: http://localhost:3000/callback?code=ac_EXAMPLECODE&state=xyz123
  • Deny — the redirect carries an error instead, with your state preserved:
Location: http://localhost:3000/callback?error=access_denied&error_description=The%20user%20denied%20the%20authorization%20request.&state=xyz123

Authorization 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

Exchange the code server-to-server with a form-encoded POST:

POST https://minutemail.co/idp/oauth/token
Content-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
ParameterRequiredDescription
grant_typeYesMust be authorization_code
codeYesThe authorization code from the redirect
client_idYesThe client’s ID
client_secretYesThe client’s secret — verified on every exchange, including PKCE clients
redirect_uriYesThe same redirect_uri used in the authorize request
code_verifierIf PKCE was usedThe 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..."
}
FieldDescription
access_tokenOpaque Bearer token, valid for 1 hour
token_typeAlways Bearer
expires_inLifetime in seconds (3600)
scopeThe scopes granted
id_tokenPresent 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:

ClaimValue
issThe issuer URL
subThe mock identity’s ID
audYour client_id
emailThe linked mailbox address
email_verifiedAlways true
name, preferred_username, pictureThe identity’s profile fields

Errors use the standard OAuth error format with Cache-Control: no-store:

{
"error": "invalid_grant",
"error_description": "Authorization code has expired."
}
HTTPerrorCause
400invalid_requestMissing code or client_id, or malformed form data
400unsupported_grant_typegrant_type is not authorization_code
400invalid_grantCode not found, expired, already used, or redirect_uri mismatch
401invalid_clientBad client_id/client_secret, or PKCE verification failed
500server_errorUnexpected server error

Call userinfo with the access token as a Bearer token:

GET https://minutemail.co/idp/oauth/userinfo
Authorization: Bearer at_EXAMPLEACCESSTOKEN

Response:

{
"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.

ScopeGrants
openidIssues an ID token at the token endpoint
emailThe email and email_verified claims
profileThe name, preferred_username, and picture claims

All three are granted by default when no scope is passed. There are no additional scopes to configure.

  • 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