# Social Login (Google OAuth) Zero-setup Google login for your app. Cohesivity handles the entire OAuth flow, stores users, issues tokens, and manages sessions. No Google Cloud Console setup needed. ## Prerequisites Provision this resource before use. ### Provision curl -s -X POST https://cohesivity.ai/api/resources/social-login \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"callback_urls": ["https://yourapp.com/auth/done", "http://localhost:5173/auth/done"]}' **Required body:** `{ "callback_urls": ["..."] }` — array of allowed redirect URLs. Also accepts `{ "callback_url": "..." }` (single string, converted to array). Subdomain wildcards are supported (e.g. `https://*.vercel.app/auth/callback`). **Re-provisioning:** Calling POST again with new callback_urls updates the URLs without deleting users or sessions. **Response:** `{ login_url, callback_urls }` — the login URL and registered callback URLs. ### Delete curl -s -X DELETE https://cohesivity.ai/api/resources/social-login \ -H "Authorization: Bearer " **WARNING: Deleting social-login permanently removes ALL users, sessions, and configuration. This is irreversible.** **Important:** Provision this resource now, before building or running the application. Provisioning is the agent's job, not the application's. ## Common Mistakes - **Accessing verify response fields incorrectly.** Response is NESTED: `{ valid, user: { id, email, name, picture } }`. Use `data.user.email`, NOT `data.email`. Use `data.user.id`, NOT `data.sub` or `data.user_id`. - **Verifying JWTs locally.** Always use the verify endpoint. Do not decode or validate JWTs yourself. - **Using `router.push("/")` in the callback handler (Next.js double-login bug).** After storing tokens from the callback URL, you MUST navigate home with `window.location.href = "/"`, NOT `router.push("/")`. In Next.js App Router, `router.push` is a client-side navigation — the layout stays mounted, so AuthProvider never re-runs and the user appears logged out. `window.location.href` forces a full page reload, which remounts the AuthProvider, reads the newly-stored tokens, and completes login in one click. This applies to any framework where an auth-checking wrapper component only initializes on mount. - **Storing tokens in localStorage from the callback page.** Safari's Intelligent Tracking Prevention (ITP) silently discards localStorage writes on pages reached via cross-origin redirects — which is exactly what OAuth does. Use a server-side route handler that sets httpOnly cookies instead. See "Recommended: Server-Side Callback" below. - **Setting `sameSite: "strict"` on auth cookies.** OAuth callbacks are cross-origin redirects. `strict` drops cookies on cross-origin navigation. Use `sameSite: "lax"` for auth cookies. - **Losing the deep link after login.** If the user opened a page like `/meeting/abc`, append `?return_to=/meeting/abc` to the login URL. Cohesivity preserves it through the OAuth flow and returns it to your callback URL as `return_to`. - **User ID type mismatch with realtime.** Social-login returns `user.id` as a **number** (e.g., `30`). Realtime presence returns user IDs as **strings** (e.g., `"30"`). When using both services together, always convert with `String(user.id)` before comparing or storing in a Set/Map. ## How It Works 1. User clicks "Sign in with Google" → link to `login_url` (optionally append `?redirect_uri=` to select which registered callback URL to use; defaults to first). To resume a deep link after login, also append `&return_to=/path/inside/your/app`. 2. Google authenticates the user 3. Cohesivity receives the result, shows a consent screen, stores the user, issues tokens 4. User is redirected to your callback URL with two query parameters, plus optional `return_to` if you provided it: - `access_token` — short-lived JWT (1 hour). Use this for all authenticated requests. - `refresh_token` — long-lived opaque token (30 days). Use this to get a new `access_token` when it expires. 5. Your app saves both tokens. Parse them from the callback URL query string. ## Callback URL Format https://yourapp.com/auth/done?access_token=&refresh_token= On error (user denied consent): https://yourapp.com/auth/done?error=access_denied ## Callback Handler Implementation Your callback page MUST: (1) read tokens from the URL query params, (2) store them in localStorage, (3) hard-navigate to `return_to` if present, otherwise home. Use `window.location.href`, NOT `router.push` or any client-side navigation. ``` // Example: Next.js App Router callback page (src/app/auth/done/page.tsx) "use client"; import { useEffect } from "react"; import { useSearchParams } from "next/navigation"; export default function AuthCallback() { const params = useSearchParams(); useEffect(() => { const access_token = params.get("access_token"); const refresh_token = params.get("refresh_token"); const return_to = params.get("return_to") || "/"; if (access_token) { localStorage.setItem("access_token", access_token); localStorage.setItem("refresh_token", refresh_token || ""); window.location.href = return_to; // MUST be hard navigation, NOT router.push } }, [params]); return

Signing in...

; } ``` **Why `window.location.href` and not `router.push`:** In Next.js App Router, layouts persist across client-side navigations. If your AuthProvider lives in the layout and already ran its initialization effect (finding no tokens), `router.push` will NOT re-trigger it. `window.location.href` forces a full page reload so the AuthProvider starts fresh and finds the tokens. ## Recommended: Server-Side Callback For Safari compatibility, use a server-side route handler instead of a client-side page. Safari's ITP silently discards localStorage writes after cross-origin redirects (which OAuth does). ``` // Next.js App Router route handler (src/app/auth/done/route.ts) import { NextRequest, NextResponse } from "next/server"; export async function GET(req: NextRequest) { const access_token = req.nextUrl.searchParams.get("access_token"); const refresh_token = req.nextUrl.searchParams.get("refresh_token"); const return_to = req.nextUrl.searchParams.get("return_to") || "/"; const res = NextResponse.redirect(new URL(return_to, req.url)); if (access_token) { res.cookies.set("access_token", access_token, { httpOnly: true, secure: true, sameSite: "lax", path: "/", maxAge: 3600 }); res.cookies.set("refresh_token", refresh_token || "", { httpOnly: true, secure: true, sameSite: "lax", path: "/", maxAge: 30 * 86400 }); } return res; } ``` Then read tokens from cookies in your server components or API routes (`req.cookies.get("access_token")`) instead of localStorage. ## Auth Helper Pattern (Next.js) Create a shared auth helper for your API routes. All Cohesivity auth calls should happen server-side — this is why httpOnly cookies work (the token never needs to leave the server). ``` // lib/auth.ts — call from API route handlers and server components export async function getAuthUser(cookieStore) { const accessToken = cookieStore.get("access_token")?.value; const refreshToken = cookieStore.get("refresh_token")?.value; if (!accessToken) return null; // 1. Verify access token const verify = await fetch(`${COHESIVITY}/edge/auth/${TENANT}/verify`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ access_token: accessToken }), }).then(r => r.json()); if (verify.valid) return { user: verify.user }; // 2. Access token expired — refresh if (!refreshToken) return null; const tokens = await fetch(`${COHESIVITY}/edge/auth/${TENANT}/refresh`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ refresh_token: refreshToken }), }).then(r => r.json()); if (!tokens.access_token) return null; // 3. Return user + new tokens (caller sets cookies on response) const re = await fetch(`${COHESIVITY}/edge/auth/${TENANT}/verify`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ access_token: tokens.access_token }), }).then(r => r.json()); if (!re.valid) return null; return { user: re.user, newTokens: tokens }; } ``` In your API route, call `getAuthUser(cookies())`. If `newTokens` is set, update cookies on the response. ## Auth Endpoints These are public endpoints — no management key needed. - **Start login:** `GET https://cohesivity.ai/edge/auth/:tenantId/google` — browser redirect to Google. Optional query param `redirect_uri` to select which registered callback URL to use (must match one of your `callback_urls`). Defaults to first registered URL. Optional `return_to=/path` preserves a deep link inside your app and sends it back on the callback URL. - **Verify access token:** `POST https://cohesivity.ai/edge/auth/:tenantId/verify` with `{ "access_token": "" }` — returns `{ valid, user: { id, email, name, picture } }` - **Refresh tokens:** `POST https://cohesivity.ai/edge/auth/:tenantId/refresh` with `{ "refresh_token": "" }` — returns new `{ access_token, refresh_token }` (old refresh invalidated) - **Logout:** `POST https://cohesivity.ai/edge/auth/:tenantId/logout` with `{ "refresh_token": "" }` — deletes session ## User Management Endpoints These require `Authorization: Bearer ` header. - **List users:** `GET https://cohesivity.ai/api/social-login/users` Response: `{ "users": [{ "id": number, "email": string, "name": string|null, "picture": string|null, "first_seen": ISO8601, "last_seen": ISO8601 }] }` - **Get user by ID:** `GET https://cohesivity.ai/api/social-login/users/:id` Response: `{ "user": { "id": number, "email": string, "name": string|null, "picture": string|null, "first_seen": ISO8601, "last_seen": ISO8601 } }` Returns 404 if user not found. ## Verify Response Format Do NOT verify JWTs locally. Always use the verify endpoint. Response is NESTED: Valid: { "valid": true, "user": { "id": 1, "email": "...", "name": "...", "picture": "..." } } Invalid: { "valid": false } Access fields as `data.user.email`, NOT `data.email`. User ID is `data.user.id`, NOT `data.sub` or `data.user_id`. ## Examples **Verify an access token:** curl -s -X POST https://cohesivity.ai/edge/auth//verify \ -H "Content-Type: application/json" \ -d '{"access_token": ""}' **Refresh tokens:** curl -s -X POST https://cohesivity.ai/edge/auth//refresh \ -H "Content-Type: application/json" \ -d '{"refresh_token": ""}' **Logout:** curl -s -X POST https://cohesivity.ai/edge/auth//logout \ -H "Content-Type: application/json" \ -d '{"refresh_token": ""}' ## Token Details - **`access_token`:** JWT, expires in 1 hour. Contains id, email, name, picture, tenant_id. Send this to the verify endpoint to authenticate requests. - **`refresh_token`:** Opaque string, expires in 30 days. Single-use — each refresh invalidates the old pair and returns a new `access_token` + `refresh_token`. - **Verify endpoint:** Stateless JWT check — no database call, fast. Always use this instead of local JWT verification.