auth

Work-email signups with Supabase Auth

Check Supabase Auth signups with isBusinessEmail from an Edge Function before calling signUp, and store the verdict in user metadata.

isBusinessEmail for Supabase Auth

Supabase Auth creates users from your client with supabase.auth.signUp. To check the address first, put a small server-side step in front of it: a Supabase Edge Function that calls isBusinessEmail with your secret key and only then signs the user up.

1. Add the secret

supabase secrets set IBE_API_KEY=ibe_live_…

2. The Edge Function

// supabase/functions/signup/index.ts
import { createClient } from 'npm:@supabase/supabase-js@2';

async function checkWorkEmail(email: string) {
  try {
    const res = await fetch('https://api.isbusinessemail.com/v1/check', {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${Deno.env.get('IBE_API_KEY')}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ email }),
      signal: AbortSignal.timeout(2500),
    });
    return res.ok ? await res.json() : { recommendation: 'allow', failOpen: true };
  } catch {
    return { recommendation: 'allow', failOpen: true };
  }
}

Deno.serve(async (req) => {
  const { email, password } = await req.json();
  const verdict = await checkWorkEmail(email);

  if (verdict.recommendation === 'block') {
    return Response.json(
      { error: 'work_email_required', did_you_mean: verdict.did_you_mean ?? null },
      { status: 422 },
    );
  }

  const supabase = createClient(Deno.env.get('SUPABASE_URL')!, Deno.env.get('SUPABASE_ANON_KEY')!);
  const { data, error } = await supabase.auth.signUp({
    email,
    password,
    options: {
      data: {
        email_category: verdict.category ?? null,
        microsoft_365: verdict.workspace?.microsoft_365?.detected ?? null,
        google_workspace: verdict.workspace?.google_workspace?.detected ?? null,
      },
    },
  });
  if (error) return Response.json({ error: error.message }, { status: 400 });
  return Response.json({ user: data.user?.id ?? null });
});

Call this function from your sign-up form instead of calling signUp directly. Add CORS headers if your frontend is on another origin. The verdict ends up in the user’s metadata, where your app can read it.

3. Make it hard to bypass

A client holding your project’s public key can still call signUp directly and skip the function. For a stronger guarantee, pick one:

  • Auth Hooks. Supabase can call your HTTP endpoint or a Postgres function during auth events. Check the Supabase Auth Hooks documentation for a hook that runs before a user is created, and call isBusinessEmail from there.
  • Server-created users. Turn off public sign-ups and create users from the function with the service-role key through the Auth admin API.
  • Gate features, not signups. Let anyone sign up, but check email_category before enabling team features or trials (enforced with Row Level Security or in your API).

Tips

  • Fail open on timeouts; mark those users for a background re-check.
  • Magic-link and OAuth sign-ins also create users. If you use them, classify after creation (for example from a database trigger that enqueues a job) as a backstop.
  • Test addresses like disposable@test.isbusinessemail.com work without a key (list).

Official docs: Supabase Auth. See also: Signup form guide.