Features · Sign-in & email

Google sign-in, minus Google Cloud.

“Continue with Google” and email sign-in for the people who use your app — no Google Cloud project, no OAuth client, no keys. Its sign-up mail goes out in your app’s name, from your own domain once it verifies.

Read the docsAgent setup

What your agent calls

  • appAuthConfigure
  • appAuthStatus
  • appAuthEmailConnect
  • appAuthEmailStatus
claude · ~/corner-bakeryexample

Let staff sign in with Google

Update(app/login/page.tsx)

1 line · Google button → TOFU_SIGN_IN_URL

Write(app/auth/confirm/route.ts)

new route · verifyOtp for the email link

tofu · appAuthConfigure confirmed: true

configured · Google sign-in, and email sign-in as well

How Google sign-in works

Tofu talks to Google, so your app does not have to.

Tofu’s broker at oauth.trytofu.ai owns one Google client and one permanent callback. Your app sends the browser there; the broker signs the person into your app’s own auth service — the Supabase project behind its managed database.

  1. Your app“Continue with Google” sends the browser to Tofu’s broker.
  2. oauth.trytofu.aiChecks the return address is one of your app’s own, and nothing else.
  3. GoogleThe person signs in, on the consent screen of Tofu’s client.
  4. Your app’s auth serviceThe account is created or found there, and the browser comes back signed in.

Because the broker is the one party that talks to Google, there is no Google Cloud project, OAuth client or key on your side — and no per-app console step for anyone.

appAuthConfigure writes Google and email sign-in into your app’s auth service in one confirmed step, then reads the configuration back before it says configured.

Deploying with a database does not turn sign-in on by itself: it is one explicit call after the deploy, and safe to repeat.

app/login/page.tsxthe whole change
// Continue with Google — Tofu brokers the sign-in.
window.location.href = process.env.NEXT_PUBLIC_TOFU_SIGN_IN_URL + "?return=" +
  encodeURIComponent(window.location.href);
An app that renders on the server adds &flow=pkce and redeems the sign-in in a small route of its own. Your agent writes both.
Sign-up email

Sign-up email, in your app’s name.

Email sign-in is switched on in the same step, so an account always has a way back in if a third-party sign-in is ever unavailable.

Your app’s own auth service sends its confirmation, magic-link, password-reset, invitation, address-change and reauthentication mail through Tofu’s email setup, with your app’s name as the sender. No SMTP password, and no email provider to sign up for.

Every message carries a one-time code as well as a link, because mail scanners that open links first would otherwise spend the token before your user could.

WhenSent from
Until your domain verifiesnoreply@notify.trytofu.app
Once it verifiesnoreply@send.cornerbakery.org
From your domain

Six records, none at your apex.

Once your domain is connected, appAuthEmailConnect creates a sending identity for send.cornerbakery.org and publishes its records — all under send., so the mail your domain already receives is untouched. Remove them, and the domain is exactly as it was.

TypeName, in cornerbakery.orgValue
CNAME‹token 1›._domainkey.send‹token 1›.dkim.amazonses.com
CNAME‹token 2›._domainkey.send‹token 2›.dkim.amazonses.com
CNAME‹token 3›._domainkey.send‹token 3›.dkim.amazonses.com
MX 10bounce.sendfeedback-smtp.‹region›.amazonses.com
TXTbounce.sendv=spf1 include:amazonses.com ~all
TXT_dmarc.sendv=DMARC1; p=none; rua=mailto:…

With the Cloudflare approval your domain connection already holds, Tofu writes them itself — no second approval. Anywhere else, it lists them to add, and asks the email provider to check.

Until the email provider reports the domain verified, mail keeps going out from notify.trytofu.app — sign-up never stops while you wait.

The one route your app owns

A link a scanner cannot spend.

Each message links to /auth/confirm on your app’s own address, and opening that link spends nothing: it shows one button, and the token is spent only when the person presses it and your app calls verifyOtp — so a scanner that opens the link first leaves it working.

A new app built through the /tofu skill gets this route; your agent writes it. An app deployed without it still signs people in with the code in each message.

app/auth/confirm/route.tsnext.js
// app/auth/confirm/route.ts — Next.js (App Router)
import { createClient } from '@/lib/supabase/server';
import { NextResponse } from 'next/server';

// Opening the link spends nothing: mail scanners open links too. It shows one button, which sends once.
export async function GET(request) {
  const { searchParams } = new URL(request.url);
  const field = (name) => (searchParams.get(name) ?? '').replace(/[^\w-]/g, '');
  const form = `<form method="post" onsubmit="this.querySelector('button').disabled=true"><input type="hidden" name="token_hash" value="${field('token_hash')}"><input type="hidden" name="type" value="${field('type')}"><button>Continue</button></form>`;
  return new Response(`<!doctype html><meta name="viewport" content="width=device-width"><title>Confirm</title><p>Press Continue to finish.</p>${form}`, { headers: { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store' } });
}

// Only the person's press of that button redeems the one-time token.
export async function POST(request) {
  const { origin } = new URL(request.url);
  const form = await request.formData();
  const token_hash = String(form.get('token_hash') ?? '');
  const type = String(form.get('type') ?? '');
  if (token_hash && type) {
    const supabase = await createClient();
    const { error } = await supabase.auth.verifyOtp({ type, token_hash });
    if (!error) return NextResponse.redirect(`${origin}/`, 303);
  }
  return NextResponse.redirect(`${origin}/login?error=link`, 303);
}

Coming soon.

  • Your own name on Google’s sign-in window. Google shows the verified name of the client it signs in through, so people read “continue to Tofu”. Your own name there needs a Google client of your own, which is not built.

By design.

  • Only with a Tofu-managed database. Sign-in is written into the auth service behind your app’s managed database. An app on Clerk, Auth0 or a Supabase project of its own is left exactly as it is.

  • Google and email, nothing else. No GitHub or Apple sign-in for your app’s users.

  • Sign-in mail, not your app’s other mail. Tofu sends the sign-in and sign-up messages; receipts, notifications and anything else your app sends do not go through Tofu.

  • Your own sender needs your own domain. The domain has to be connected to the app first; until it verifies, mail comes from noreply@notify.trytofu.app.

  • The own-domain sender is the newest part. Its records, verification and sender switch have run end to end on Tofu’s own test project alone.

Paste one message. Online in 10 minutes.

Your app, its database, your domain and your sign-in — from the agent you already use.

Start free