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
Field | Type | Description |
---|---|---|
user_id | string | Unique identifier for the user attempting to sign in. Correlate this to the auth.users table. |
valid | boolean | Whether 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.
Field | Type | Description |
---|---|---|
decision | string | The 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. |
message | string | The message to show the user if the decision was reject . |
should_logout_user | boolean | Whether 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.
_10create 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:
_62create function public.hook_password_verification_attempt(event jsonb)_62returns jsonb_62language plpgsql_62as $$_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_62grant execute_62 on function public.hook_password_verification_attempt_62 to supabase_auth_admin;_62_62grant all_62 on table public.password_failed_verification_attempts_62 to supabase_auth_admin;_62_62revoke execute_62 on function public.hook_password_verification_attempt_62 from authenticated, anon, public;_62_62revoke all_62 on table public.password_failed_verification_attempts_62 from authenticated, anon, public;