Edit Page

OAuth 2.0 / 2.1

RESTHeart Cloud
{% include interactive-docs-config.html %}

Overview

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 [RESTHEART-URL] with your RESTHeart server URL (e.g., http://localhost:8080).

Available endpoints:

Endpoint Enabled by default Description

POST /token

yes

Issues a JWT access token. Supports password, client_credentials, and authorization_code grants.

POST /token/cookie

yes

Issues a JWT and sets it as an HttpOnly cookie (browser apps).

GET /token

yes

Returns the current token for the authenticated user.

DELETE /token

yes

Invalidates the current token.

GET /authorize

no

Starts the Authorization Code + PKCE flow โ€” redirects to the configured login page.

POST /authorize

no

Completes the Authorization Code flow โ€” issues the authorization code after successful login.

GET /.well-known/oauth-authorization-server

no

Authorization Server Metadata (RFC 8414) โ€” for automatic client discovery.

GET /.well-known/oauth-protected-resource

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.

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:

  1. base-url configuration value

  2. Request Host header

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.