OAuth 2.0 / 2.1
RESTHeart CloudOverview
RESTHeart provides a standards-compliant OAuth 2.0/2.1 token endpoint and authorization server. It supports three grant types and the full Authorization Code + PKCE flow for delegated user authentication.
|
Note
|
In all examples below, replace |
Available endpoints:
| Endpoint | Enabled by default | Description |
|---|---|---|
|
yes |
Issues a JWT access token. Supports |
|
yes |
Issues a JWT and sets it as an HttpOnly cookie (browser apps). |
|
yes |
Returns the current token for the authenticated user. |
|
yes |
Invalidates the current token. |
|
no |
Starts the Authorization Code + PKCE flow โ redirects to the configured login page. |
|
no |
Completes the Authorization Code flow โ issues the authorization code after successful login. |
|
no |
Authorization Server Metadata (RFC 8414) โ for automatic client discovery. |
|
no |
Protected Resource Metadata (RFC 9728) โ advertises the authorization server to MCP clients. |
Token Endpoint
authTokenService is enabled by default and bound to POST /token.
Password Grant
Authenticate with username and password using HTTP Basic Auth:
cURL
curl -i -X POST [RESTHEART-URL]/token \
-u [BASIC-AUTH]
HTTPie
http POST [RESTHEART-URL]/token \
Authorization:"Basic [BASIC-AUTH]"
Or using OAuth 2.0 form data (grant_type=password, RFC 6749 ยง4.3):
HTTPie
http -f POST [RESTHEART-URL]/token \
grant_type=password \
username=admin \
password=secret
Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 900,
"username": "admin",
"roles": ["admin"]
}
Client Credentials Grant
|
Note
|
Available from RESTHeart v9.2.0. |
For machine-to-machine authentication, use grant_type=client_credentials (RFC 6749 ยง4.4).
The client_id and client_secret map to user credentials in the configured authenticator.
HTTPie
http -f POST [RESTHEART-URL]/token \
grant_type=client_credentials \
client_id=myapp \
client_secret=s3cret
Using the Token
Once you have the token, use it as a Bearer token:
cURL
curl -i -X GET [RESTHEART-URL]/mycollection \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
HTTPie
http GET [RESTHEART-URL]/mycollection \
"Authorization:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Token Renewal
To renew a token before it expires, add ?renew to any GET /token or POST /token request:
HTTPie
http GET [RESTHEART-URL]/token?renew \
Authorization:"Bearer <current-token>"
|
Note
|
Token generation is a cryptographic operation with some overhead. The client is responsible for renewing before expiry. |
Cookie-Based Authentication
For browser applications, use /token/cookie to set an HttpOnly cookie (the token is never exposed to JavaScript):
HTTPie
http --session=./session.json POST [RESTHEART-URL]/token/cookie \
Authorization:"Basic [BASIC-AUTH]"
Subsequent requests in the same session automatically include the cookie.
Authorization Code + PKCE Flow
|
Note
|
Available from RESTHeart v9.2.1. |
The OAuth 2.1 Authorization Code flow with PKCE (RFC 7636) allows a user to authenticate via a frontend login page. The access token is issued only after successful authentication โ no credentials are passed to the client.
This flow is the recommended approach for:
-
Web applications with a frontend login UI
-
MCP clients (e.g., Claude Desktop via
mcp-remote) -
CLI tools that open a browser for login
Flow overview:
Client RESTHeart Frontend login UI
| | |
|-- GET /authorize ------>| |
| (code_challenge, S256)| |
|<-- 302 to login-url ----| |
| |<-- POST /authorize -----|
| | username+password |
| |--(valid)- 302 callback?code=... ->|
| |--(invalid)- 302 login?error=... ->|
|<----------------------------------------------- code ---|
|-- POST /token ---------->|
| (code, code_verifier) |
|<-- access_token ---------|
Step 1: Start the authorization request
Generate a PKCE pair and redirect the user to GET /authorize:
# Generate PKCE pair
CODE_VERIFIER="dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
CODE_CHALLENGE=$(echo -n "$CODE_VERIFIER" | openssl dgst -sha256 -binary | \
openssl base64 | tr '+/' '-_' | tr -d '=')
http --follow=false GET "[RESTHEART-URL]/authorize" \
"response_type==code" \
"client_id==my-app" \
"redirect_uri==https://myapp.example.com/callback" \
"code_challenge==${CODE_CHALLENGE}" \
"code_challenge_method==S256" \
"state==random-state-value"
RESTHeart responds with 302 to the configured login-url, forwarding all OAuth parameters as query string.
Step 2: User authenticates
The frontend presents the login form and POSTs the credentials to POST /authorize.
The OAuth parameters are forwarded as query string (exactly as received from the GET /authorize redirect).
Two credential formats are supported:
Option A โ form body (recommended for browser login pages):
RESTHeart reads username and password from the application/x-www-form-urlencoded body.
No Authorization header is needed โ the browser’s native form POST works as-is.
http --follow=false POST \
"[RESTHEART-URL]/authorize?response_type=code&client_id=my-app&\
redirect_uri=https://myapp.example.com/callback&\
code_challenge=${CODE_CHALLENGE}&code_challenge_method=S256&state=random-state-value" \
username=admin \
password=secret
On invalid credentials RESTHeart redirects back to login-url?error=invalid_credentials&<original_query_params>
so the login page can show an error without losing the OAuth context.
Option B โ HTTP Basic Auth (for API clients / programmatic use):
http --follow=false -a admin:secret POST \
"[RESTHEART-URL]/authorize?response_type=code&client_id=my-app&\
redirect_uri=https://myapp.example.com/callback&\
code_challenge=${CODE_CHALLENGE}&code_challenge_method=S256&state=random-state-value"
|
Note
|
If an Authorization: Basic header is present it always takes precedence over form body credentials.
|
Response (valid credentials): 302 to redirect_uri?code=<authorization_code>&state=<state>.
The authorization code is a short-lived JWT (5-minute TTL) signed with the shared jwtConfigProvider key.
It is stateless โ valid across all nodes in a cluster without shared storage.
Step 3: Exchange the code for an access token
http -f POST [RESTHEART-URL]/token \
grant_type=authorization_code \
code="<authorization_code>" \
redirect_uri="https://myapp.example.com/callback" \
client_id=my-app \
code_verifier="${CODE_VERIFIER}"
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 899
}
Configuration
All three OAuth endpoints are disabled by default and must be explicitly enabled. Enable them together for the full Authorization Code + PKCE flow:
oauthAuthorizationServerMetadataService:
enabled: true
# Optional: override scheme+host for metadata URLs (needed behind a TLS-terminating proxy)
# Falls back to the request Host header when null.
base-url: null # e.g. https://api.example.com
authorize-endpoint-uri: /authorize
oauthAuthorizationService:
enabled: true
# URL of the frontend login page (required)
login-url: https://myapp.example.com/login
# Allowed redirect_uri values โ supports * wildcard
allowed-redirect-uris:
- https://myapp.example.com/callback
- http://localhost:* # for local development
oauthProtectedResourceMetadataService:
enabled: true
# Optional: same as base-url in oauthAuthorizationServerMetadataService
base-url: null # e.g. https://api.example.com
|
Important
|
The jwtTokenManager must be enabled (it is by default in RESTHeart v9). The authorization code is signed with the same key as regular JWT tokens, ensuring stateless multi-node operation.
|
Authorization Server Metadata (RFC 8414)
|
Note
|
Available from RESTHeart v9.2.0. Disabled by default from v9.2.1. |
oauthAuthorizationServerMetadataService exposes GET /.well-known/oauth-authorization-server per RFC 8414.
OAuth 2.0 clients (API gateways, MCP clients, CLI tools) can use this endpoint to auto-configure without hardcoded URLs.
http GET [RESTHEART-URL]/.well-known/oauth-authorization-server
{
"issuer": "https://api.example.com",
"authorization_endpoint": "https://api.example.com/authorize",
"token_endpoint": "https://api.example.com/token",
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "password", "client_credentials"],
"code_challenge_methods_supported": ["S256"],
"token_endpoint_auth_methods_supported": ["none", "client_secret_basic", "client_secret_post"]
}
The endpoint is publicly accessible (no authentication required).
When base-url is set, all URLs in the response use it as the base.
When null, the base URL is derived in this order:
-
base-urlconfiguration value -
Request
Hostheader
oauthAuthorizationServerMetadataService:
enabled: true
base-url: https://api.example.com # optional
authorize-endpoint-uri: /authorize # must match oauthAuthorizationService URI
Protected Resource Metadata (RFC 9728)
|
Note
|
Available from RESTHeart v9.2.0. Disabled by default from v9.2.1. |
oauthProtectedResourceMetadataService exposes GET /.well-known/oauth-protected-resource per RFC 9728.
MCP clients (e.g., Claude Desktop via mcp-remote) use this endpoint to discover the authorization server URL before starting the OAuth flow.
http GET [RESTHEART-URL]/.well-known/oauth-protected-resource
{
"resource": "https://api.example.com",
"authorization_servers": ["https://api.example.com"]
}
A resource-specific path can be appended to scope the metadata:
http GET [RESTHEART-URL]/.well-known/oauth-protected-resource/mcp/ade
{
"resource": "https://api.example.com/mcp/ade",
"authorization_servers": ["https://api.example.com"]
}
The base-url option solves the common reverse-proxy problem where the Host header arrives as http:// while the public URL is https://, causing issuer validation failures per RFC 8414 ยง3.3:
oauthProtectedResourceMetadataService:
enabled: true
base-url: https://api.example.com # optional, resolves TLS proxy mismatch
Token Manager Configuration
The JWT Token Manager is enabled by default in RESTHeart v9:
jwtTokenManager:
key: secret # Change this in production!
enabled: true
ttl: 15 # Token time-to-live in minutes
issuer: restheart.org
|
Important
|
Always set a strong, random key value in production environments.
|