Social login setup (Google & Apple)
A step-by-step guide for project owners to let their app's end-users sign in with Google or Apple. This is white-label: you use your own OAuth credentials, so the consent screen shows your app — not manggaleh.
For an assistant guiding a customer: this page is self-contained. Before starting, ask the customer for: (1) their project slug, (2) which environment they're configuring (
dev/staging/prod), and (3) their app's public URL (e.g.https://app.acme.com). Do each provider's steps in order; every value the customer needs to copy has an exact source named below. The callback URL is the #1 source of mistakes — always copy it from the dashboard, never type it by hand.
How it works (30-second mental model)
user clicks "Sign in with Google" in your app
→ your app calls client.auth.signInSocial({ provider, callbackURL })
→ manggaleh returns Google's authorization URL; your app redirects the browser there
→ user approves on Google
→ Google redirects back to MANGGALEH's callback URL (with a one-time code)
→ manggaleh exchanges the code (using YOUR client secret), creates/links the end-user
in your environment, starts a session, and redirects the browser to your callbackURL
→ your app calls client.auth.getSession() → the user is signed in
The registered "redirect URI" at Google/Apple is manggaleh's callback URL, not your app's.
Your callbackURL is only where manggaleh sends the browser after login completes.
Where the dashboard settings live: open your project → pick the environment → Sign-up tab. You'll use two sections there: Auth origins and Social login.
Part A — Google
A1. Create OAuth credentials in Google Cloud Console
- Go to https://console.cloud.google.com/ → create or pick a project.
- APIs & Services → OAuth consent screen: choose External, set your app name and logo (this is what users see on the consent screen), and add your support email. Publish it (or add test users while in "Testing").
- APIs & Services → Credentials → Create Credentials → OAuth client ID.
- Application type: Web application. Give it a name (e.g. "Acme — prod").
- Under Authorized redirect URIs, click Add URI and paste the exact callback URL
shown in manggaleh (see A2). It looks like:
https://<your-manggaleh-api-host>/api/t/<slug>/<env>/auth/callback/google - Create. Copy the Client ID and Client secret.
A2. Enter it in manggaleh
- Dashboard → project → environment → Sign-up tab → Social login.
- First, copy the "Callback URL" shown under Google there and register it in Google (step A5 above) — the dashboard is the source of truth for this URL.
- Tick Google, paste Client ID and Client secret, and Save. (The secret is stored encrypted and never shown again — leave the field blank on future edits to keep it.)
A3. Allow your app to receive the return
In the same Sign-up tab → Auth origins, add your app's origin, e.g.
https://app.acme.com. This lets manggaleh accept your callbackURL after login. (A relative
callbackURL like /auth/done also works without this.)
Field map (Google):
| Google Console | → | manggaleh field |
|---|---|---|
| Client ID | → | Client ID |
| Client secret | → | Client secret |
| Authorized redirect URI | ← | Callback URL (copy from manggaleh into Google) |
Part B — Apple
Apple needs an Apple Developer Program membership ($99/yr) and four identifiers. It's more involved than Google — take the steps in order.
B1. Gather the four Apple values
At https://developer.apple.com/account/ → Certificates, Identifiers & Profiles:
- App ID — under Identifiers, create (or reuse) an App ID with the Sign in with Apple capability enabled.
- Services ID — create an Identifier of type Services ID (e.g.
com.acme.web). This string is your client_id. Enable Sign in with Apple on it, then Configure:- Primary App ID: the App ID from step 1.
- Domains and Subdomains: your manggaleh API host (e.g.
api.manggaleh.com). - Return URLs: paste the Apple callback URL shown in manggaleh:
https://<your-manggaleh-api-host>/api/t/<slug>/<env>/auth/callback/apple
- Key (.p8) — under Keys, create a key with Sign in with Apple enabled. Download
the
.p8file (you can only download it once!) and note the Key ID. - Team ID — shown at the top-right of the developer portal (a 10-character string).
B2. Enter it in manggaleh
Dashboard → Sign-up tab → Social login → tick Apple and fill:
| Apple value | → | manggaleh field |
|---|---|---|
Services ID (e.g. com.acme.web) |
→ | Services ID |
| Team ID (10 chars) | → | Team ID |
| Key ID (from the key) | → | Key ID |
Contents of the .p8 file |
→ | Private key (.p8) |
| Return URL | ← | Callback URL (copy from manggaleh into Apple) |
Then Save. manggaleh generates the client-secret JWT Apple requires from your .p8 +
Team/Key/Services IDs automatically — you don't create it yourself.
Also add your app origin under Auth origins (same as Google, step A3).
B3. Apple gotchas (tell the customer up front)
- HTTPS only, no
localhost. Apple rejectshttp/localhost return URLs. To test locally, use a tunnel (ngrok/cloudflared) or test on a real staging domain. - Name is returned only once — on the user's first authorization. If your app doesn't capture it then, it's gone (Apple won't resend it).
- Email may be a private relay (
…@privaterelay.appleid.com) if the user picked "Hide My Email". Treat it as a normal, working address.
Part C — In your app (the SDK)
import { createClient } from "@manggaleh/sdk";
const client = createClient({ tenant: "acme", env: "prod", apiKey: "mgpk_…" });
// On your "Sign in with Google" (or Apple) button:
async function signInWithGoogle() {
const { url } = await client.auth.signInSocial({
provider: "google", // or "apple"
callbackURL: "https://app.acme.com/auth/done", // must be a trusted origin, or a relative path
});
window.location.href = url!; // browser goes to Google, then back to callbackURL
}
// On your /auth/done page (after the redirect back):
const session = await client.auth.getSession(); // → the signed-in end-user, or null
After this the user is a normal end-user in that environment — subject to RLS, ABAC permissions, everything. You can link, list, or manage them like any other user.
The snippet above works when your app is served from the manggaleh API origin (the session
cookie is first-party). If your app is on a different origin — a separate domain, or a
Capacitor app (capacitor://localhost / http://localhost) — use the token bridge below.
Capacitor / cross-origin web apps
A Capacitor app is a web app in a native WebView, and its origin is cross-origin to the API,
so the plain cookie flow can't hand the session back. Use returnTo + completeSocialSignIn():
manggaleh routes the OAuth return through a token bridge and sends you back with the session
token in the URL fragment. This works the same regardless of how Capacitor serves your app —
you just register the right origin and set returnTo to it. Two common modes:
- Bundled locally (Capacitor ships the web assets): origin is
capacitor://localhost(iOS) orhttp://localhost/ yourserver.hostname(Android). - Remote /
server.url(the WebView loads your live site over https): origin is that site, e.g.https://app.acme.com. This is the cleanest variant — everything is https, no custom scheme, and the origin guard is host-tight (https://app.acme.com, not the whole scheme).
1. Register your app's origin under Sign-up → Auth origins (dashboard) — whichever your Capacitor config actually uses:
capacitor://localhost # iOS bundled (Capacitor default)
http://localhost # Android bundled (default) — or your server.hostname
https://app.acme.com # remote server.url mode
2. Start sign-in with returnTo (the URL to land on after login):
const { url } = await client.auth.signInSocial({
provider: "google", // or "apple"
returnTo: "capacitor://localhost/auth/done", // bundled; or "https://app.acme.com/auth/done"
}); // (server.url mode). Must match an Auth origin.
window.location.href = url!; // WebView → Google → bridge → back to returnTo
3. Finish on your return page (/auth/done):
const session = await client.auth.completeSocialSignIn(); // reads #token from the URL, stores it
// session.user is now signed in; the SDK uses the token as its bearer for all calls
That's it — no cookies, works in the WebView. Notes:
- Google/Apple console setup is unchanged. You still register only the https
…/auth/callback/<provider>there. The bridge and yourcapacitor://return are internal — the provider never sees them. - The token arrives in the URL fragment (
#token=…);completeSocialSignIn()reads it and strips it from the URL. Never put it in a query string. returnTomust be a registered Auth origin — the bridge refuses to forward a token to any other destination (open-redirect / token-theft guard).
Per-environment: important
Each environment (dev, staging, prod) has its own callback URL and its own OAuth
config:
https://…/api/t/acme/dev/auth/callback/google
https://…/api/t/acme/prod/auth/callback/google ← different!
So you either register multiple redirect URIs on one Google OAuth client (one per env), or create a separate OAuth client per environment. The dashboard always shows the correct callback URL for the environment you're viewing — copy that one.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
Invalid callbackURL / INVALID_CALLBACK_URL from manggaleh |
Your callbackURL isn't a trusted origin |
Add your app origin under Sign-up → Auth origins, or use a relative path (/auth/done) |
Capacitor: Invalid or untrusted return URL on the bridge (400) |
Your returnTo origin isn't registered |
Add the exact Capacitor origin (capacitor://localhost, http://localhost, …) under Sign-up → Auth origins |
Capacitor: back on the app but completeSocialSignIn() returns null |
The return page didn't receive the #token fragment |
Ensure returnTo points at a page that runs the SDK and calls completeSocialSignIn(); check the URL kept its #… fragment |
Google: redirect_uri_mismatch |
The redirect URI registered in Google ≠ the callback manggaleh sends | Copy the exact callback URL from the dashboard into Google's Authorized redirect URIs (watch for a missing /, wrong env, or http vs https) |
| Google: consent screen blocked / "app not verified" | OAuth consent screen still in Testing | Add the tester's email as a test user, or publish the consent screen |
Apple: invalid_client |
Wrong Services ID / Team ID / Key ID, or the .p8 doesn't match the Key ID, or the Return URL isn't registered on the Services ID |
Re-check all four values in Social login, and confirm the Return URL is listed on the Services ID's Sign in with Apple config |
Apple: invalid_grant / redirect fails |
Return URL is http/localhost, or domain not verified |
Use an HTTPS domain; add it under Domains and register the exact Return URL |
Signed in but getSession() is null on your page |
callbackURL pointed somewhere that didn't load the SDK/cookie |
Ensure the return page runs the SDK on the same origin you configured |
Provider button does nothing / 404 on /sign-in/social |
Provider not enabled/saved in manggaleh | Tick the provider and Save in Social login; confirm the Client ID/secret (or Apple values) are filled |
Checklist (quick reference)
- OAuth consent screen configured (app name + logo)
- OAuth Web client created
- manggaleh's Google Callback URL added to Google's Authorized redirect URIs
- Client ID + Client secret saved in manggaleh → Social login
- App origin added under Auth origins
Apple
- App ID with Sign in with Apple
- Services ID (client_id) with manggaleh's Apple Callback URL as a Return URL
- Key created +
.p8downloaded + Key ID noted - Team ID noted
- Services ID / Team ID / Key ID /
.p8saved in manggaleh → Social login - App origin added under Auth origins
App
-
client.auth.signInSocial({ provider, callbackURL })wired to the button - Return page calls
client.auth.getSession()