OAuth 2.0 Social Login
RESTHeartrestheart-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 Cloud Console setup
-
Open Google Cloud Console and select (or create) your project.
-
Navigate to APIs & Services → OAuth consent screen and complete the consent screen configuration (app name, support email, authorized domains).
-
Navigate to APIs & Services → Credentials.
-
Click Create Credentials → OAuth 2.0 Client ID.
-
Set Application type to Web application.
-
Under Authorized redirect URIs, add:
https://api.example.com/auth/oauth/callback/google
Replace the hostname with your actual
oauthConfig.api-base-url. -
Click Create. Copy the Client ID and Client Secret into
oauthConfig.providers.google. -
Ensure the Google People API (or the
openidscope via Google Identity) is enabled for the project — it is required to retrieveemailandprofileclaims.
User behavior
New user (no matching email in the users collection):
-
A new user document is created with
status: "active". Google has already verified the email address, so no separate email verification step is needed. -
A team is created automatically and the user is assigned the
ownerrole. -
A JWT cookie is set and the browser is redirected to
frontend-success-url.
Existing user (email matches an existing document):
-
The user’s profile fields (
firstName,lastName,googleId) are updated from Google’s response if they have changed. -
The user’s
statusis not changed — a user who was previously suspended or pending remains in that state and is not granted access. -
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
-
Open GitHub Developer Settings → OAuth Apps → New OAuth App.
-
Fill in the Application name and Homepage URL.
-
Set Authorization callback URL to:
https://api.example.com/auth/oauth/callback/github
Replace the hostname with your actual
oauthConfig.api-base-url. -
Click Register application. Copy the Client ID, then click Generate a new client secret and copy the secret.
-
Add both values to
oauthConfig.providers.githubinrestheart.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
OAuthProviderandInitializer. -
Inject
oauthServicewith@Inject("oauthService")and callregisterProvider(this)insideinit(). -
getProviderName()must return the exact key used in theoauthConfig.providersmap. -
buildService()can use any ScribeJavaOAuth20Apisubclass. -
fetchUserProfile()must return aBsonDocumentwith the following fields:
| Field | Type | Required | Notes |
|---|---|---|---|
|
|
Yes |
Used as the user’s unique identifier in MongoDB |
|
|
Yes |
Display name |
|
|
Yes |
Provider-assigned user ID |
|
|
No |
Profile picture URL; use |
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
stateparameter (256-bit entropy) is generated for every authorization request and stored in theoauth_codesMongoDB collection with a 10-minute TTL. -
On callback,
stateis validated with constant-time comparison to prevent timing attacks. -
The
oauth_codesdocument is deleted immediately after a successful state validation — it cannot be replayed. -
Token exchange is performed entirely server-side by ScribeJava;
client_secretvalues 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) |
|
End-user sign-in via OAuth provider |
|
MCP / OAuth 2.1 server |
|
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.