API Reference

Complete endpoint documentation for the blah.dev OAuth provider.

Base URL: https://blah.dev

Discovery

GET/.well-known/openid-configuration

Returns OAuth 2.0 / OpenID Connect server metadata per RFC 8414.

json
{
  "issuer": "https://blah.dev",
  "authorization_endpoint": "https://blah.dev/oauth/authorize",
  "token_endpoint": "https://blah.dev/oauth/token",
  "userinfo_endpoint": "https://blah.dev/oauth/userinfo",
  "revocation_endpoint": "https://blah.dev/oauth/revoke",
  "introspection_endpoint": "https://blah.dev/oauth/introspect",
  "scopes_supported": ["openid", "email", "profile"],
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "token_endpoint_auth_methods_supported": [
    "client_secret_post", "client_secret_basic"
  ],
  "code_challenge_methods_supported": ["plain", "S256"]
}

Authorization

GET/oauth/authorize

Initiates the OAuth authorization flow. Validates parameters and redirects to the consent screen.

ParameterTypeRequiredDescription
response_typestringRequiredMust be "code"
client_idstringRequiredYour OAuth client ID
redirect_uristringRequiredMust exactly match a registered URI
scopestringOptionalSpace-separated scopes. Default: "openid"
statestringOptionalOpaque value for CSRF protection (recommended)
code_challengestringOptionalPKCE challenge (required for public clients)
code_challenge_methodstringOptional"S256" (recommended) or "plain"

Supported Scopes

ScopeClaims Granted
openidsub
emailemail
profileReserved for future use

Success redirects to:

https://myapp.com/callback?code=AUTH_CODE&state=xyz789

Tokens

POST/oauth/token

Exchanges an authorization code for tokens, or refreshes an existing token.

Authorization Code Grant

ParameterTypeRequiredDescription
grant_typestringRequired"authorization_code"
codestringRequiredThe authorization code
redirect_uristringRequiredMust match the original request
client_idstringRequiredYour client ID
client_secretstringOptionalRequired for confidential clients
code_verifierstringOptionalRequired if PKCE was used
Exchange code for tokens
curl -X POST https://blah.dev/oauth/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=authorization_code" \ -d "code=AUTH_CODE" \ -d "redirect_uri=https://myapp.com/callback" \ -d "client_id=YOUR_CLIENT_ID" \ -d "client_secret=YOUR_CLIENT_SECRET" \ -d "code_verifier=YOUR_VERIFIER"

Response:

json
{
  "access_token": "at_abc123...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "rt_def456...",
  "scope": "openid email"
}

Refresh Token Grant

ParameterTypeRequiredDescription
grant_typestringRequired"refresh_token"
refresh_tokenstringRequiredThe refresh token
client_idstringRequiredYour client ID
client_secretstringOptionalRequired for confidential clients
Refresh tokens
curl -X POST https://blah.dev/oauth/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=refresh_token" \ -d "refresh_token=REFRESH_TOKEN" \ -d "client_id=YOUR_CLIENT_ID" \ -d "client_secret=YOUR_CLIENT_SECRET"

Note: Refresh tokens are rotated on each use. The old token is revoked when a new one is issued.

User Info

GET/oauth/userinfo

Returns claims about the authenticated user. Requires a valid access token.

Get user info
curl https://blah.dev/oauth/userinfo \ -H "Authorization: Bearer ACCESS_TOKEN"

Response:

json
{
  "sub": "user_abc123",
  "email": "user@example.com"
}

Available Claims

ClaimScopeDescription
subopenidUnique user identifier
emailemail or openidUser's email address

Token Introspection

POST/oauth/introspect

Validates a token and returns metadata. Per RFC 7662. Used by resource servers.

ParameterTypeRequiredDescription
tokenstringRequiredThe token to introspect
token_type_hintstringOptional"access_token" or "refresh_token"
client_idstringRequiredYour client ID
client_secretstringOptionalYour client secret
Introspect token
curl -X POST https://blah.dev/oauth/introspect \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "token=ACCESS_TOKEN" \ -d "client_id=YOUR_CLIENT_ID" \ -d "client_secret=YOUR_CLIENT_SECRET"

Active token response:

json
{
  "active": true,
  "scope": "openid email",
  "client_id": "abc123",
  "username": "user@example.com",
  "token_type": "Bearer",
  "exp": 1704067200,
  "iat": 1704063600,
  "sub": "user_abc123"
}

Inactive token response:

json
{ "active": false }

Token Revocation

POST/oauth/revoke

Revokes an access or refresh token. Per RFC 7009. Always returns 200 OK regardless of whether the token was valid (prevents enumeration).

ParameterTypeRequiredDescription
tokenstringRequiredThe token to revoke
token_type_hintstringOptional"access_token" or "refresh_token"
client_idstringRequiredYour client ID
client_secretstringOptionalYour client secret
Revoke token
curl -X POST https://blah.dev/oauth/revoke \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "token=REFRESH_TOKEN" \ -d "token_type_hint=refresh_token" \ -d "client_id=YOUR_CLIENT_ID" \ -d "client_secret=YOUR_CLIENT_SECRET"

Revoking a refresh token also revokes all associated access tokens. Revoking an access token only revokes that specific token.

Client Management

All client management endpoints require authentication via session cookie.

GET/api/oauth/clients

Returns all OAuth clients owned by the authenticated user.

json
[
  {
    "client_id": "client_abc123",
    "name": "My App",
    "redirect_uris": ["https://myapp.com/callback"],
    "created_at": 1704067200000
  }
]
POST/api/oauth/clients

Creates a new OAuth client. Returns the client secret (shown only once).

Request body
{
  "name": "My Application",
  "redirect_uris": ["https://myapp.com/callback"]
}

Validation

FieldRules
name1–100 characters
redirect_uris1–10 valid URLs, HTTPS required (except localhost)
GET/api/oauth/clients/{clientId}

Returns details for a specific client. Client secret hash is never returned.

DELETE/api/oauth/clients/{clientId}

Deletes an OAuth client. Only the owner can delete. All tokens for this client become invalid. Cannot be undone.

Error Codes

All errors follow the OAuth 2.0 error format:

json
{
  "error": "error_code",
  "error_description": "Human-readable message"
}
CodeStatusDescription
invalid_request400Missing or malformed parameter
invalid_client401Client authentication failed
invalid_grant400Grant (code/token) invalid or expired
unauthorized_client400Client not authorized for grant type
unsupported_grant_type400Grant type not supported
invalid_scope400Scope invalid or exceeds granted
access_denied403User denied authorization
invalid_token401Token invalid or expired
server_error500Internal server error