Skip to content
KismetKismetDevelopers
llms.txt

Add Google guest sign-in

View .md

Let guests sign in with Google without leaving their place in your site. Kismet verifies the Google account and returns a short-lived code to your site; the SDK exchanges it through your server and creates the same secure guest session used by email-code sign-in.

Signing in is not marketing consent, membership enrollment, an offer claim, or a booking. Keep those choices as separate, explicit actions after sign-in. A successful sign-in must not trigger an enrollment automatically.

Use Kismet OAuth in the Developer MCP or the collection’s Developer API settings to configure:

  • An installation with guest_auth.write, in TEST for your first integration.
  • The exact HTTPS origin serving your site, such as https://staging.example.com, authorized on both the collection and installation. An authorized sibling domain is not sufficient.
  • A restricted server key, stored in your hosting provider’s secret store. A publishable key cannot start or complete this flow.
  • The Google account email you will test with, registered as a TEST identity on that same installation. The account still signs in with Google; the registration does not replace Google’s verification. An unregistered account is refused at completion. No email is sent by this flow.

Register the staging site and installation

Section titled “Register the staging site and installation”

Connect the Developer MCP with your Kismet manager account. A collection administrator can register the site hostname with register_collection_domain, using STAGING, PREVIEW, or DEVELOPMENT as appropriate. Preview the registration first and confirm it only if the hostname is not already registered to that collection.

Then configure a TEST Developer API installation with guest_auth.write and the same exact HTTPS origin in its allowed origins. Domain registration establishes which collection owns the site; the installation grants access to that origin. You need both. Registering example.com does not authorize staging.example.com, and a registered staging site does not authorize a LIVE installation.

For example, ask your coding agent:

Set up Google guest sign-in for my collection on https://staging.example.com. Check that staging.example.com is registered as STAGING, and preview a TEST installation with guest_auth.write and that exact origin. Show me the proposed access before applying it. Store any server key directly in my approved hosting secret store, and register the Google email I provide as a TEST identity.

See Create an application and key for credential setup. Do not add sign-in hosts by changing tracking configuration or by copying credentials between TEST and LIVE.

Install Kismet’s browser tracker and wait for the existing _kid_sid identity cookie before enabling sign-in. Do not generate a replacement session ID in your login component. The SDK returns 409 CANONICAL_SESSION_REQUIRED if the cookie is unavailable or ambiguous.

Kismet owns the registered Google callback. You do not need a customer-specific Google OAuth client or Google client secret. Your application owns only its same-origin return page, for example /my/auth/return.

The current verifier accepts Gmail and Google Workspace accounts with a verified email. For a Google account using another email provider, offer email-code sign-in instead.

TEST and LIVE use separate installations. TEST sign-in activity and journey adoption remain sandboxed; switching to LIVE requires explicitly configured LIVE access. Do not switch environments to work around a TEST error.

Keep this module server-only. The session secret is a separate random value of at least 32 characters; do not reuse the API key.

lib/kismet-auth.ts
import 'server-only';
import {
createKismetGuestAuthServerClient,
createKismetGuestAuthNextHandlers,
} from '@kismet-tech/sdk/next';
export const guestAuth = createKismetGuestAuthNextHandlers({
client: createKismetGuestAuthServerClient({
baseUrl: 'https://api.ksmt.app/v1',
serverKey: process.env.KISMET_SERVER_KEY!,
origin: 'https://staging.example.com',
}),
sessionSecret: process.env.KISMET_SESSION_SECRET!,
allowedOrigins: ['https://staging.example.com'],
googleReturnPath: '/my/auth/return',
production: true, // Secure, host-only cookies, including on HTTPS staging.
});

Mount these Next.js App Router handlers alongside your existing email-code handlers:

Route file Export
app/api/kismet/auth/csrf/route.ts export const GET = guestAuth.csrf;
app/api/kismet/auth/google/start/route.ts export const POST = guestAuth.googleStart;
app/api/kismet/auth/google/complete/route.ts export const POST = guestAuth.googleComplete;
app/api/kismet/auth/session/route.ts export const GET = guestAuth.session;
app/api/kismet/auth/refresh/route.ts export const POST = guestAuth.refresh;
app/api/kismet/auth/logout/route.ts export const POST = guestAuth.logout;

Each route imports guestAuth from your server module. The SDK generates state and S256 PKCE, seals the verifier in a host-only HttpOnly cookie, validates the return against that browser’s state and identity anchor, and stores the guest session in an encrypted HttpOnly cookie. Access tokens, refresh tokens, and server keys never belong in browser storage or component props.

Open sign-in without losing the current screen

Section titled “Open sign-in without losing the current screen”

Use the browser client from @kismet-tech/sdk/react. Open the popup synchronously from a user click, before awaiting the start request, to avoid popup blocking. Disable repeated clicks while a flow is active; starting another flow replaces the first flow’s cookie.

import { createKismetGuestAuthBrowserClient } from '@kismet-tech/sdk/react';
const auth = createKismetGuestAuthBrowserClient();
// Invoke from the sign-in button. Keep offer/drawer state in this window.
async function signInWithGoogle(onSignedIn: () => void) {
const popup = window.open('about:blank', '_blank', 'popup,width=520,height=720');
if (!popup) throw new Error('Allow popups to sign in with Google.');
const origin = window.location.origin;
let timer: ReturnType<typeof setInterval>;
const cleanup = () => {
window.removeEventListener('message', receive);
clearInterval(timer);
};
const receive = async (event: MessageEvent) => {
if (event.origin !== origin || event.source !== popup ||
event.data?.type !== 'kismet:guest-signed-in') return;
cleanup();
// The message is a signal, never identity or authorization evidence.
const session = await auth.session();
if (session.signedIn) onSignedIn();
};
window.addEventListener('message', receive);
timer = setInterval(() => { if (popup.closed) cleanup(); }, 500);
try {
const { authorizationUrl } = await auth.startGoogle();
popup.location.replace(authorizationUrl);
} catch (error) {
cleanup();
popup.close();
throw error;
}
}

Handle errors from auth.session() in your application’s normal error UI. The onSignedIn callback should update the signed-in display, not claim an offer or submit a preference. Keep any open drawer or unsubmitted form in the original window.

Build /my/auth/return as a small, publicly reachable page on the same origin. Do not put an authentication redirect in front of it. Do not load third-party analytics on this page. Run the completion exactly once, including under React development effect replay.

Kismet returns kismet_auth_code and state in the URL fragment. Remove the fragment immediately, before awaiting network activity. The code expires after 60 seconds and can be used once. Neither the code nor the fragment belongs in logs, telemetry, or postMessage.

// Run once on the return page. Show its error message locally if it rejects.
async function finishGoogleReturn() {
const params = new URLSearchParams(window.location.hash.slice(1));
window.history.replaceState(null, '', window.location.pathname + window.location.search);
for (const name of ['state', 'kismet_auth_code', 'kismet_auth_error']) {
if (params.getAll(name).length > 1) throw new Error('Restart Google sign-in.');
}
if (params.has('kismet_auth_error')) {
throw new Error('Sign-in did not finish. Close this window and try again.');
}
const code = params.get('kismet_auth_code') ?? '';
const state = params.get('state') ?? '';
if (!/^[\w-]{43}$/.test(code) || !/^[\w-]{43}$/.test(state)) {
throw new Error('Restart Google sign-in.');
}
await auth.completeGoogle({ code, state });
if (window.opener) {
window.opener.postMessage({ type: 'kismet:guest-signed-in' }, window.location.origin);
window.close();
}
// Otherwise show a signed-in confirmation and a link back to your account page.
}

Cancellation and provider errors do not establish a session. Show a retry path without treating the guest as signed in. A full-page redirect can use the same return handler, but your app must explicitly preserve any unsubmitted UI state; the SDK does not restore it automatically.

  • Forward /api/kismet/* to your application, preserving Cookie, Origin, X-Kismet-CSRF, request method, and body. Forward every Set-Cookie header back to the browser, without combining or stripping them.
  • Forward the return page and account routes to that same application. Do not strip cookies on /my or /my/auth/return.
  • Keep auth requests and responses out of CDN caches (Cache-Control: no-store). Never cache a response containing Set-Cookie.
  • The request URL seen by the handler must have the authorized public origin. If a trusted proxy adapter reconstructs it, use only trusted forwarding metadata, not arbitrary client headers.
  • Preserve the opener relationship for the popup flow. Test your Cross-Origin-Opener-Policy; a policy that isolates the Google popup requires a different completion signal or full-page UX.

The server client exposes startGoogle(input, { csrfToken }) and redeemGoogle(input, { csrfToken }). They wrap:

  • POST start hosted Google sign-in: send a server-selected returnPath, canonical kidSid, random state, and S256 codeChallenge. It returns authorizationUrl and expiresAt.
  • POST complete hosted Google sign-in: send the return code, original codeVerifier, and the same kidSid. It returns the canonical guest and server-only session tokens.

Both requests require the server bearer credential, exact authorized Origin, and X-Kismet-CSRF. Use the supported BFF handlers where possible. A custom BFF must enforce same-origin CSRF, seal the verifier in a secure HttpOnly cookie, validate state and cookie expiry, match the current identity anchor, and keep session tokens on the server. Never let a browser choose the return origin or submit a Google identity as proof.

Result What to check
401 The server credential is present, active, and belongs to the intended installation.
403 ORIGIN_NOT_AUTHORIZED The exact staging hostname is registered to the correct collection, the installation is TEST, and its allowed origins contain the same HTTPS origin used by the browser and BFF.
Other 403 Check guest_auth.write and the BFF’s same-origin CSRF cookie/header. At completion, check that the Google email is a registered TEST identity.
409 CANONICAL_SESSION_REQUIRED Kismet’s existing identity cookie is available and the proxy preserves it.
Invalid or expired return Start a new flow. Check that state, flow cookie, and identity anchor have not changed; do not retry the consumed code.
503 Google sign-in or its shared callback service is unavailable. Keep email-code sign-in or a retry option visible.
Popup closes but account stays signed out Verify opener policy, exact message origin/source, session-cookie forwarding, and a fresh auth.session() request.

Keep guest preferences and membership separate from authentication. Google proves the guest’s account; Kismet’s preference workflow records their explicit choices.