Edit Page

OAuth 2.0 Social Login

RESTHeart

restheart-accounts supports OAuth 2.0 social login with multiple providers out of the box (Google, GitHub) and a Java SPI for custom providers. The backend handles the complete Authorization Code flow using ScribeJava; the client application performs only two redirects and never handles tokens directly.

Social login is opt-in: it is enabled only when oauthConfig.enabled: true is set in restheart.yml. Each provider is individually toggled via its own enabled flag inside the providers map.

Note
This is not the same as RESTHeart’s built-in OAuth 2.1 authorization server (for MCP). See [_mcp_authentication] for details.

Flow

Browser  → GET /auth/oauth/authorize/{provider}
         restheart-accounts generates state, stores in oauth_codes (TTL 10 min)
         → 302 to provider consent screen

Provider → GET /auth/oauth/callback/{provider}?code=…&state=…
         restheart-accounts:
           validates state (constant-time comparison)
           exchanges code for access token (server-side, via ScribeJava)
           fetches user profile from provider
           upserts user in MongoDB
           issues JWT cookie
           → 302 to frontend-success-url

Configuration

oauthConfig:
  # Master switch. Set to false to disable all OAuth endpoints while keeping
  # the configuration in place.
  enabled: true

  # Base URL of the RESTHeart API instance.
  # Each provider's callback URL is constructed as:
  #   {api-base-url}/auth/oauth/callback/{provider}
  api-base-url: https://api.example.com

  # Browser is redirected here after a successful login.
  frontend-success-url: https://app.example.com/app

  # Browser is redirected here when the OAuth flow fails (e.g. user denied
  # consent, invalid state).
  frontend-error-url: https://app.example.com/login?error=oauth_error

  providers:
    google:
      enabled: true
      client-id:     "123….apps.googleusercontent.com"
      client-secret: "GOCSPX-…"
      scope:         "openid email profile"   # optional — this is the default

    github:
      enabled: true
      client-id:     "Iv1.…"
      client-secret: "…"
      scope:         "user:email"             # optional — this is the default

    # Custom provider — requires a matching OAuthProvider plugin in plugins/
    # myapp:
    #   enabled: true
    #   client-id: "…"
    #   client-secret: "…"
    #   scope: "read:profile"
Note
Store all client-secret values as environment variables. See Secrets management (RHO).

Google

Google Cloud Console setup

  1. Open Google Cloud Console and select (or create) your project.

  2. Navigate to APIs & Services → OAuth consent screen and complete the consent screen configuration (app name, support email, authorized domains).

  3. Navigate to APIs & Services → Credentials.

  4. Click Create Credentials → OAuth 2.0 Client ID.

  5. Set Application type to Web application.

  6. Under Authorized redirect URIs, add:

    https://api.example.com/auth/oauth/callback/google

    Replace the hostname with your actual oauthConfig.api-base-url.

  7. Click Create. Copy the Client ID and Client Secret into oauthConfig.providers.google.

  8. Ensure the Google People API (or the openid scope via Google Identity) is enabled for the project — it is required to retrieve email and profile claims.

User behavior

New user (no matching email in the users collection):

  1. A new user document is created with status: "active". Google has already verified the email address, so no separate email verification step is needed.

  2. A team is created automatically and the user is assigned the owner role.

  3. A JWT cookie is set and the browser is redirected to frontend-success-url.

Existing user (email matches an existing document):

  1. The user’s profile fields (firstName, lastName, googleId) are updated from Google’s response if they have changed.

  2. The user’s status is not changed — a user who was previously suspended or pending remains in that state and is not granted access.

  3. A JWT cookie is set and the browser is redirected to frontend-success-url.

Note
If the user was registered with email + password, adding Google login does not remove the password. Both authentication methods remain available.

GitHub

GitHub OAuth App setup

  1. Open GitHub Developer Settings → OAuth Apps → New OAuth App.

  2. Fill in the Application name and Homepage URL.

  3. Set Authorization callback URL to:

    https://api.example.com/auth/oauth/callback/github

    Replace the hostname with your actual oauthConfig.api-base-url.

  4. Click Register application. Copy the Client ID, then click Generate a new client secret and copy the secret.

  5. Add both values to oauthConfig.providers.github in restheart.yml.

Email resolution

GitHub does not always include the user’s email address in the main profile response (e.g. when the user has set their email to private). When the email is absent from https://api.github.com/user, GitHubOAuthProvider automatically fetches https://api.github.com/user/emails and selects the first verified, primary address.

This secondary fetch requires the user:email scope, which is the default. Narrowing the scope may cause email resolution to fail and the login flow to return an error.

Custom providers

Any Java plugin can register an additional OAuth provider via the OAuthProvider SPI.

Interface contract

@RegisterPlugin(name = "myappOAuthProvider", description = "MyApp OAuth provider")
public class MyAppOAuthProvider implements OAuthProvider, Initializer {

    @Inject("oauthService")
    private OAuthService oauthService;

    @Override
    public void init() {
        // Register this provider with the OAuth service at startup.
        oauthService.registerProvider(this);
    }

    /**
     * Must return the key used in oauthConfig.providers.
     * The OAuth endpoints derive the provider name from the URL path:
     *   /auth/oauth/authorize/myapp  →  getProviderName() == "myapp"
     */
    @Override
    public String getProviderName() { return "myapp"; }

    /**
     * Build the ScribeJava OAuth20Service for this provider.
     * Use any ScribeJava OAuth20Api subclass or a custom implementation.
     */
    @Override
    public OAuth20Service buildService(String clientId, String secret,
                                       String callbackUrl, String scope) {
        return new ServiceBuilder(clientId)
                .apiSecret(secret)
                .callback(callbackUrl)
                .defaultScope(scope)
                .build(MyAppApi.instance());
    }

    /**
     * Fetch the user profile using the access token.
     * Return a BsonDocument with: email, name, providerId, avatarUrl.
     */
    @Override
    public BsonDocument fetchUserProfile(OAuth20Service service, String accessToken)
            throws Exception {
        // Call your provider's user-info endpoint and map the response fields.
        return new BsonDocument()
                .append("email",      new BsonString("user@example.com"))
                .append("name",       new BsonString("Display Name"))
                .append("providerId", new BsonString("12345"))
                .append("avatarUrl",  BsonNull.VALUE);
    }
}

Key rules:

  • Implement both OAuthProvider and Initializer.

  • Inject oauthService with @Inject("oauthService") and call registerProvider(this) inside init().

  • getProviderName() must return the exact key used in the oauthConfig.providers map.

  • buildService() can use any ScribeJava OAuth20Api subclass.

  • fetchUserProfile() must return a BsonDocument with the following fields:

Field Type Required Notes

email

BsonString

Yes

Used as the user’s unique identifier in MongoDB

name

BsonString

Yes

Display name

providerId

BsonString

Yes

Provider-assigned user ID

avatarUrl

BsonString or BsonNull

No

Profile picture URL; use BsonNull.VALUE if unavailable

Configuration for a custom provider

Add the provider key to the providers map and place the compiled plugin JAR in RESTHeart’s plugins/ directory alongside restheart-accounts.jar.

oauthConfig:
  enabled: true
  api-base-url: https://api.example.com
  frontend-success-url: https://app.example.com/app
  frontend-error-url:   https://app.example.com/login?error=oauth_error

  providers:
    myapp:
      enabled: true
      client-id:     "…"
      client-secret: "…"
      scope:         "read:profile"

The provider is auto-discovered at startup via @RegisterPlugin.

Security notes

  • A cryptographically random state parameter (256-bit entropy) is generated for every authorization request and stored in the oauth_codes MongoDB collection with a 10-minute TTL.

  • On callback, state is validated with constant-time comparison to prevent timing attacks.

  • The oauth_codes document is deleted immediately after a successful state validation — it cannot be replayed.

  • Token exchange is performed entirely server-side by ScribeJava; client_secret values are never exposed to the browser.

See also: Security.

MCP Authentication

RESTHeart ships with a built-in OAuth 2.1 authorization server that exposes standard endpoints such as /authorize, /token, and /introspect. This server is designed for tool and client authentication in MCP (Model Context Protocol) deployments — i.e. it allows MCP clients to obtain tokens that authorize calls to the RESTHeart API.

This is completely separate from the social login feature documented on this page:

Feature Endpoints Purpose Implemented by

Social login (Google, GitHub, custom)

/auth/oauth/authorize/{provider}
/auth/oauth/callback/{provider}

End-user sign-in via OAuth provider

restheart-accounts plugin

MCP / OAuth 2.1 server

/authorize, /token, /introspect, etc.

Tool / client authentication for MCP

RESTHeart core

Configuring or disabling oauthConfig in restheart-accounts has no effect on RESTHeart’s built-in OAuth 2.1 server, and vice versa. Refer to the RESTHeart core documentation for MCP OAuth 2.1 setup instructions.