Edit Page

OAuth 2.0 / 2.1

RESTHeart Cloud

🔧 Configuration

Sets localhost:8080 with admin:secret
Values are saved in your browser

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
  registration-endpoint-uri: /register # optional, enables registration_endpoint in metadata

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

Dynamic Client Registration (RFC 7591)

Note
Available from RESTHeart v9.3.0. Disabled by default.

oauthClientRegistrationService exposes POST /register per RFC 7591, allowing OAuth clients to self-register without manual admin configuration. This is required by tools such as mcp-inspector and MCP SDK clients that perform dynamic registration before starting the authorization flow.

http POST [RESTHEART-URL]/register \
  redirect_uris:='["https://client.example.com/callback"]' \
  client_name="My MCP Client" \
  token_endpoint_auth_method=none
{
  "client_id": "550e8400-e29b-41d4-a716-446655440000",
  "client_id_issued_at": 1712345678,
  "redirect_uris": ["https://client.example.com/callback"],
  "client_name": "My MCP Client",
  "token_endpoint_auth_method": "none",
  "grant_types": ["authorization_code"],
  "response_types": ["code"]
}

The endpoint is publicly accessible (no authentication required). The returned client_id is a UUID that clients must use in subsequent authorization requests.

Note
RESTHeart’s authorization service embeds client_id directly in the authorization code JWT without database validation. No client storage is required on the server side.

To enable dynamic client registration and advertise it in the AS metadata:

oauthClientRegistrationService:
  enabled: true

oauthAuthorizationServerMetadataService:
  enabled: true
  registration-endpoint-uri: /register  # adds registration_endpoint to discovery metadata

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.