The platform can identify Tor exit nodes and commercial VPN services from the incoming IP address before a single credential check runs. This recipe turns that signal into a hard block — users on those networks cannot complete a login at all.
Deny any login attempt that originates from a Tor exit node or a known commercial VPN. The user sees a generic access-denied message; the audit log records the specific reason. No second factor is offered; the session is never issued.
When to use it
Section titled “When to use it”Tor users include privacy advocates, journalists, activists, and ordinary people who have legitimate reasons to protect their network identity. VPN users include remote workers, travelers, and anyone using corporate network security tooling. Blocking these networks carries real costs. Do not reach for this recipe without a documented policy requirement.
This pattern is appropriate for high-value B2B platforms and regulated industries — banking, financial services, healthcare, government — where the compliance posture explicitly prohibits anonymous-network access. If your goal is to add friction for suspicious logins without a hard block, use the Step-up MFA on risk score recipe instead. That pattern preserves access for legitimate users on VPNs while still challenging elevated-risk sessions.
What you'll build
Section titled “What you'll build”A Login flow where the Post-Auth stage reads two flags from event.request.asn and routes any request where either is true to a Deny terminal. Requests that pass go straight to Issue Session.
Login flow Stage: Pre-Login Block: Identity Lookup Block: Password Check Stage: Post-Auth Block: Decision <-- reads event.request.asn.* Branch: blocked <-- is_tor OR is_vpn == true Block: Deny <-- terminates the flow Branch: proceed (default) <-- neither flag set Stage: Post-Login Block: Issue SessionThis recipe has two variants: a plain Decision-block version for the common case, and an optional custom action variant for tenants that need to allowlist specific corporate ASNs before applying the VPN block.
Configure it — block-only variant
Section titled “Configure it — block-only variant”Step 1 — Open the Login flow editor
Section titled “Step 1 — Open the Login flow editor”Go to Flows, click Login, then Edit.
Step 2 — Add a Decision block to Post-Auth
Section titled “Step 2 — Add a Decision block to Post-Auth”In the Post-Auth stage, open the block picker and drag a Decision block onto the canvas.
Step 3 — Configure the branches
Section titled “Step 3 — Configure the branches”Open the Decision block's settings. Add two branches:
| Branch label | When expression | Route to |
|---|---|---|
blocked | event.request.asn.is_tor == true || event.request.asn.is_vpn == true | Deny block |
proceed | (default) | Issue Session |
Step 4 — Add the Deny block
Section titled “Step 4 — Add the Deny block”On the blocked branch, drag a Deny block. Set the Reason field to network-blocked. This string appears in the audit log so you can trace the denial; it is never shown to the user. The user sees whatever message your tenant's denial template is configured to display.
Step 5 — Save and publish
Section titled “Step 5 — Save and publish”Click Save, review the diff, and click Publish.
Configure it — custom action variant (allowlist corporate ASNs)
Section titled “Configure it — custom action variant (allowlist corporate ASNs)”If your users connect through a corporate VPN whose ASN you know, you can allowlist it before applying the block. This prevents legitimate employees from being denied access.
Step 1 — Create the custom action
Section titled “Step 1 — Create the custom action”Go to Custom Actions, click + New Action, and name it network-policy-check. Set the trigger to Login. Paste the following code:
// network-policy-check// Allow traffic from known corporate ASNs even when VPN flag is set.// Deny everything else on Tor or commercial VPN.
const KNOWN_CORPORATE_ASNS = new Set([ // Replace these with your organization's ASN numbers. // Find your ASN at: https://bgp.he.net/ 64512, 64513,]);
const asn = event.request.asn;
// Corporate VPN: allow through even if the VPN flag is set.if (KNOWN_CORPORATE_ASNS.has(asn.number)) { api.log('info', `Corporate ASN allowed: ${asn.number} (${asn.org})`); return;}
// Tor exit node: always deny, no exceptions.if (asn.is_tor) { api.deny('network-blocked:tor'); return;}
// Commercial VPN not on the corporate allowlist: deny.if (asn.is_vpn) { api.deny('network-blocked:vpn'); return;}Click Save, then enable the action.
Step 2 — Wire the action into the Login flow
Section titled “Step 2 — Wire the action into the Login flow”Go to Flows, click Login, then Edit. In the Post-Auth stage, drag a Run Custom Action block and select network-policy-check. The action calls api.deny() directly, so no downstream Decision or Deny block is needed — the flow halts as soon as api.deny() is called.
Step 3 — Save and publish
Section titled “Step 3 — Save and publish”Click Save and Publish.
Test it
Section titled “Test it”Open the Test panel on the Decision block (block-only variant) or use the Test tab on your custom action (action variant).
For the block-only variant, use these test payloads:
// Payload 1: Tor exit node — should hit the Deny block{ "event.request.asn.is_tor": true, "event.request.asn.is_vpn": false, "event.request.asn.number": 0}
// Payload 2: commercial VPN — should hit the Deny block{ "event.request.asn.is_tor": false, "event.request.asn.is_vpn": true, "event.request.asn.number": 0}
// Payload 3: clean residential connection — should reach Issue Session{ "event.request.asn.is_tor": false, "event.request.asn.is_vpn": false}For the custom action variant, paste each payload into the Test tab in the Custom Actions editor. Confirm that the API calls panel shows api.deny(...) for payloads 1 and 2, and no API calls for payload 3.
Verify on a live flow
Section titled “Verify on a live flow”Connect to an actual Tor Browser and sign in to your tenant. The login should fail with your tenant's generic denial message. Disconnect from Tor and sign in from your normal network — the login should complete.
Testing Tor blocking on a local k3d cluster requires routing your test traffic through a real Tor exit node. The IP address 127.0.0.1 or your development machine's LAN IP will not match the Tor exit node database. Use the Test pane's simulate payload, or test against a staging environment that receives real external traffic.
Cautions
Section titled “Cautions”Tor blocking has real costs. Privacy-conscious users, journalists in high-censorship regions, and security researchers are legitimate users. Blocking Tor is a policy decision — not a best practice — and it should be documented and reviewed regularly.
VPN classification is heuristic. The platform's ASN classification identifies commercial VPN providers, but the dataset is not exhaustive. A VPN that launched after the last threat-feed update will not be flagged. Conversely, some legitimate corporate or university networks may share ASN prefixes with flagged ranges. The custom action variant's allowlist mechanism is the correct mitigation for the latter case.
Do not surface the blocking reason to the user. The Deny block's reason field is audit-log only. If your error template says something like "Tor access is not permitted," you are telling attackers exactly what signal to evade. Use a generic message such as "Access from your current network is not permitted."
Consider step-up as the default. For most tenants, the Step-up MFA on risk score recipe is the better starting point. It challenges Tor and VPN sessions with MFA rather than denying them, which keeps the door open for legitimate users while still protecting against account takeover.