Integrate Autheona with Supabase Auth

Step-by-step guide to integrate Autheona validation into Supabase Auth signup flows using the Before User Created hook for secure user registration.

Prerequisites

Before using this integration, you need:

  1. An Autheona account: Sign up
  2. An Autheona project. Use a Sandbox project for testing and a Production project for live applications. See the Quick Start Guide.
  3. An Autheona access token for the project. See the Access Tokens documentation.
  4. A Supabase project with Supabase Auth enabled.
  5. Supabase Edge Functions enabled for your project.

Autheona access tokens provide access to the Autheona API. Store the token securely using Supabase Edge Function secrets. Never hardcode an access token in your Edge Function source code.

Important

Decide where you want the Autheona check to occur:

  • Use Before User Created when the primary goal is to prevent an email address from creating a Supabase Auth user.
  • Supabase's Custom Access Token hook can add information to tokens, but it is not a registration denial mechanism.

For signup protection, the Before User Created hook is the recommended integration point.

Before User Created

Use a Before User Created hook when you want to evaluate an email address before Supabase Auth creates the user. This is generally the appropriate integration point when your primary goal is preventing disposable, suspicious, or otherwise disallowed email addresses from registering.

Supabase sends the incoming user object to the hook before the user is created. If the hook returns an error response, the signup is denied and the user is not created.

Edge Function Code

Create a new Supabase Edge Function:

supabase functions new before-user-created

Replace the contents of:

supabase/functions/before-user-created/index.ts

with:

import { Webhook } from "https://esm.sh/standardwebhooks@1.0.0";

const json = (body: unknown, status = 200) =>
  new Response(JSON.stringify(body), {
    status,
    headers: {
      "Content-Type": "application/json",
    },
  });

Deno.serve(async (req) => {
  const payload = await req.text();

  const secret = Deno.env
    .get("BEFORE_USER_CREATED_HOOK_SECRET")
    ?.replace("v1,whsec_", "");

  if (!secret) {
    console.log("Supabase Auth hook secret is not configured");
    return json(
      {
        error: {
          message: "Hook configuration error.",
          http_code: 500,
        },
      },
      500,
    );
  }

  try {
    const headers = Object.fromEntries(req.headers);
    const webhook = new Webhook(secret);

    const { user } = webhook.verify(payload, headers);

    const email = user?.email;

    if (!email) {
      return json(
        {
          error: {
            message: "An email address is required.",
            http_code: 400,
          },
        },
        400,
      );
    }

    const apiKey = Deno.env.get("AUTHEONA_API_KEY");

    if (!apiKey) {
      console.log("Autheona access token is not configured");

      return json({});
    }

    const normalizedEmail = email.toLowerCase().trim();

    const controller = new AbortController();
    const timeout = setTimeout(() => controller.abort(), 3000);

    try {
      const response = await fetch("https://api.autheona.com/v1/intelligence", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "x-api-key": apiKey,
        },
        body: JSON.stringify({
          email_address: normalizedEmail,
        }),
        signal: controller.signal,
      });

      if (!response.ok) {
        console.log(`Autheona API error: ${response.status}`);

        return json({});
      }

      const result = await response.json();

      if (result.action === "block") {
        return json(
          {
            error: {
              message: "Email address is not allowed.",
              http_code: 400,
            },
          },
          400,
        );
      }

      console.log(
        `Autheona returned action "${result.action}" for ${normalizedEmail}`,
      );

      return json({});
    } finally {
      clearTimeout(timeout);
    }
  } catch (error) {
    console.log(
      `Autheona validation error: ${
        error instanceof Error ? error.message : String(error)
      }`,
    );

    return json({});
  }
});

Failure behavior

The example above uses a fail-open strategy:

This behavior is intentional. It prevents an Autheona outage from automatically becoming an outage of your registration system. The webhook signature is still verified before the request is processed. An invalid or malformed Supabase Auth hook request is rejected. If your security requirements require fail-closed behavior, change the Autheona error-handling branches so that signup is denied when Autheona cannot be reached or returns an unexpected error. Supabase requires the Before User Created hook to return an empty successful response to allow signup. Returning an error object with a 4xx status blocks user creation.

Configuration

Create the Edge Function

Create the function using the Supabase CLI:

supabase functions new before-user-created

Paste the Before User Created code above into:

supabase/functions/before-user-created/index.ts

The function receives the Supabase Auth hook request, verifies the webhook signature, extracts the email address, sends it to Autheona, and returns the appropriate response to Supabase Auth.

Add the Autheona Secret

Supabase Edge Functions support environment variables for storing sensitive credentials. Create the following secrets:

AUTHEONA_API_KEY
BEFORE_USER_CREATED_HOOK_SECRET

Set the Autheona access token:

supabase secrets set AUTHEONA_API_KEY=your_autheona_access_token

The second secret is generated by Supabase when you configure the HTTP Auth Hook. For local development, you can use:

supabase/functions/.env

Example:

AUTHEONA_API_KEY=your_autheona_access_token
BEFORE_USER_CREATED_HOOK_SECRET=v1,whsec_your_hook_secret

Do not commit this file to Git. Do not put the Autheona access token directly into the TypeScript source code.

Deploy the Edge Function

Deploy the function with JWT verification disabled:

supabase functions deploy before-user-created --no-verify-jwt

The function is called by Supabase Auth as an authentication hook rather than by an authenticated application user. The Supabase Auth hook uses signed webhook headers for request verification.

Configure the Supabase Auth Hook

Open your Supabase project dashboard. Go to:

Authentication -> Hooks

Create a new hook and select:

Before User Created

Choose the HTTP implementation. Set the Edge Function URL:

https://<project-ref>.supabase.co/functions/v1/before-user-created

Generate a new hook secret. Copy the generated secret and store it as:

BEFORE_USER_CREATED_HOOK_SECRET

Save the hook configuration. Supabase sends the webhook signature using the Standard Webhooks specification. The Edge Function verifies this signature before processing the request.

Testing With a Custom Policy

For testing and development, you can provide a custom_policy in the Autheona request. For example:

const customPolicy = {
  parent_rules: [
    {
      action: "block",
      field: "domain_disposable",
      operator: "==",
      target: {
        bool: true,
      },
    },
  ],
  stop_on_block: true,
  version: "1.0",
};

const response = await fetch("https://api.autheona.com/v1/intelligence", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-api-key": apiKey,
  },
  body: JSON.stringify({
    email_address: normalizedEmail,
    custom_policy: customPolicy,
  }),
});

Warning

custom_policy is intended for testing and development. For production use, configure your policy in the Autheona Policy Editor instead of sending a custom policy with every request.

See the Autheona Policy Editor documentation.

Understanding Autheona Actions

The User Trust API returns an action representing the policy result. The commonly used actions are:

Your application should decide what each action means for its own workflow. For this Supabase integration:

flowchart TD
  A[Supabase Signup] --> B[Before User Created Hook]
  B --> C[Autheona User Trust API]
  C --> D{Policy Decision}
  D -->|block| E[Signup denied]
  D -->|allow| F[Create Supabase user]
  D -->|ignore| F

Do not automatically interpret ignore as "manual review required" unless your application actually implements a review workflow.

Supabase Request Data

The Before User Created hook receives a payload containing the user object and request metadata. The email address is available at:

user.email

The request metadata includes information such as:

metadata.ip_address
metadata.uuid
metadata.time
metadata.name

For example:

{
  "metadata": {
    "uuid": "8b34dcdd-9df1-4c10-850a-b3277c653040",
    "time": "2025-04-29T13:13:24.755552-07:00",
    "name": "before-user-created",
    "ip_address": "127.0.0.1"
  },
  "user": {
    "email": "user@example.com"
  }
}

The current integration uses only the email address. If your application needs additional signals, you can extend the Autheona request to include the relevant information supported by your Autheona project and policy configuration.

API Errors and Rate Limits

The Autheona API uses the following HTTP response categories:

HTTP statusMeaning
200Successful request with intelligence results
400Invalid request
401Invalid or missing authentication
500Internal server error

Autheona currently documents a rate limit of 10 requests per second per access token. Production integrations should avoid unnecessary retries and should handle transient rate-limit or server errors appropriately. See the Autheona API Reference.

Timeout and Retry Considerations

The example uses a 3-second request timeout to prevent the external API request from waiting indefinitely. The timeout is an example value. Adjust it based on your application's authentication and registration requirements. If you implement retries:

For authentication flows, a short timeout with a deliberate fail-open or fail-closed policy is preferable to an unrestricted retry loop.

Choosing the Correct Supabase Auth Hook

Use Before User Created when your primary requirement is:

Do not allow this email address to create a Supabase Auth user.

The Before User Created hook runs immediately before Supabase creates the user, making it the appropriate integration point for signup protection. Supabase also provides a Custom Access Token hook:

Authentication -> Hooks -> Custom Access Token

The Custom Access Token hook runs before a JWT is issued and is intended for adding or modifying token claims. It should not be used as the primary mechanism for blocking signup.

If you need to evaluate users during authentication after they already exist, implement that decision in your application's authentication or authorization layer rather than treating the Custom Access Token hook as a signup denial mechanism.

Production Recommendations

Before deploying this integration to production:

Minimal Integration Architecture

For registration protection:

flowchart TD
  A[User] --> B[Supabase Auth]
  B --> C[Before User Created Hook]
  C --> D[Supabase Edge Function]
  D --> E[Autheona User Trust API]
  E --> F{Policy Decision}
  F -->|allow| G[Create Supabase user]
  F -->|ignore| H[Continue signup]
  F -->|block| I[Deny signup]

The complete request flow is:

flowchart TD
  A[User] --> B[Supabase Auth]
  B --> C[Before User Created]
  C --> D[Supabase Edge Function]
  D --> E[Autheona User Trust API]
  E --> F{Policy Decision}

  F -->|allow| G[Create user]
  F -->|ignore| G
  F -->|block| H[Deny signup]

Support and Documentation