Auth

Password Verification Hook


Your company wishes to increase security beyond the requirements of the default password implementation in order to fulfill security or compliance requirements. You plan to track the status of a password sign-in attempt and take action via an email or a restriction on logins where necessary.

As this hook runs on unauthenticated requests, malicious users can abuse the hook by calling it multiple times. Pay extra care when using the hook as you can unintentionally block legitimate users from accessing your application.

Check if a password is valid prior to taking any additional action to ensure the user is legitimate. Where possible, send an email or notification instead of blocking the user.

Inputs

FieldTypeDescription
user_idstringUnique identifier for the user attempting to sign in. Correlate this to the auth.users table.
validbooleanWhether the password verification attempt was valid.

_10
{
_10
"user_id": "3919cb6e-4215-4478-a960-6d3454326cec",
_10
"valid": true
_10
}

Outputs

Return these only if your hook processed the input without errors.

FieldTypeDescription
decisionstringThe decision on whether to allow authentication to move forward. Use reject to deny the verification attempt and log the user out of all active sessions. Use continue to use the default Supabase Auth behavior.
messagestringThe message to show the user if the decision was reject.
should_logout_userbooleanWhether to log out the user if a reject decision is issued. Has no effect when a continue decision is issued.

_10
{
_10
"decision": "reject",
_10
"message": "You have exceeded maximum number of password sign-in attempts.",
_10
"should_logout_user": "false"
_10
}

As part of new security measures within the company, users can only input an incorrect password every 10 seconds and not more than that. You want to write a hook to enforce this.

Create a table to record each user's last incorrect password verification attempt.


_10
create table public.password_failed_verification_attempts (
_10
user_id uuid not null,
_10
last_failed_at timestamp not null default now(),
_10
primary key (user_id)
_10
);

Create a hook to read and write information to this table. For example:


_62
create function public.hook_password_verification_attempt(event jsonb)
_62
returns jsonb
_62
language plpgsql
_62
as $$
_62
declare
_62
last_failed_at timestamp;
_62
begin
_62
if event->'valid' is true then
_62
-- password is valid, accept it
_62
return jsonb_build_object('decision', 'continue');
_62
end if;
_62
_62
select last_failed_at into last_failed_at
_62
from public.password_failed_verification_attempts
_62
where
_62
user_id = event->'user_id';
_62
_62
if last_failed_at is not null and now() - last_failed_at < interval '10 seconds' then
_62
-- last attempt was done too quickly
_62
return jsonb_build_object(
_62
'error', jsonb_build_object(
_62
'http_code', 429,
_62
'message', 'Please wait a moment before trying again.'
_62
)
_62
);
_62
end if;
_62
_62
-- record this failed attempt
_62
insert into public.password_failed_verification_attempts
_62
(
_62
user_id,
_62
last_failed_at
_62
)
_62
values
_62
(
_62
event->'user_id',
_62
now()
_62
)
_62
on conflict do update
_62
set last_failed_at = now();
_62
_62
-- finally let Supabase Auth do the default behavior for a failed attempt
_62
return jsonb_build_object('decision', 'continue');
_62
end;
_62
$$;
_62
_62
-- Assign appropriate permissions
_62
grant execute
_62
on function public.hook_password_verification_attempt
_62
to supabase_auth_admin;
_62
_62
grant all
_62
on table public.password_failed_verification_attempts
_62
to supabase_auth_admin;
_62
_62
revoke execute
_62
on function public.hook_password_verification_attempt
_62
from authenticated, anon, public;
_62
_62
revoke all
_62
on table public.password_failed_verification_attempts
_62
from authenticated, anon, public;