Prerequisites
Before using this integration, you need:
- An Autheona account: Sign up
- An Autheona project. Use a Sandbox project for testing and a Production project for live applications. See the Quick Start Guide.
- An Autheona access token for the project. See the Access Tokens documentation.
- A Supabase project with Supabase Auth enabled.
- 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:
- If Autheona returns
block, signup is denied. - If Autheona returns
alloworignore, signup continues. - If Autheona is temporarily unavailable, signup continues.
- If the Autheona request times out, signup continues.
- If the Autheona API returns an HTTP error, signup continues.
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:
allow: Allow the request to continue.block: Block the request.ignore: Do not block the request based on the returned policy decision.
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| FDo 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 status | Meaning |
|---|---|
200 | Successful request with intelligence results |
400 | Invalid request |
401 | Invalid or missing authentication |
500 | Internal 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:
- Retry only transient failures.
- Do not blindly retry authentication failures such as HTTP
401. - Avoid excessive retries during user registration.
- Consider Autheona rate limits and API quotas.
- Use exponential backoff where appropriate.
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:
- Keep the Autheona access token in Supabase Edge Function secrets.
- Never hardcode the Autheona access token in Edge Function source code.
- Keep the Supabase Auth Hook secret in Supabase secrets.
- Never expose the Supabase Auth Hook secret to the browser.
- Use a Production Autheona project for live traffic.
- Configure production policies in the Autheona Policy Editor.
- Test the
allow,block, andignoreoutcomes. - Test Autheona API failures and timeouts.
- Decide explicitly whether your integration should fail open or fail closed.
- Keep the HTTP timeout appropriate for your registration UX.
- Avoid unnecessary retries.
- Monitor Autheona API usage and rate limits.
- Verify Supabase webhook signatures before processing requests.
- Revoke and replace an Autheona access token immediately if it is exposed.
- Do not expose Edge Function secrets in client-side code.
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]