DOCS
Getting started
Introduction Obj 1. Protecting your login Obj 2. Account recovery Obj 3. Engaging your users
Guides
Denying bots and abuse Policies Webhooks User devices list Asynchronous tracking In-line challenges Mobile applications Managing team members Client-side event tracking Billing API reference
Building Admin Tools
Device Management Tool GDPR APIs Impersonation Mode Slack Alerts
SDKs
Android SDK iOS SDK JavaScript Package (NPM) Java SDK PHP SDK Python SDK Ruby SDK .NET SDK
Sign In

Denying bots and abuse

With just a few lines of code, you’ll be denying abusive requests pre-authentication, before they reach your business logic. This helps you eliminate the need for traditional anti-bot measures such as CAPTCHAs. Create a free account here and get started in minutes.

Pre vs Post-authentication If you’re primarily interested in detecting anomalous user behavior post-authentication, sometimes referred to as user behavior analytics, or UBA, then you should instead check out Protecting your login.


Step 1. Select the form you want to protect


Step 2. Include Castle.js Client-side

Include the Castle.js script on each page of your website, not just your page. Castle.js analyzes device properties and user interactions in order to detect bots and scripted activity.

CDNnpm
<script src="https://d2t77mnxyo7adj.cloudfront.net/v1/c.js?XTEXTapp_idZTEXT"></script>
<!-- https://www.npmjs.com/package/castle.js -->
<script type="text/javascript" src='dist/c.js'></script>
<script type="text/javascript">
  _castle('setAppId', 'XTEXTapp_idZTEXT');
</script>

We’ve populated the example with the sample App ID XTEXTapp_idZTEXT. Replace the sample App ID with the actual Sandbox App ID you’ll find in your dashboard.

No JavaScript? In order to detect sophisticated bots and abuse, Castle.js is required for $XTEXTapiZTEXT.attempted to work. That said, if you’re in an environment where you’re unable to integrate a JavaScript tag, you can still integrate Castle post-authentication in a server-side only manner.


Step 3. Intercept the form Client-side

When the user submits the form, you intercept the submission and call _castle('getClientId') to generate a single-use token that you then pass to your server-side handler.

JavaScript
var form = document.getElementById('XTEXTapiZTEXT-form');

form.addEventListener("submit", function(evt) {
  evt.preventDefault()

  // Get the ClientID token
  var clientId = _castle('getClientId');

  // Populate a hidden <input> field named `castle_client_id`
  var hiddenInput = document.createElement('input');
  hiddenInput.setAttribute('type', 'hidden');
  hiddenInput.setAttribute('name', 'castle_client_id');
  hiddenInput.setAttribute('value', clientId);

  // Add the `castle_client_id` to the HTML form
  form.appendChild(hiddenInput);

  form.submit()
});

Step 4. Handle the request Server-side

In your server-side handler, extract the parameter that was passed in the form submission in Step 2, in this example castle_client_id.

The email address (alternatively phone or username) submitted in the form is a required field and is used by Castle to do the trust assessment.

RubyPHPPythonJava.NETCurl
Castle.api_secret = 'XTEXTapi_secretZTEXT'

begin
  castle = Castle::Client.from_request(request)

  # IMPLEMENT: Get the Client ID from the form submission
  client_id = params[:castle_client_id]

  # IMPLEMENT: Get the email form field
  email = params[:email]

  res = castle.authenticate(
    event: '$XTEXTapiZTEXT.attempted',
    properties: {
      email: email
    },
    context: {
      client_id: client_id
    }
  )

  # You can ignore the returned action during evaluation
  if res[:action] == Castle::Verdict::DENY
    # ...
  end

rescue Castle::Error => e
  # handle error
end
Castle::setApiKey('XTEXTapi_secretZTEXT');

try {
  // IMPLEMENT: Get the Client ID from the form submission
  $clientId = $params['castle_client_id'];

  // IMPLEMENT: Get the email form field
  $email = $params['email'];

  $context = json_decode(Castle_RequestContext::extractJson(), true);

  // override the client_id attribute
  $context = array('client_id' => $clientId) + $context;
  $res = Castle::authenticate(array(
    'event' => '$XTEXTapiZTEXT.attempted',
    'properties' => Array('email' => $email),
    'context' => $context
  ));

  // You can ignore the returned action during evaluation
  if ($res->action == 'deny') {
    // ...
  }
} catch (Castle_Error $e) {
  // handle error
}
from castle.configuration import configuration
from castle.client import Client
from castle import events
from castle.verdict import Verdict

configuration.api_secret = 'XTEXTapi_secretZTEXT'

try:
    castle = Client.from_request(request)

    # IMPLEMENT: Get the Client ID from the form submission
    client_id = params.get('castle_client_id')

    # IMPLEMENT: Get the email form field
    email = params.get('email')

    res = castle.authenticate({
        'event': '$XTEXTapiZTEXT.attempted',
        'properties': {
            'email': email
        },
        'context': {
            'client_id': client_id
        }
    })

    # You can ignore the returned action during evaluation
    if res['action'] == Verdict.DENY.value:
        # ...
except CastleError as e:
    # handle error
Castle castle = Castle.initialize("XTEXTapi_secretZTEXT");

try {
    // IMPLEMENT: Get the Client ID from the form submission
    String clientId = params.get('castle_client_id');

    // IMPLEMENT: Get the email form field
    String email = params.get('email');

    CastleContext context = castle.contextBuilder()
        .fromHttpServletRequest(req)
        .build();

    // override the client_id attribute
    context.setClientId(clientId);

    CastleMessage payload = CastleMessage.builder("$XTEXTapiZTEXT.attempted")
        .properties(ImmutableMap.builder()
            .put('email', email)
            .build())
        .context(context)
        .build();

    Verdict res = castle.client().authenticate(payload);

    // You can ignore the returned action during evaluation
    if (res.getAction() == AuthenticateAction.DENY) {
      // ...
    }
} catch (CastleServerErrorException e) {
    // handle error
}
var castle = new CastleClient("XTEXTapi_secretZTEXT");

try {
    // IMPLEMENT: Get the Client ID from the form submission
    string clientId = params["castle_client_id"];

    // IMPLEMENT: Get the email form field
    string email = params["email"];

    var res = await castle.Authenticate(new ActionRequest()
    {
        Event = "$XTEXTapiZTEXT.attempted",
        Properties = new Dictionary<string, string>()
        {
            ["email"] = email
        },
        Context = new RequestContext()
        {
            Ip = Request.HttpContext.Connection.RemoteIpAddress.ToString(),
            ClientId = clientId,
            Headers = Request.Headers.ToDictionary(x => x.Key, y => y.Value.FirstOrDefault())
        }
    });

    // You can ignore the returned action during evaluation
    if (res["action"] == ActionType.Deny) {
      // ...
    }
} catch (CastleExternalException e) {
    // handle error
}
curl https://api.castle.io/v1/authenticate \
  -X POST \
  -u ":XTEXTapi_secretZTEXT" \
  -H "Content-Type: application/json" \
  -d '
    {
      "event": "$XTEXTapiZTEXT.attempted",
      "properties": {
        "email": "johan@castle.io"
      },
      "context": {
        "client_id": "faf117b2-9457-4e3b-9c13-d2795656b78e-094e81caa170c1d2",
        "ip": "37.46.187.90",
        "headers": {
          "User-Agent": "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko",
          "Accept": "text/html",
          "Accept-Language": "en-us,en;q=0.5",
          "Accept-Encoding": "gzip, deflate, br",
          "Connection": "Keep-Alive",
          "Content-Length": "122",
          "Content-Type": "application/javascript",
          "Origin": "https://castle.io/",
          "Referer": "https://castle.io/login"
        }
      }
    }'

The deny action is determined by the policy that you’ve configured in your Policy settings. When you sign up for Castle, there will be a default policies that you can modify to your needs. If no policy triggered, you’ll receive an allow action.

The Events Debugger will come in handy when inspecting Castle API calls to ensure you got all the details right.

What’s collected behind the scenes

Castle’s server-side SDK will automatically extract IP and key header information from the HTTP request. Sensitive headers such as Cookie and anything related to authentication are not sent.

Note that during local development, the IP address will likely always be 127.0.0.1. In production, make sure your load balancer or firewall doesn’t override the client IP address with a static internal one.

In some cases you want to track data to Castle from a context where the above data isn’t available, for example when authenticate is called from a separate microservice. In that case you’ll need to forward the request context between services.

DOCS
Getting started
Introduction Obj 1. Protecting your login Obj 2. Account recovery Obj 3. Engaging your users
Guides
Denying bots and abuse
  • Denying bots and abuse
        • What’s collected behind the scenes
Policies Webhooks User devices list Asynchronous tracking In-line challenges Mobile applications Managing team members Client-side event tracking Billing API reference
Building Admin Tools
Device Management Tool GDPR APIs Impersonation Mode Slack Alerts
SDKs
Android SDK iOS SDK JavaScript Package (NPM) Java SDK PHP SDK Python SDK Ruby SDK .NET SDK