Edit Page

How Clients Authenticate

RESTHeart Cloud

πŸ”§ Configuration

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

What You’ll Learn

In this guide, you’ll learn how to:

  1. Authenticate using Basic Authentication with username and password

  2. Understand how the Authorization header works

  3. Obtain and use OAuth 2.0 access tokens

  4. Check user credentials and roles

  5. Avoid browser authentication popups

By the end, you’ll understand how clients authenticate with RESTHeart and how to implement authentication in your applications.

Note

In all examples below:

  • [RESTHEART-URL] - Replace with your RESTHeart server URL (e.g., http://localhost:8080)

  • [BASIC-AUTH] - Replace with username:password format (e.g., admin:secret)

The interactive examples on this page can automatically substitute these values.

⚑ Setup Guide

β–Ό

To run the examples on this page, you need a RESTHeart instance.

Option 1: Use RESTHeart Cloud (Recommended)

The fastest way to get started is with RESTHeart Cloud. Create a free service in minutes:

  1. Sign up at cloud.restheart.com

  2. Create a free API service

  3. Set up your root user following the Root User Setup guide

  4. Use the configuration panel above to set your service URL and credentials

Tip
All code examples on this page will automatically use your configured RESTHeart Cloud credentials.

Option 2: Run RESTHeart Locally

If you prefer local development, follow the Setup Guide to install RESTHeart on your machine.

Note
Local instances run at http://localhost:8080 with default credentials admin:secret

Introduction

Clients can authenticate passing credentials via the different authentication schemes handled by restheart-security. This section shows how clients can authenticate using the simple basic authentication, a standard method for an HTTP user agent to provide a username and password when making a request.

Some examples

Here’s how to authenticate with basic credentials:

cURL

curl -i --user [BASIC-AUTH] -X GET [RESTHEART-URL]/

HTTPie

http -a [BASIC-AUTH] GET [RESTHEART-URL]/

JavaScript

const username = 'your-username';
const password = 'your-password';
const credentials = btoa(`${username}:${password}`);

fetch('[RESTHEART-URL]/', {
    method: 'GET',
    headers: {
        'Authorization': `Basic ${credentials}`
    }
})
.then(response => response.json())
.then(data => {
    console.log('Retrieved data:', data);
})
.catch(error => console.error('Error:', error));

Basic Authentication for dummies

Basic Authentication requires the client to send its credentials with the Authorization request header.

The value of the Authorization request header must be: Basic base64(<userid> + ':' + <password>)

In other words:

  1. userid and password are combined into a string "userid:password". Note the colon between userid and password (userid cannot contain the ":" character).

  2. The resulting string is base 64 encoded

  3. The string "Basic " (note the space) is then put before the encoded string.

Authentication Token

RESTHeart v9 provides a standards-compliant OAuth 2.0/2.1 token endpoint. Once a client has authenticated (Basic Auth, an OAuth grant, or a delegated provider login), it needs a token back to use for subsequent requests β€” RESTHeart offers three ways to hand that token over, and picking the right one is the core decision in designing a sign-in flow.

Choosing How to Deliver the Token

RESTHeart offers three ways to hand a token to a client, each with a different security trade-off. There’s no universally "best" option β€” pick based on your architecture.

Quick decision guide:

  1. Frontend and backend share a registrable domain (same-site β€” e.g. app.example.com calling api.example.com)?
    β†’ Use POST /token/cookie. It’s the simplest option: the browser attaches the cookie automatically, the token is never exposed to JavaScript, and none of the cross-origin caveats below apply.

  2. Otherwise (cross-origin frontend), does the sign-in step end in a real browser navigation rather than a fetch() call (OAuth-provider callback, email-verification link, magic link)?
    β†’ Use GET /token/redirect (since 9.5). There’s no JSON response for your JavaScript to read in this case, so the token has to travel via the redirect itself.

  3. Otherwise (cross-origin, and your own JavaScript makes the request) β€” login forms, token refresh, any SPA that can’t rely on a same-site cookie:
    β†’ Use POST/GET /token and hold the token in memory (not localStorage). This is the general default for cross-origin SPAs, and the only option that works reliably in Safari, where third-party cookies are blocked outright regardless of SameSite.

Pros Cons Use it when

Token in body (POST/GET /token)

Works identically in every browser, no cross-origin restrictions of any kind; simplest to reason about; no ambient credential means near-zero CSRF risk (a forged cross-site request can’t include a token it doesn’t know)

The token is JavaScript-accessible wherever the client stores it (memory, localStorage, …​) β€” if the page has an XSS vulnerability, the token can be read and exfiltrated. Mitigate with short TTLs and in-memory-only storage (never localStorage)

The frontend itself initiates the request (fetch()/XHR) β€” login forms, token refresh, any cross-origin SPA that can’t rely on a same-site cookie

HttpOnly cookie (POST /token/cookie)

Never readable by JavaScript at all β€” immune to XSS token theft (though an XSS can still ride the ambient cookie to perform actions as the user)

Doesn’t work reliably cross-origin: SameSite=Strict/Lax block it for cross-site fetch/XHR outright, and even SameSite=None isn’t honored uniformly β€” Safari’s ITP has blocked third-party cookies by default since 13.1 (2019), independent of SameSite. Best suited to same-site deployments (frontend and API share a registrable domain)

Frontend and API share a registrable domain (true same-site), and you want the session to survive without any client-side token handling at all

Redirect + fragment (GET /token/redirect, since 9.5)

Works in every browser like the body option (no cookie involved at all); purpose-built for flows that must end in a real navigation (OAuth callbacks, etc.)

Same XSS-exposure profile as the body option, plus the token is briefly visible in the URL/browser history until the frontend clears the fragment

The authentication step itself ends in a server-side redirect, not a fetch() β€” there’s no JSON response for the frontend to read (OAuth provider callbacks are the canonical case)

The three flows

Token in body β€” the client makes the request directly and reads the token from the JSON response:

sequenceDiagram
  participant C as Client
  participant R as RESTHeart

  C->>R: POST /token (Basic Auth)
  R-->>C: 200, JSON body { token, ... }
  Note over C: hold token in memory
  C->>R: GET /resource (Authorization: Bearer <token>)
  R-->>C: 200

HttpOnly cookie β€” the browser handles the cookie automatically on same-site requests, no client-side token handling at all:

sequenceDiagram
  participant C as Client
  participant R as RESTHeart

  C->>R: POST /token/cookie (Basic Auth)
  R-->>C: 200, Set-Cookie: <JWT> (HttpOnly)
  C->>R: GET /resource (cookie sent automatically)
  R-->>C: 200

Redirect + fragment β€” used when authentication itself ends in a real browser navigation rather than a fetch() call (e.g. after an OAuth-provider round trip):

sequenceDiagram
  participant C as Browser
  participant R as RESTHeart

  Note over C,R: some flow ending in a real navigation, e.g. OAuth callback
  C->>R: GET /token/redirect (already authenticated)
  R-->>C: 307, Location: https://app.example.com/callback#access_token=...
  Note over C: frontend JS reads location.hash,<br/>stores token, clears the fragment
  C->>R: GET /resource (Authorization: Bearer <token>)
  R-->>C: 200
Synergy with originVetoer

originVetoer rejects requests whose Origin header isn’t allowlisted β€” the standard CSRF defense for cookie-based sessions, since a browser attaches a cookie automatically regardless of which site triggered the request.

  • Cookie-based auth genuinely needs originVetoer (or equivalent Origin validation): without it, any website could trigger state-changing requests using a victim’s ambient session cookie.

  • Token-in-body and redirect+fragment don’t rely on cookies, so originVetoer isn’t strictly required for CSRF purposes with those β€” a forged request from another site simply has no token to send. But precisely because there’s no cookie in play, there’s no cross-origin trade-off to protect (no SameSite relaxation, no legitimate cross-site cookie flow that a strict whitelist could break) β€” this makes originVetoer a genuinely low-cost opportunity here: whitelisting the exact origin(s) allowed to call /token and /token/redirect restricts who can even attempt to obtain a token in the first place (e.g. blocking credential-stuffing or brute-force attempts issued from arbitrary/unauthorized origins), on top of whatever’s already true for authenticated requests.

originVetoer itself supports a per-request whitelist override since RESTHeart 9.5 (override-origin-whitelist, see Other Security Plugins) β€” the same multi-tenant use case /token/redirect’s `override-redirect-url addresses: one RESTHeart instance serving many services, each needing its own allowed origin(s)/redirect target.

The OAuth 2.0 / 2.1 page has the complete API reference for each endpoint:

  • POST /token β€” password, client_credentials, and authorization_code grants

  • POST /token/cookie β€” HttpOnly cookie for browser applications

  • GET /token/redirect (since 9.5) β€” redirect-based handoff for browser-navigation flows

  • Authorization Code + PKCE flow (OAuth 2.1, RFC 7636)

  • Authorization Server Metadata discovery (RFC 8414)

  • Protected Resource Metadata (RFC 9728)

Legacy Token Management (Automatic Injection)

Note
This is the legacy token management approach. For new applications, use the OAuth 2.0 /token endpoint.

RESTHeart can also automatically inject auth tokens into response headers. The default configuration includes the tokenBasicAuthMechanism and the rndTokenManager.

With those plugins enabled, when a request is successfully authenticated, an auth token is generated by the Token Manager and included in every subsequent responses.

the tokenBasicAuthMechanism allows to authenticate the client using the auth token; the auth token is used as a temporary password in the basic authentication scheme. This means that the Authorization request header can either be calculated from the the Auth-Token itself:

Authorization: Basic base64(<userid> + ':' + <password>) or Authorization: Basic base64(<userid> + ':' + <auth-token>)

Auth token information are passed in the following response headers:

Auth-Token: 6a81d622-5e24-4d9e-adc0-e3f7f2d93ac7
Auth-Token-Location: /tokens/user@si.com
Auth-Token-Valid-Until: 2015-04-16T13:28:10.749Z
Note
the URI in the Auth-Token-Location header: the client can issue a GET request to obtain information about token or a DELETE request to invalidate it. Of course clients can only request their own tokens (otherwise response code will be 403 Forbidden).

Token Management Examples

To get information about your auth token:

cURL

curl -i --user [BASIC-AUTH] -X GET [RESTHEART-URL]/tokens/userid

HTTPie

http GET [RESTHEART-URL]/tokens/userid Authorization:"Basic [BASIC-AUTH]"

JavaScript

const username = 'your-username';
const password = 'your-password';
const credentials = btoa(`${username}:${password}`);

fetch('[RESTHEART-URL]/tokens/userid', {
    method: 'GET',
    headers: {
        'Authorization': `Basic ${credentials}`
    }
})
.then(response => response.json())
.then(data => {
    console.log('Token information:', data);
})
.catch(error => console.error('Error:', error));

To invalidate your auth token:

cURL

curl -i --user [BASIC-AUTH] -X DELETE [RESTHEART-URL]/tokens/userid

HTTPie

http DELETE [RESTHEART-URL]/tokens/userid Authorization:"Basic [BASIC-AUTH]"

JavaScript

const username = 'your-username';
const password = 'your-password';
const credentials = btoa(`${username}:${password}`);

fetch('[RESTHEART-URL]/tokens/userid', {
    method: 'DELETE',
    headers: {
        'Authorization': `Basic ${credentials}`
    }
})
.then(response => {
    if (response.ok) {
        console.log('Token invalidated successfully');
    } else {
        console.error('Failed to invalidate token:', response.status);
    }
})
.catch(error => console.error('Error:', error));
Tip
The Authentication Token is a very important feature when you are developing a web application. Since every request needs to include the credentials, you need to store them either in a cookie or (better) in the session storage. The sign-in form can check the credentials using the actual password; if it succeeds, the auth token can be stored and used.
Warning
Pay attention to the authentication token in case of multi-node deployments (horizontal scalability). In this case, you need to either disable it or use a load balancer with the sticky session option or a different Token Manager implementation.

The rndTokenManager can be configured as follows (note option TTL the auth token Time To Live in minutes):

rndTokenManager:
    ttl: 15
    srv-uri: /tokens

Suggested way to check credentials

The default restheart configuration file sets up the useful service roles, bound to /roles/<userid>

Here’s how to check credentials using the roles endpoint:

cURL

curl -i --user [BASIC-AUTH] -X GET [RESTHEART-URL]/roles/userid

HTTPie

http GET [RESTHEART-URL]/roles/userid Authorization:"Basic [BASIC-AUTH]"

JavaScript

const username = 'your-username';
const password = 'your-password';
const credentials = btoa(`${username}:${password}`);

fetch('[RESTHEART-URL]/roles/userid', {
    method: 'GET',
    headers: {
        'Authorization': `Basic ${credentials}`
    }
})
.then(response => response.json())
.then(data => {
    console.log('User roles:', data);
})
.catch(error => console.error('Error:', error));

The possible response codes of the request GET /roles/<userid> are:

  • 401 Unauthorized missing or wrong credentials

  • 403 Forbidden the userid in the URL does not match the one in the Authorization header

  • 200 OK credentials match; the following response document is sent back:

 {
    "authenticated": true,
    "roles": [
        "USER"
    ]
}

Of course, if the request succeeds, the client gets back the auth token as well.

Note
It is easy to check the user credentials from a login form with this handler: in case the client gets back 200, they match and the auth token can be stored for further request; otherwise passed credentials are wrong.

How to avoid the basic authentication popup in browsers

With basic authentication, browsers can show a awful login popup window and this is not what you usually want.

What happens behind the scene, is that the server sends the WWW-Authenticate response header that actually leads to it.

You can avoid RESTHeart to actually send this header avoiding the popup login window altogether, either specifying the No-Auth-Challenge request header or using the noauthchallenge query parameter. In this case, RESTHeart will just respond with 401 Unauthorized in case of missing or wrong credentials.

Tip
This feature together with the authentication token, allows you to implement a form based authentication experience on top of the simple and effective basic authentication mechanism.