# Block account takeover on login and checkout flows

A fraud or security team in retail, financial services, or gaming sees credential stuffing, fake account creation, and card testing on its login, signup, and checkout endpoints. Those three paths are where an automated client gains the most per request, and where a refused customer costs the most. This page configures Bot Manager on the firewall for those paths only: it scores every request for automation, sends a flagged client on login and signup to a challenge a person can solve, and denies a flagged client on checkout, while WAF filters attacks on the same paths. The result is measured by the share of automated login and checkout attempts blocked, the false-positive rate on real users, and the attack traffic that still reaches the origin.

This use case does not cover general API abuse, which [Protect public APIs from abuse](/en/documentation/use-cases/secure-applications-and-networks/protect-public-apis-from-abuse/) covers.

## Prerequisites

- An application that serves the site through a connector and a workload. To create them, refer to [Applications quickstart](/en/documentation/platform/applications/quickstart/).
- A firewall bound to that workload's deployment, with WAF and Functions turned on in **Main Settings** › **Modules**. To bind it, refer to [Bind a firewall to a workload](/en/documentation/guides/application-security/firewall-and-waf/firewall-protect-your-domain/), and to turn on WAF, refer to [Set a firewall's main settings](/en/documentation/guides/application-security/firewall-and-waf/firewall-configure-main-settings/).
- Bot Manager enabled on the account, or Bot Manager Lite installed from Azion Marketplace. To install Bot Manager Lite, refer to [Install Bot Manager Lite](/en/documentation/guides/application-development/integrations/bot-manager-lite/).
- ALTCHA instanced on the same firewall, with its rule matching `/login`, `/signup`, `/az-request-verify`, and `/az-request-captcha`, created before the Bot Manager rules on this page. ALTCHA serves the challenge Bot Manager redirects a flagged client to. To set it up, follow [Protect a route with an ALTCHA challenge](/en/documentation/guides/application-development/functions-and-runtime/altcha/) on this firewall, with `/login` and `/signup` as the protected routes, and leave out its application rule: on this page, the `redirect` action of Bot Manager sends only flagged clients to the challenge.
- A personal token, for the API tabs. To create one, refer to [Personal tokens](/en/documentation/guides/platform/account-and-billing/personal-tokens/).
- The values of your site. This page uses `www.example.com` for the domain, `/login`, `/signup`, and `/checkout` for the three paths, and `auth` as the prefix of every object it creates. Replace each value with yours in every step.

---

## Required products

| The flows need                                                     | Which means                                                                                                 | Product          | Documented in                                                                                                                          |
| ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| Every request to login, signup, and checkout scored for automation | Bot Manager instances run by firewall rules scoped to the three paths                                       | Bot Manager      | [Run Bot Manager on selected paths](/en/documentation/guides/application-security/bots-and-network/run-bot-manager-on-selected-paths/) |
| A flagged client on login and signup challenged instead of refused | The `redirect` action, sending the client to the ALTCHA challenge                                           | Bot Manager      | [Arguments](/en/documentation/platform/firewall/bot-manager/arguments/#action)                                                         |
| A challenge that a person can solve                                | The ALTCHA function, run on the firewall before Bot Manager                                                 | Functions        | [Protect a route with an ALTCHA challenge](/en/documentation/guides/application-development/functions-and-runtime/altcha/)             |
| Attack payloads on the same paths refused                          | A WAF rule set applied by a *Set WAF* rule on the three paths                                               | WAF              | [WAF quickstart](/en/documentation/platform/firewall/waf/quickstart/)                                                                  |
| Bot Manager's decisions in the fraud team's tools                  | A stream of the *Functions* data source, which carries the Bot Manager report lines, to the team's endpoint | Data Stream      | [Debug functions with Data Stream](/en/documentation/guides/platform/observability/debugging-functions-data-stream/)                   |
| Each decision explained, request by request                        | The report line of the request, with its score and the rules it matched                                     | Real-Time Events | [Read the report log](/en/documentation/guides/application-security/bots-and-network/read-the-report-log/)                             |

---

## Reference architecture

This page builds the *Bot-scoring perimeter for authentication flows*: firewall rules that scope Bot Manager to the login, signup, and checkout paths, where the score decides whether a request passes, is challenged, or is denied.

```mermaid
%%{init: {"layout": "dagre", "themeVariables": {"fontSize": "13px"}, "flowchart": {"nodeSpacing": 12, "rankSpacing": 12, "padding": 6, "wrappingWidth": 70, "minNodeWidth": 40, "useMaxWidth": true}}}%%
flowchart TD
  Client["Client"] --> ALTCHA["ALTCHA rule: verifies a solved challenge"]
  ALTCHA --> WAF["WAF rule set on the three paths"]
  WAF --> Path{"Path"}
  Path -->|"/login or /signup"| Login["auth-login-challenge instance"]
  Path -->|"/checkout"| Checkout["auth-checkout-deny instance"]
  Path -->|"any other path"| App["application"]
  Login -->|"score below threshold"| App
  Login -->|"score at threshold"| Verify["redirect to /az-request-verify"]
  Checkout -->|"score below threshold"| App
  Checkout -->|"score at threshold"| Deny["403"]
  App --> Origin["identity system or payment flow"]
```

Read the diagram at the path split. The firewall's rules hand only the three sensitive paths to WAF and Bot Manager, so every other page is served without a score. On `/login`, `/signup`, and `/checkout`, the score of the instance that path runs sends each request one of three ways: through to the application, to the ALTCHA challenge a person can solve, or to a refusal. One instance per kind of path lets each run its own threshold and action.

### Dataflow

1. A request reaches the firewall bound to the workload. The ALTCHA rule runs first, so a client that already solved a challenge carries a session that ALTCHA validates.
2. The WAF rule scores requests to the three paths for attack payloads, such as an injection in a form field.
3. On `/login` and `/signup`, the `auth-login-challenge` instance scores the request. A score at its threshold redirects the client to the ALTCHA challenge. A person who solves it continues, and later requests pass without a new challenge.
4. On `/checkout`, the `auth-checkout-deny` instance scores the request. A score at its threshold receives `403`, because a checkout request that a script sends has no person to answer a challenge.
5. A request below the threshold reaches the application, and from it the identity system or the payment flow.
6. Each instance writes a report line per request, which Real-Time Events holds and Data Stream sends to the fraud team's tools.

### Components

- **firewall**: the Platform Resource whose path-scoped rules decide which requests Bot Manager and WAF see. One rule per kind of path lets each run its own threshold and action.
- **Bot Manager**: scores each request for automation and runs the action its instance sets once the score reaches the threshold. Its actions include `redirect`, which sends a client to a challenge, and `deny`, which refuses it with `403`.
- **WAF**: scores the same paths for attack payloads, such as injections in login and checkout form fields.
- **Functions**: runs the challenge, here ALTCHA from Azion Marketplace, ahead of Bot Manager, and any check the team writes for the `firewall` execution environment on the same paths.
- **Data Stream**: sends the *Functions* data source, which carries the Bot Manager report lines, to an endpoint the fraud team reads.
- **SIEM**: the integration where the fraud team correlates Bot Manager's decisions with its own signals.
- **Real-Time Events**: holds each report line, with the score and the rules behind it, for the investigation of one decision.

---

## Configure the observation window

Before any instance refuses a request, one instance in observation mode scores the three paths and refuses nothing: `action` is `allow`, and `internal_logs` is `2`, so every request writes a report line. Run it for 24 to 72 hours, long enough to cover peak hours, weekly crawlers, and overnight jobs. The lines are the only description of how your own customers score, and the thresholds in the next section come from them. `threshold` is `10`, the stricter of the two starting points below, so the `classified` label shows which requests that threshold would act on. `log_tag` is `auth-observe`, so each line names this instance.

Create the instance and the rule as [Run Bot Manager on selected paths](/en/documentation/guides/application-security/bots-and-network/run-bot-manager-on-selected-paths/) describes, with these values:

- **Instance**: `auth-observe`, with these arguments:

  ```json
  { "threshold": 10, "action": "allow", "internal_logs": 2, "log_tag": "auth-observe" }
  ```

- **Rule**: `auth - observe authentication paths`, with one block of criteria that names the three paths, joined by `or`: `Request Uri` *starts with* `/login`, `/signup`, or `/checkout`. In the API, the block is:

  ```json
  [
    { "variable": "${request_uri}", "conditional": "if", "operator": "starts_with", "argument": "/login" },
    { "variable": "${request_uri}", "conditional": "or", "operator": "starts_with", "argument": "/signup" },
    { "variable": "${request_uri}", "conditional": "or", "operator": "starts_with", "argument": "/checkout" }
  ]
  ```

- **Behavior**: *Run Function* with `auth-observe`.

Every request to the three paths is scored and served, and each one writes a line tagged `auth-observe`. Read `score` and `matched_rules` on requests you recognize as customers, because `classified` depends on the threshold in force. For the procedure, refer to [Run Bot Manager in observation mode](/en/documentation/guides/application-security/bots-and-network/observation-mode/).

---

## Configure challenge and deny by path

One instance carries one threshold and one action, so the two kinds of path take an instance each. The thresholds start where [Firewall best practices](/en/documentation/platform/firewall/best-practices/#run-a-stricter-bot-manager-instance-on-a-high-value-path) start them for these paths, and the observation window moves them: lower while automated clients pass, higher while customers you recognize are flagged.

| Instance               | Paths               | `threshold` | `action`   | Why                                                                                                                                                                                                             |
| ---------------------- | ------------------- | ----------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `auth-login-challenge` | `/login`, `/signup` | `10`        | `redirect` | A person flagged by mistake on a sign-in or a signup can still pass the challenge. Account creation starts at `12` in Firewall best practices; this page uses one instance for both paths, at the stricter `10` |
| `auth-checkout-deny`   | `/checkout`         | `10`        | `deny`     | Card testing has no person behind it, and a checkout request a challenge interrupts loses the order anyway                                                                                                      |

`redirect_to` is `/az-request-verify`, the path where ALTCHA serves its challenge. With no valid `redirect_to`, Bot Manager runs `allow` instead, so confirm in the report log that the instance redirects. Each instance has its own `log_tag`, so a refusal can be traced to the path that produced it.

**Console**

To create the two instances:

1. **Open the Functions Instances tab**

   Access [Azion Console](https://console.azion.com/) > **Firewalls**, select the firewall, then go to the **Functions Instances** tab.

2. **Create the challenge instance**

   Select **+ Function**, enter `auth-login-challenge`, select the Bot Manager function, enter these arguments, and select **Save**:

   ```json
   { "threshold": 10, "action": "redirect", "redirect_to": "/az-request-verify", "internal_logs": 2, "log_tag": "auth-login-challenge" }
   ```

3. **Create the deny instance**

   Select **+ Function**, enter `auth-checkout-deny`, select the Bot Manager function, enter these arguments, and select **Save**:

   ```json
   { "threshold": 10, "action": "deny", "internal_logs": 2, "log_tag": "auth-checkout-deny" }
   ```

To point the rules at them:

1. **Edit the observation rule**

   In the **Rules Engine** tab, open `auth - observe authentication paths`. Remove the `/checkout` criterion, rename the rule `auth - challenge login and signup`, and select `auth-login-challenge` in **Run Function**. Select **Save**.

2. **Create the checkout rule**

   Select **+ Rule** and enter `auth - deny on checkout`. In the **Criteria** section, select `Request Uri`, *starts with*, and `/checkout`. In the **Behaviors** section, select **Run Function**, then `auth-checkout-deny`. Select **Save**.

**API**

To create the challenge instance:

```bash
curl --request POST \
  --url https://api.azion.com/v4/workspace/firewalls/<firewall-id>/functions \
  --header 'Authorization: Token <personal-token>' \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "auth-login-challenge",
  "function": <bot-manager-function-id>,
  "active": true,
  "args": { "threshold": 10, "action": "redirect", "redirect_to": "/az-request-verify", "internal_logs": 2, "log_tag": "auth-login-challenge" }
}'
```

To create the deny instance:

```bash
curl --request POST \
  --url https://api.azion.com/v4/workspace/firewalls/<firewall-id>/functions \
  --header 'Authorization: Token <personal-token>' \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "auth-checkout-deny",
  "function": <bot-manager-function-id>,
  "active": true,
  "args": { "threshold": 10, "action": "deny", "internal_logs": 2, "log_tag": "auth-checkout-deny" }
}'
```

Each call answers `202` with a `state` of `pending` and the instance's `id`. Then create one rule per instance. This one runs the challenge instance on `/login` and `/signup`:

```bash
curl --request POST \
  --url https://api.azion.com/v4/workspace/firewalls/<firewall-id>/request_rules \
  --header 'Authorization: Token <personal-token>' \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "auth - challenge login and signup",
  "active": true,
  "criteria": [
    [
      { "variable": "${request_uri}", "conditional": "if", "operator": "starts_with", "argument": "/login" },
      { "variable": "${request_uri}", "conditional": "or", "operator": "starts_with", "argument": "/signup" }
    ]
  ],
  "behaviors": [{ "type": "run_function", "attributes": { "value": <challenge-instance-id> } }]
}'
```

The checkout rule is the same body with the name `auth - deny on checkout`, one criterion `${request_uri}` `starts_with` `/checkout`, and `<deny-instance-id>` in `value`. Each call answers `202` with a `state` of `pending`. Delete the observation rule afterward, with `DELETE /v4/workspace/firewalls/<firewall-id>/request_rules/<observe-rule-id>`.

`internal_logs` stays at `2` on both instances while you verify them, so every request writes a line. Observation mode writes a line for every request, so lower `internal_logs` once the thresholds hold. An argument change reaches traffic about 105 seconds after it is saved, and a new rule 6 to 10 minutes after.

---

## Configure WAF on the authentication paths

The WAF rule set `auth-waf` scores requests to the three paths for attack payloads, such as an injection in a login form field. It carries the eight threat families at `medium`, the level every family starts at, and the rule that applies it starts in *Logging*. Move it to *Blocking* once 3 days of **Tuning** hold no request that should have been served. The rule matches the paths, not the query string, because a login form posts its fields in the body.

**Console**

To create the rule set and apply it:

1. **Create the rule set**

   Access [Azion Console](https://console.azion.com/) > **Edge Libraries** > **WAF Rules**, select **+ WAF Rule**, enter `auth-waf` as the **Name**, keep every family at *Sensitivity Medium*, and select **Save**.

2. **Create the rule**

   In the firewall's **Rules Engine** tab, select **+ Rule** and enter `auth - apply auth-waf`.

3. **Match the three paths**

   In the **Criteria** section, select `Request Uri`, *starts with*, and `/login`. Add two criteria joined by **Or**: `/signup` and `/checkout`.

4. **Add the Set WAF behavior**

   In the **Behaviors** section, select **Set WAF**, then `auth-waf` and *Logging*.

5. **Select Save**

**API**

To create the rule set, send the body of the [WAF quickstart](/en/documentation/platform/firewall/waf/quickstart/) with `"name": "auth-waf"`, to `POST https://api.azion.com/v4/workspace/wafs`. The API answers `202` with the rule set's `id`. To apply it:

```bash
curl --request POST \
  --url https://api.azion.com/v4/workspace/firewalls/<firewall-id>/request_rules \
  --header 'Authorization: Token <personal-token>' \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "auth - apply auth-waf",
  "active": true,
  "criteria": [
    [
      { "variable": "${request_uri}", "conditional": "if", "operator": "starts_with", "argument": "/login" },
      { "variable": "${request_uri}", "conditional": "or", "operator": "starts_with", "argument": "/signup" },
      { "variable": "${request_uri}", "conditional": "or", "operator": "starts_with", "argument": "/checkout" }
    ]
  ],
  "behaviors": [{ "type": "set_waf", "attributes": { "waf_id": <waf-id>, "mode": "logging" } }]
}'
```

The API answers `202` with a `state` of `pending` and the rule as it was stored.

Requests to the three paths are scored for attack payloads, and what would be blocked is recorded. To move the rule to *Blocking*, refer to [Switch a rule set to blocking](/en/documentation/guides/application-security/firewall-and-waf/switch-to-blocking/).

---

## Verify the setup

An argument change reaches traffic about 105 seconds after it is saved, and a new rule 6 to 10 minutes after. Repeat each request until the answer holds. Each check reads the report line in the `functionConsoleEvents` dataset of Real-Time Events, as [Read the report log](/en/documentation/guides/application-security/bots-and-network/read-the-report-log/) describes.

- **Only the three paths are scored.** Request the home page and `/login`:

  ```bash
  curl -s -o /dev/null https://www.example.com/
  curl -s -o /dev/null https://www.example.com/login
  ```

  A report line appears for `/login`, and none for `/`.

- **A flagged checkout request is denied.** Send a request with no user agent, which is what a scripted client sends:

  ```bash
  curl -s -o /dev/null -w '%{http_code}\n' -A "" https://www.example.com/checkout
  ```

  When the request's score reaches `10`, the command prints `403`, and the line tagged `auth-checkout-deny` reads `"action":"deny"`. A lower score is served, and the line shows the score it received.

- **A flagged login request is challenged.** Send the same request to `/login`:

  ```bash
  curl -s -o /dev/null -w '%{redirect_url}\n' -A "" https://www.example.com/login
  ```

  When the score reaches `10`, the command prints the address of `/az-request-verify`, and the line tagged `auth-login-challenge` reads `"action":"redirect"`. A line that reads `allow` at a score at the threshold means `redirect_to` is missing or invalid.

- **A person passes the challenge.** Open `https://www.example.com/login` in a browser, from a client the instance flags, and solve the challenge. The login page loads, and later requests pass without a new challenge.

- **WAF scores the three paths.** Send `https://www.example.com/login?q=1%27%20OR%20%271%27%3D%271`. In *Logging*, the page loads. After the switch to *Blocking*, the request receives `400`.

---

## Measuring results

| Metric                                                 | Where to read it                                                                                                                                                                                                       | What working looks like                                                                                                                                  |
| ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Share of automated login and checkout attempts blocked | **Top Bot Action** on the Bot Manager dashboard, with **Top Impacted URLs** narrowing it to the three paths. Refer to [Secure dashboards](/en/documentation/platform/real-time-metrics/secure-dashboards/#bot-manager) | `Deny` and `Redirect` carry the automated traffic on the three paths. Record the threshold beside every figure, because the classification moves with it |
| False-positive rate on real users                      | **Bot CAPTCHA** on the same dashboard, which splits the challenges into solved and not solved, and the `challenge_solved` field of the report line                                                                     | A solved challenge is a flagged client that a person answered, so a share that keeps rising points to a threshold inside your customers' scores          |
| Attack traffic that still reaches the origin           | The report lines of the three paths whose `action` reads `allow`, read against their `score`                                                                                                                           | No cluster of automated clients sits just under the threshold                                                                                            |

---

## Best practices

- **Set each threshold from your own scores.** The starting values are documented starting points, not descriptions of your traffic. A threshold inside the customers' cluster refuses them, and one past the automated cluster acts on nothing. For the reasoning, refer to [Set the Bot Manager threshold from your own score distribution](/en/documentation/platform/firewall/best-practices/#set-the-bot-manager-threshold-from-your-own-score-distribution).
- **Run ALTCHA before Bot Manager.** A redirected client comes back to the firewall. When Bot Manager scores the returning request before ALTCHA validates its session, it redirects the client again.
- **Write out every argument the instance depends on.** Nothing validates the arguments object. A misspelled key such as `thresold` is stored without an error, and the instance runs its default, which on Bot Manager Lite is `deny` at `30`.
- **Read what a rule matched before you disable it.** `matched_rules` names the Bot Manager rules behind each score. Disabling one stops it for every client, so narrow the instance's paths instead when a rule flags customers on one path.
- **Keep a copy of the report lines you own.** Real-Time Events keeps an event record for 7 days, and a fraud investigation often looks further back. Send the *Functions* data source to the fraud team's endpoint with [Debug functions with Data Stream](/en/documentation/guides/platform/observability/debugging-functions-data-stream/).

---

## Guides in this use case

- [Run Bot Manager on selected paths](/en/documentation/guides/application-security/bots-and-network/run-bot-manager-on-selected-paths.md): Create the instances and the rules that scope Bot Manager to login, signup, and checkout.
- [Protect a route with an ALTCHA challenge](/en/documentation/guides/application-development/functions-and-runtime/altcha.md): Instantiate ALTCHA on the firewall, with the rule that serves the challenge the redirect action sends a flagged client to.
