sondahub / OAuth 2.0 test server
OAuth 2.0 and OpenID Connect test server
A small, real authorization server for trying a client end to end: the authorization-code flow with PKCE and a consent screen, client credentials, password and refresh grants, dynamic client registration, resource indicators, RS256 tokens you can verify against the JWKS, discovery, userinfo and introspection — and a whole company directory to sign in as. Free, public credentials.
Settings to paste
- Discovery
- https://api.sondahub.com/.well-known/openid-configuration
- Authorization
- https://api.sondahub.com/v1/utils/oauth/authorize
- Token
- https://api.sondahub.com/v1/utils/oauth/token
- Userinfo
- https://api.sondahub.com/v1/utils/oauth/userinfo
- Introspection
- https://api.sondahub.com/v1/utils/oauth/introspect
- Revocation
- https://api.sondahub.com/v1/utils/oauth/revoke
- JWKS
- https://api.sondahub.com/.well-known/jwks.json
- Registration
- https://api.sondahub.com/v1/utils/oauth/register
- Client
- sonda / probe-secret, or register one
- User
- sonda / probe, or any directory user / probe
- Scopes
- any; openid adds an id_token; read when none is asked
Public on purpose — this is a playground, and the credentials protect nothing.
curl https://api.sondahub.com/.well-known/openid-configuration
Authorization code with PKCE
1. Send the browser to the authorization URL with a code_challenge. The consent screen asks Allow or Deny (add &auto=1 to skip it and allow). The answer is a redirect to your redirect_uri with code and your state.
https://api.sondahub.com/v1/utils/oauth/authorize?response_type=code&client_id=sonda&redirect_uri=http://127.0.0.1:8080/callback&scope=openid%20read&state=xyz&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256
2. Exchange the code with the verifier the challenge was made from:
curl https://api.sondahub.com/v1/utils/oauth/token \ -u sonda:probe-secret \ -d grant_type=authorization_code \ -d code=THE_CODE \ -d redirect_uri=http://127.0.0.1:8080/callback \ -d code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
The answer: access_token, refresh_token, expires_in, scope, and an id_token when the scope has openid. A wrong verifier answers invalid_grant saying it does not match the challenge.
In Sonda: OAuth 2 in the auth editor, authorization-code grant, the two URLs and the client above, PKCE on — Sonda opens the consent screen and catches the code on its own 127.0.0.1 redirect.
Client credentials and password
curl -u sonda:probe-secret -d grant_type=client_credentials -d scope=read https://api.sondahub.com/v1/utils/oauth/token
curl -d grant_type=password -d username=sonda -d password=probe \ -d client_id=sonda -d client_secret=probe-secret -d "scope=openid read" \ https://api.sondahub.com/v1/utils/oauth/token
curl -u sonda:probe-secret -d grant_type=refresh_token -d refresh_token=THE_REFRESH_TOKEN https://api.sondahub.com/v1/utils/oauth/token
Register a client, ask for an audience
Dynamic Client Registration hands out working clients without storing them — the client_id is the signed registration. A resource parameter (RFC 8707) on the authorization and token requests becomes the token's aud, which is how the OAuth-protected MCP server knows a token is meant for it.
curl -X POST https://api.sondahub.com/v1/utils/oauth/register \
-H "Content-Type: application/json" \
-d '{"client_name": "My CLI", "redirect_uris": ["http://127.0.0.1:8080/callback"], "token_endpoint_auth_method": "none"}'
curl -d grant_type=password -d username=clara.brown -d password=probe \ -d client_id=sonda -d client_secret=probe-secret \ -d "scope=openid groups mcp:read" -d resource=https://api.sondahub.com/mcp/secure \ https://api.sondahub.com/v1/utils/oauth/token
Use the token
Authorization: Bearer <access_token>. ?scope=write demands that scope and answers 403 insufficient_scope without it.token=… → active and the claims.curl -H "Authorization: Bearer ACCESS_TOKEN" https://api.sondahub.com/v1/utils/oauth/protected
Questions
Which grant types does it support?
authorization_code (with or without PKCE, S256 or plain), client_credentials, password and refresh_token. The client may authenticate with HTTP Basic or with client_id and client_secret in the form.
Which redirect URIs are allowed?
Any absolute URL — a loopback address like http://127.0.0.1:8080/callback for a desktop or CLI app, or your own development URL. The code remembers the redirect URI it was issued for: a token request that names a different one is refused with invalid_grant.
How do I verify the tokens?
Access and ID tokens are RS256 JWTs: fetch the key from https://api.sondahub.com/.well-known/jwks.json (kid sondahub-2026), check iss = https://api.sondahub.com and aud = sondahub. Or POST the token to the introspection endpoint.
Can I register my own client?
Yes, with Dynamic Client Registration (RFC 7591): POST your client metadata to /v1/utils/oauth/register and get a client_id — and a secret, unless you register a public client with token_endpoint_auth_method: none. Nothing is stored: the client_id carries its own signed registration, so it works forever and anywhere the hub runs. Redirect URIs you registered are then enforced.
Can I sign in as someone other than the playground user?
Anyone in the Identity directory: their user_name (clara.brown, say) with the password probe, on the consent screen or in the password grant. Their id token and userinfo carry name, email, department, title and groups; sub is user_{id}, and the SCIM /Me endpoint answers for them.
How long do tokens live?
Authorization codes ten minutes, access tokens an hour, refresh tokens thirty days. Revocation answers 200 but cannot shorten a token’s life: the server keeps no state, so a token lives until it expires.