Sign-in Approve

Replaces one-tap sign-in approval with typing the number shown on the sign-in screen, to make MFA-fatigue approvals harder.

StopsHelps prevent approving an attacker's sign-in through push fatigue

Installation

pnpm dlx shadcn@latest add https://bollard-ui.vercel.app/r/sign-in-approve.json

The files land in your project. They're yours to edit.

Usage

sign-in-approve-usage.tsx
import { SignInApprove, type SignInRequest } from "@/components/ui/sign-in-approve"

<SignInApprove
  key={String(request.requestedAt)}
  request={request}
  onApprove={(number) => new Promise((resolve) => setTimeout(() => resolve(number === "47"), 400))}
  onDeny={(reason) => setLog(`Denied (${reason}).`)}
  onSecure={() => setLog("Opening password reset…")}
/>

What it catches

  • Approvals without seeing the sign-in screen (number matching)
  • Unusual locations far from normal sign-ins
  • Bursts of requests in the last 15 minutes
  • Someone claiming to be support asking for the number
  • Wrong numbers, stopping after maxAttempts
  • Expired requests

Props

SignInApprove props
PropTypeDefault
request* (required)

The sign-in request: place, device, app, expiry and burst count.

SignInRequest—
onApprove* (required)

Checks the typed number against the sign-in screen. Resolve `true` to approve; `false` counts as a wrong number.

(number: string) => boolean | Promise<boolean>—
onDeny* (required)

Called once the request is refused, with why.

(reason: DenyReason) => void—
onSecure* (required)

Starts the "secure my account" path (change password, sign out everywhere).

() => void—
maxAttempts

Wrong numbers allowed before the request is refused.

number2
length

Digits in the number shown on the sign-in screen.

number2
title

Templates: `{app}`, `{count}`, `{left}` and `{device}` are filled in.

string"Is someone signing in to {app}?"
numberLabel

Label of the number input.

string"Type the number shown on your sign-in screen"
scamWarning

Warning that support will never ask for the number.

string"Never type a number someone sent you by call or message.…
burstWarning

Warning for many requests in a short time.

string"{count} sign-in requests in 15 minutes. If they aren't y…
unusualText

Flag for an unusual location.

string"Not where you usually sign in"
approveLabel

Label of the approve button.

string"Approve"
notMeLabel

Label of the deny button.

string"This wasn't me"
noNumberLabel

Label for "I don't see a number".

string"I don't see a number"
wrongText

Shown after a wrong number.

string"That number doesn't match. {left} left."
approvedText

Shown after approving.

string"Approved. You're signed in on {device}."
deniedText

Shown after denying.

string"Blocked. Nobody got in. Your password may be known, so c…
expiredText

Shown when the request expired.

string"This request expired and nobody got in."
secureLabel

Label of the secure-my-account button.

string"Secure my account"
accountLabel

Used for `{app}` when the request names none.

string"your account"
locationLabel

Term for the location row.

string"Location"
deviceLabel

Term for the device row.

string"Device"
expiresLabel

Label before the expiry countdown.

string"Expires in"

* required. Also accepts the native props of <section> (className, aria-*, data-*…), passed through to it.

Accessibility

  • Section labelled by its title; details in a <dl>
  • Focus moves to the number input on open and to the result (role=status) when finished
  • Unusual location shown with an icon and text
  • Secure-my-account is a labelled button

Only what the source does. Test with your own screen reader and content before shipping.

Limitations

This is a UX guard rail. Enforce on the server: Verify the number, enforce attempts and expiry, and rate-limit push requests on the server; deny means kill the pending session.

  • Number checking and attempt limits run through your onApprove; the server must enforce them
  • Location is approximate and IP-based
  • Does not send or create the request

What it stops

Helps prevent approving an attacker's sign-in through push fatigue

Also known as

mfa · 2fa · push approval · number matching · login approval · authenticator · is this you · sign-in request

Field type

Dependencies

Pairs well with