Baseline Integration of Castle

This guide serves as an outline to the integration steps needed for a baseline integration. It also details how Castle will expose your existing security blind spots and how you can leverage Castle’s tools to ensure your users are protected.

Step 1. Castle.js

Implementing Castle.js into your application allows Castle to pull hundreds of signals about the user's device and interaction.

A fingerprint is created from these signals, and the fingerprints we collect build our models of what we classify as behavioral norms, for both individual users and your users as a whole. All deviations and changes in behavior are tracked, and each one influences how our self-learning models score user devices.

Paste the JavaScript snippet into the <head> section of all your pages:

<script type="text/javascript">
  (function(e,t,n,r){function i(e,n){e=t.createElement("script");e.async=1;e.src=r;n=t.getElementsByTagName("script")[0];n.parentNode.insertBefore(e,n)}e[n]=e[n]||function(){(e[n].q=e[n].q||[]).push(arguments)};e.attachEvent?e.attachEvent("onload",i):e.addEventListener("load",i,false)})(window,document,"_castle","//d2t77mnxyo7adj.cloudfront.net/v1/c.js")

  // Get your App ID here: https://dashboard.castle.io/settings/general
  _castle('setAppId', '123456789012345');

  // Use your favorite templating language to inject the user ID
  <% if user_signed_in? %>
    _castle('identify', 'e325bcdd10ac');
  <% end %>
</script>

The JavaScript snippet needs to go into all your pages, both before and after the user is authenticated, including the landing page of your website.

Step 2. Server-side SDK

This event tracking enriches Castle’s fingerprinting and serves to spot botnet or machine automated attacks on your application. A spike in failed login attempts for example, is indicative of a brute force or credential stuffing attack and allows you to make use of Castle’s auto-mitigation tools to defend against hacking attempts.

It's important that you disable any dynamic authentication logic such as IP rate limiting or WAF rules as well as remove any CAPTCHA prompts after you've added the login tracking. Mechanisms like these will "hide" malicious traffic from Castle.

Fetch your API Secret from the Castle Dashboard and then install and configure the server-side SDK:

Step 3. Tracking Successful Logins

Track $login.succeeded when the user enters the correct password.

begin
  castle.track(
    event: '$login.succeeded',
    user_id: 'e325bcdd10ac',
    user_traits: {
      email: 'johan@castle.io',
      created_at: '2015-02-23T22:28:55.387Z'
    }
  )
rescue Castle::Error => e
  puts e.message
end
<?
try {
  Castle::track(array(
    'event' => '$login.succeeded',
    'user_id' => $user->id,
    'user_traits' => array(
      'email' => 'johan@castle.io',
      'created_at' => '2015-02-23T22:28:55.387Z'
    )
  ));
} catch (Castle_Error $e) {
  // ...
}
castle.track({
    'event': '$login.succeeded',
    'user_id': 'e325bcdd10ac',
    'user_traits': {
        'email': 'johan@castle.io',
        'created_at': '2015-02-23T22:28:55.387Z'
    }
})
ImmutableMap traits = ImmutableMap.builder()
  .put("email", "johan@castle.io")
  .put("created_at", "2015-02-23T22:28:55.387Z")
  .build();

castle.track(
  "$login.succeeded",
  "e325bcdd10ac",
  null,
  null,
  traits
);

curl https://api.castle.io/v1/track \
  -X POST \
  -u ":YOUR-API-SECRET" \
  -H "Content-Type: application/json" \
  -d '
    {
      "event": "$login.succeeded",
      "user_id": "e325bcdd10ac",
      "user_traits": {
        "email": "johan@castle.io",
        "created_at": "2015-02-23T22:28:55.387Z"
      },
      "context": {
        "client_id": "a97b492d-dcc3-4fc1-87d6-65682955afa5",
        "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"
        }
      }
    }'

Asynchronous queuing: When calling track from a worker thread, the request context (Client ID, IP address and HTTP headers) will need to be transferred to the asynchronous context.

Step 4. Tracking Failed Login Attempts

Track $login.failed when the user fails to enter the correct password.

Add the user-submitted form field value as user_traits.email.

begin
  castle.track(
    event: '$login.failed',
    user_traits: {
      email: 'admin@cstl.io'
    }
  )
rescue Castle::Error => e
  puts e.message
end
<?
Castle::track(array(
  'event' => '$login.failed',
  'user_traits' => array(
    'email' => 'admin@cstl.io'
  )
));
castle.track({
    'event': '$login.failed',
    'user_traits': {
        'email': 'admin@cstl.io',
    }
})
ImmutableMap traits = ImmutableMap.builder()
  .put("email", "admin@cstl.io")
  .build();

castle.track(
  "$login.failed",
  null,
  null,
  null,
  traits
);
curl https://api.castle.io/v1/track \
  -X POST \
  -u ":YOUR-API-SECRET" \
  -H "Content-Type: application/json" \
  -d '
    {
      "event": "$login.failed",
      "user_traits": {
        "email": "admin@cstl.io"
      },
      "context": {
        "client_id": "a97b492d-dcc3-4fc1-87d6-65682955afa5",
        "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"
        }
      }
    }'