URL: /products/auth-protection

---
title: Auth Protection
description: Prevent fake signups and account takeover with a blended IP, email, and browser-reputation verdict plus multi-account correlation on every signup and login.
---

One server-to-server call returns a blended IP, email, and browser-reputation verdict — plus multi-account correlation — for any signup or login. Drop the beacon on your auth pages, resolve the visitor from your backend, then flag, gate, or block on the verdict.

Your auth provider's `user.created` webhook carries no IP, no user agent, and no browser context, so by the time a fake account or a takeover attempt reaches your backend the signals that would have caught it are already gone. Disposable emails, datacenter IPs, and one person spinning up dozens of accounts all look identical at the point you can actually block them. Auth Protection scores the visitor at the edge *before* the form is submitted, so the verdict is ready the moment your server creates the account.

<Warning>
  Auth Protection is in **early access**. The visitor-resolve endpoint, `POST /v1/sessions/resolve`, is **live now** — it returns the edge verdict, session continuity, and multi-account correlation, and binds an account id you pass. The higher-level `POST /v1/auth/check` pre-flight that wraps the same signals into a single allow/review/block call is **coming soon**. Build against `/v1/sessions/resolve` today; this is not yet a finished self-serve product.
</Warning>

## When to use it

Reach for Auth Protection wherever an account is created or a session is established and you want to catch abuse at the point you can still block it — signup, login, password reset, or a step-up flow. A datacenter or residential-proxy IP on a consumer signup, a disposable email, or twenty accounts sharing one browser fingerprint are all invisible per-request but obvious here.

It complements the [beacon](/quickstart) and [server reporting](/guides/server-reporting), which score full requests into your dashboard. Auth Protection is the synchronous, secret-key lookup you call from your backend at the auth decision point — and because the verdict only ever crosses back to a secret-key call, it is never exposed to the browser and cannot be tampered with.

## How it works

<Steps>
  <Step title="Drop the beacon on your auth pages">
    Add the FormShield beacon to your signup and login views. It sets a first-party `_fs` cookie and scores the visitor at the edge — IP reputation, email signals, browser fingerprint, automation tells — before the form is ever submitted, so the verdict is ready when your server needs it.
  </Step>

  <Step title="Resolve the visitor from your backend">
    On signup or login, read the `_fs` cookie server-side and call `POST /v1/sessions/resolve` with a secret key. It returns the edge verdict, session continuity (fresh, history-less sessions are themselves a tell), and multi-account correlation, then binds the account id you pass so future lookups stay linked even after the cookie is cleared.
  </Step>

  <Step title="Flag, gate, or block on the verdict">
    Use the returned `decision` and the count of accounts sharing a fingerprint or IP to flag a signup for review, step up verification, or reject it. Because the verdict only ever returns to a secret-key backend call, it is never exposed to the browser and cannot be tampered with.
  </Step>
</Steps>

## Quickstart

Read the `_fs` cookie server-side and send it, along with the visitor's IP and the email and account id you're creating, to `POST /v1/sessions/resolve` with your **secret** key (`sessions` scope). The beacon on your auth page must have already scored the visitor for a verdict to be ready.

```bash
curl -X POST https://api.formshield.dev/v1/sessions/resolve \
  -H "Authorization: Bearer fs_live_YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "session_id": "<_fs cookie read server-side>",
    "ip": "203.0.113.42",
    "email": "user@example.com",
    "external_user_id": "user_2abc123",
    "observation_type": "auth.signup"
  }'
```

The response carries the blended `verdict`, session `continuity`, multi-account `correlation`, and `bound: true` once the account id is linked:

```json
{
  "version": "1",
  "data": {
    "bound": true,
    "verdict": { "score": 0.82, "decision": "review", "reasons": ["datacenter_ip", "shared_fingerprint"] },
    "continuity": { "found": true, "first_seen": 1733500000000, "last_seen": 1733500120000, "age_seconds": 120, "observation_count": 3, "is_fresh": false },
    "correlation": { "accounts_sharing_session": 1, "accounts_sharing_fingerprint": 7, "accounts_sharing_ip": 4, "linked_emails": 6 }
  },
  "error": null,
  "metadata": {
    "request_id": "req_a1b2c3d4e5f6",
    "processing_time_ms": 38
  }
}
```

This response says: the visitor came from a datacenter IP and shares a browser fingerprint with six other accounts, so the blended verdict is `review`. Seven accounts share this fingerprint and four share the IP — the core fake-signup tell. Branch on `verdict.decision` and the `correlation` counts to decide whether to allow, step up, or reject the account.

<Note>
  The verdict only ever crosses back on this secret-key server call — it is never sent to the browser, so it cannot be inspected or tampered with by a client. Read the `_fs` cookie on your server from the incoming request; never trust a verdict computed or relayed by client code.
</Note>

## The signals

Each call fuses four independent signals. No single tell decides the outcome — the blended verdict and the correlation counts are read together.

<CardGroup cols={2}>
  <Card title="Blended reputation verdict" icon="shield">
    A single decision — `allow`, `review`, or `block` — fused from IP class (datacenter, VPN, proxy, Tor, residential proxy), email reputation, and browser fingerprint, with the `reasons` that drove it. The verdict crosses back only on a secret-key call, never to the browser.
  </Card>
  <Card title="Multi-account correlation" icon="users">
    Counts of distinct identities that share this visitor's fingerprint, IP, or session. One person registering twenty accounts is invisible per-request but obvious here. This is the core fake-signup tell.
  </Card>
  <Card title="Session continuity" icon="clock">
    First-seen, last-seen, age, and an `is_fresh` flag. A brand-new, history-less session arriving straight at your signup form is weak but useful corroborating evidence of automation.
  </Card>
  <Card title="Durable identity binding" icon="fingerprint">
    Pass `external_user_id` (e.g. a Clerk or Auth0 user id) and FormShield binds it to the visitor and persists the link in ClickHouse. Correlation survives a cleared cookie because the durable join is fingerprint plus IP, not the cookie alone.
  </Card>
</CardGroup>

## The fields

The `data` payload groups the verdict, the continuity history, and the correlation counts. Branch on `verdict.decision` and the correlation counts together.

<ResponseField name="bound" type="boolean">
  Whether the `external_user_id` you passed was bound to this visitor. Once `true`, future lookups for the same identity stay linked through fingerprint and IP even after the `_fs` cookie is cleared.
</ResponseField>

<ResponseField name="verdict" type="object">
  The blended decision: `score` (`0.0`–`1.0`), `decision` (`allow` \| `review` \| `block`), and `reasons` — the tells that fired, e.g. `datacenter_ip`, `shared_fingerprint`. Fused from IP class, email reputation, and browser fingerprint.
</ResponseField>

<ResponseField name="continuity" type="object">
  Session history: `found`, `first_seen`, `last_seen`, `age_seconds`, `observation_count`, and `is_fresh`. A fresh, history-less session arriving straight at signup is weak but useful corroborating evidence of automation.
</ResponseField>

<ResponseField name="correlation" type="object">
  Multi-account linkage: `accounts_sharing_session`, `accounts_sharing_fingerprint`, `accounts_sharing_ip`, and `linked_emails`. High counts on fingerprint or IP are the core fake-signup tell — one person spinning up many accounts.
</ResponseField>

<ResponseField name="request_id" type="string">
  Identifier for the stored observation, prefixed `req_`. Correlates this resolve with its observation in the dashboard.
</ResponseField>

## Endpoints

<ParamField path="POST /v1/sessions/resolve" type="endpoint">
  **Live.** Read the `_fs` cookie server-side and resolve the visitor: returns the blended verdict, session continuity, and multi-account correlation, and binds the `external_user_id` you pass. Secret key, `sessions` scope. A raw session resolve is 2 credits.
</ParamField>

<ParamField path="POST /v1/auth/check" type="endpoint">
  **Coming soon.** A higher-level pre-flight that wraps the same IP, email, browser, continuity, and correlation signals into a single `allow` / `review` / `block` call for a signup or login decision. Until it ships, call `/v1/sessions/resolve` and branch on its verdict.
</ParamField>

## Common questions

**How do I prevent fake signups with FormShield today?**

Add the beacon to your signup page, then call `POST /v1/sessions/resolve` from your backend (secret key, `sessions` scope) when an account is created. The response gives you a blended IP/email/browser verdict plus a count of other accounts sharing the same fingerprint or IP, so you can flag, step up, or reject. This endpoint is live now. A higher-level `POST /v1/auth/check` pre-flight that wraps the same signals into a single allow/review/block call is coming soon.

**Do I have to store risk data in my own database?**

No. You pass your account id as `external_user_id` at signup and FormShield keeps the binding and history. To render risk later, call `GET /v1/users/{external_user_id}` for that identity's current verdict, first/last seen, and multi-account linkage. The customer stores nothing; FormShield owns the identity-risk state.

**What does this cost in credits?**

Credits are weighted per operation: a signup check is 5 credits, a login check is 1 credit, and a raw session resolve is 2 credits. You meter and bill in credits rather than raw request counts, so heavier auth decisions cost proportionally more than a simple login.

## Next steps

<CardGroup cols={2}>
  <Card title="IP Intelligence" icon="globe" href="/products/ip-intelligence">
    The IP reputation lookup behind the verdict — datacenter, VPN, proxy, Tor, and residential-proxy tags.
  </Card>
  <Card title="Email Intelligence" icon="envelope" href="/products/email-intelligence">
    Disposable-domain, deliverability, and domain-age checks that feed the email signal.
  </Card>
  <Card title="Server reporting" icon="server" href="/guides/server-reporting">
    Capture full requests — UA and IP together — including crawlers and AI bots.
  </Card>
  <Card title="API Reference" icon="code" href="/api-reference/introduction">
    The response envelope, authentication, and error codes.
  </Card>
</CardGroup>
