{"__v":0,"_id":"5719767ec863120e0012a05f","category":{"__v":0,"_id":"5719767ec863120e0012a044","project":"56008ba98c0c9d0d00dcaeb0","version":"5719767ec863120e0012a042","sync":{"url":"","isSync":false},"reference":false,"createdAt":"2016-03-03T22:29:05.165Z","from_sync":false,"order":1,"slug":"guides","title":"Guide"},"parentDoc":null,"project":"56008ba98c0c9d0d00dcaeb0","user":"56008b651503430d007cc929","version":{"__v":4,"_id":"5719767ec863120e0012a042","hasDoc":true,"hasReference":true,"project":"56008ba98c0c9d0d00dcaeb0","createdAt":"2016-04-22T00:55:26.295Z","releaseDate":"2016-04-22T00:55:26.295Z","categories":["5719767ec863120e0012a043","5719767ec863120e0012a044","5719767ec863120e0012a045","5719767ec863120e0012a046","5719767ec863120e0012a047","5719767ec863120e0012a048","5719767ec863120e0012a049","57f45a18da14e71700d12e4a","582b71b15403840f008c0410","58c060cf3eee111b00a8b210"],"is_deprecated":false,"is_hidden":false,"is_beta":true,"is_stable":true,"codename":"","version_clean":"2.0.0","version":"2.0"},"updates":[],"next":{"pages":[],"description":""},"createdAt":"2016-03-04T00:09:43.935Z","link_external":false,"link_url":"","githubsync":"","sync_unique":"","hidden":false,"api":{"results":{"codes":[]},"settings":"","auth":"required","params":[],"url":""},"isReference":false,"order":3,"body":"When ThisData detects unusual activity we will send out \"Was This You?\" notifications to your users. This is the first step in the security workflow. Now let's close the loop, by securing your user's account when things go wrong.\n[block:image]\n{\n  \"images\": [\n    {\n      \"image\": [\n        \"https://files.readme.io/wFvnpM92TSjQoKZDh4Pn_thisdata_flow_diagram.png\",\n        \"thisdata_flow_diagram.png\",\n        \"1296\",\n        \"1176\",\n        \"#8259ec\",\n        \"\"\n      ],\n      \"sizing\": \"full\"\n    }\n  ]\n}\n[/block]\n\n[block:api-header]\n{\n  \"type\": \"basic\",\n  \"title\": \"Tell ThisData where to send webhooks\"\n}\n[/block]\nGo to [ThisData.com](https://thisdata.com), click on [+ Add Integration] in the top navigation, then click Login Intelligence API.\nIn the form field labelled \"Webhook URL\", enter a URL which your app will listen to. When we create an alert, and also if/when a user responds to a \"Was This You?\" notification with \"Yes\" or \"No\", we will `POST` that information to this URL.\n[block:api-header]\n{\n  \"type\": \"basic\",\n  \"title\": \"Learn what the webhooks have in them\"\n}\n[/block]\nThe data we `POST` to you can be found in [our detailed documentation on Webhooks](doc:webhooks).\n[block:api-header]\n{\n  \"type\": \"basic\",\n  \"title\": \"Verify that the webhook really came from ThisData\"\n}\n[/block]\nOn that same settings page, we recommend you provide a secret String. We will use this shared secret to do an HMAC signature of the request. When you receive a request to your webhook's URL, you can verify that the HMAC signature has correctly signed the body of the request with the shared secret.\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"# Calculate an HMAC for the webhook ThisData has sent us\\nkey = \\\"'Secret for Webhook Signatures' key copied from your settings page\\\"\\nbody = request.body.string\\ndigest = OpenSSL::Digest.new('sha512')\\nhmac = OpenSSL::HMAC.hexdigest(digest, key, body)\\n\\n# Verify it matches the signature they sent\\nif hmac == request.headers[\\\"X-Signature\\\"]\\n  # carry on\\nelse\\n  # someone is pretending to be ThisData\\nend\",\n      \"language\": \"ruby\"\n    },\n    {\n      \"code\": \" var signature = req.headers['x-signature'];\\n var digest = crypto\\n     .createHmac('sha1', settings.sharedSecret)\\n     .update(JSON.stringify(req.body))\\n     .digest('hex');\\n\\nif (signature === digest) {\\n\\n // do cool stuff\\n\\n}\",\n      \"language\": \"javascript\",\n      \"name\": \"Node\"\n    }\n  ]\n}\n[/block]\n\n[block:api-header]\n{\n  \"type\": \"basic\",\n  \"title\": \"Respond to ThisData's webhook by terminating all current user sessions\"\n}\n[/block]\nWhen a user has said their account has been accessed by someone else, we recommend revoking all current sessions. How this is achieved will depend on how your app implements user sessions.\n\nIf you use database-backed sessions, delete all current sessions (or invalidate them). Any subsequent requests will be unauthorized because there is no longer a valid session stored in your database.\n\nIf you use cookie-based sessions only, it's a little more difficult as you can't reach in to someone's browser and remove their cookies. Perhaps the cookie needs to contain some value which matches up with a user to remain valid - if so, update the affected user in your database so that those two values no longer match and the cookie is invalidated.\n[block:api-header]\n{\n  \"type\": \"basic\",\n  \"title\": \"Change your user's password to something long and random\"\n}\n[/block]\nIf the user's account is compromised, it is likely to be caused by their email and password being compromised. Invalidating all current sessions is the first step, but an attacker can log in again with the unchanged password.\n\nUpdate the password to something long and random. The old password is now useless, and existing sessions have been neutralize. The attacker can no longer cause any damage.\n\nWhen you get in touch with your affected user, they can use your \"reset password\" functionality to recover access to their account.\n[block:callout]\n{\n  \"type\": \"danger\",\n  \"title\": \"Don't use a bad password!\",\n  \"body\": \"Update the password to something long and random. It should not be predictable, and your employee's should not be able to figure it out either.\"\n}\n[/block]\nWhen you get in touch with your affected user, they can use your \"reset password\" functionality to recover access to their account.\n[block:api-header]\n{\n  \"type\": \"basic\",\n  \"title\": \"Notify your support & ops teams\"\n}\n[/block]\nThe attacker did get in, probably for at least a few seconds. Now it's time to figure out what, if anything, they had time to do.\n[block:api-header]\n{\n  \"type\": \"basic\",\n  \"title\": \"Get in touch with your customer\"\n}\n[/block]\nEmail your customer, or ideally phone them, to let them know what is happening. Keep them in the loop. You might not immediately know what the attacker did, but *you* stopped them in their tracks.\n\nIt's such an amazing experience for them when you front-foot it, show that you're competently handling the situation, and have prevented the worst from happening. ๐Ÿ‘๐Ÿ˜\n[block:api-header]\n{\n  \"type\": \"basic\",\n  \"title\": \"Tying it all together with some code:\"\n}\n[/block]\nHere is some code for Ruby on Rails which shows us receiving a request, verifying it, and acting upon it by resetting the password, neutralizing all sessions, and emailing our customer with a quick heads-up message.\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"class ThisDataWebhooksController < ApplicationController\\n\\n  def receive\\n    # Calculate an HMAC for the webhook ThisData has sent us\\n    key = \\\"'Secret for Webhook Signatures' key copied from your settings page\\\"\\n    body = request.body.string\\n    digest = OpenSSL::Digest.new('sha512')\\n    hmac = OpenSSL::HMAC.hexdigest(digest, key, body)\\n\\n    if hmac == request.headers[\\\"X-Signature\\\"]\\n      payload = JSON.parse(body)\\n      # Confirm that the user said it was not them\\n      if payload[:was_user] == false\\n        user = User.find(payload[:user][:id])\\n        # Manually re-set the password. The old compromised password will no longer work.\\n        new_random_password = SecureRandom.hex(32)\\n        user.update!(new_password: new_random_password)\\n        # Find and destroy all the user session records for this user.\\n        # Any subsequent requests will be unauthorized, and require them to log in again.\\n        user.user_sessions.destroy_all!\\n        # Tell the user what we've done\\n        Mailer.begin_account_restoration(user).deliver!\\n      end\\n    end\\n\\n  end\\n\\nend\",\n      \"language\": \"ruby\"\n    }\n  ]\n}\n[/block]","excerpt":"Learn how to accept ThisData's webhooks and create a security workflow to quickly resecure compromized accounts.","slug":"create-a-security-workflow-with-thisdatas-alert-webhooks","type":"basic","title":"4. Create a security workflow with Alert Webhooks"}

4. Create a security workflow with Alert Webhooks

Learn how to accept ThisData's webhooks and create a security workflow to quickly resecure compromized accounts.

When ThisData detects unusual activity we will send out "Was This You?" notifications to your users. This is the first step in the security workflow. Now let's close the loop, by securing your user's account when things go wrong. [block:image] { "images": [ { "image": [ "https://files.readme.io/wFvnpM92TSjQoKZDh4Pn_thisdata_flow_diagram.png", "thisdata_flow_diagram.png", "1296", "1176", "#8259ec", "" ], "sizing": "full" } ] } [/block] [block:api-header] { "type": "basic", "title": "Tell ThisData where to send webhooks" } [/block] Go to [ThisData.com](https://thisdata.com), click on [+ Add Integration] in the top navigation, then click Login Intelligence API. In the form field labelled "Webhook URL", enter a URL which your app will listen to. When we create an alert, and also if/when a user responds to a "Was This You?" notification with "Yes" or "No", we will `POST` that information to this URL. [block:api-header] { "type": "basic", "title": "Learn what the webhooks have in them" } [/block] The data we `POST` to you can be found in [our detailed documentation on Webhooks](doc:webhooks). [block:api-header] { "type": "basic", "title": "Verify that the webhook really came from ThisData" } [/block] On that same settings page, we recommend you provide a secret String. We will use this shared secret to do an HMAC signature of the request. When you receive a request to your webhook's URL, you can verify that the HMAC signature has correctly signed the body of the request with the shared secret. [block:code] { "codes": [ { "code": "# Calculate an HMAC for the webhook ThisData has sent us\nkey = \"'Secret for Webhook Signatures' key copied from your settings page\"\nbody = request.body.string\ndigest = OpenSSL::Digest.new('sha512')\nhmac = OpenSSL::HMAC.hexdigest(digest, key, body)\n\n# Verify it matches the signature they sent\nif hmac == request.headers[\"X-Signature\"]\n # carry on\nelse\n # someone is pretending to be ThisData\nend", "language": "ruby" }, { "code": " var signature = req.headers['x-signature'];\n var digest = crypto\n .createHmac('sha1', settings.sharedSecret)\n .update(JSON.stringify(req.body))\n .digest('hex');\n\nif (signature === digest) {\n\n // do cool stuff\n\n}", "language": "javascript", "name": "Node" } ] } [/block] [block:api-header] { "type": "basic", "title": "Respond to ThisData's webhook by terminating all current user sessions" } [/block] When a user has said their account has been accessed by someone else, we recommend revoking all current sessions. How this is achieved will depend on how your app implements user sessions. If you use database-backed sessions, delete all current sessions (or invalidate them). Any subsequent requests will be unauthorized because there is no longer a valid session stored in your database. If you use cookie-based sessions only, it's a little more difficult as you can't reach in to someone's browser and remove their cookies. Perhaps the cookie needs to contain some value which matches up with a user to remain valid - if so, update the affected user in your database so that those two values no longer match and the cookie is invalidated. [block:api-header] { "type": "basic", "title": "Change your user's password to something long and random" } [/block] If the user's account is compromised, it is likely to be caused by their email and password being compromised. Invalidating all current sessions is the first step, but an attacker can log in again with the unchanged password. Update the password to something long and random. The old password is now useless, and existing sessions have been neutralize. The attacker can no longer cause any damage. When you get in touch with your affected user, they can use your "reset password" functionality to recover access to their account. [block:callout] { "type": "danger", "title": "Don't use a bad password!", "body": "Update the password to something long and random. It should not be predictable, and your employee's should not be able to figure it out either." } [/block] When you get in touch with your affected user, they can use your "reset password" functionality to recover access to their account. [block:api-header] { "type": "basic", "title": "Notify your support & ops teams" } [/block] The attacker did get in, probably for at least a few seconds. Now it's time to figure out what, if anything, they had time to do. [block:api-header] { "type": "basic", "title": "Get in touch with your customer" } [/block] Email your customer, or ideally phone them, to let them know what is happening. Keep them in the loop. You might not immediately know what the attacker did, but *you* stopped them in their tracks. It's such an amazing experience for them when you front-foot it, show that you're competently handling the situation, and have prevented the worst from happening. ๐Ÿ‘๐Ÿ˜ [block:api-header] { "type": "basic", "title": "Tying it all together with some code:" } [/block] Here is some code for Ruby on Rails which shows us receiving a request, verifying it, and acting upon it by resetting the password, neutralizing all sessions, and emailing our customer with a quick heads-up message. [block:code] { "codes": [ { "code": "class ThisDataWebhooksController < ApplicationController\n\n def receive\n # Calculate an HMAC for the webhook ThisData has sent us\n key = \"'Secret for Webhook Signatures' key copied from your settings page\"\n body = request.body.string\n digest = OpenSSL::Digest.new('sha512')\n hmac = OpenSSL::HMAC.hexdigest(digest, key, body)\n\n if hmac == request.headers[\"X-Signature\"]\n payload = JSON.parse(body)\n # Confirm that the user said it was not them\n if payload[:was_user] == false\n user = User.find(payload[:user][:id])\n # Manually re-set the password. The old compromised password will no longer work.\n new_random_password = SecureRandom.hex(32)\n user.update!(new_password: new_random_password)\n # Find and destroy all the user session records for this user.\n # Any subsequent requests will be unauthorized, and require them to log in again.\n user.user_sessions.destroy_all!\n # Tell the user what we've done\n Mailer.begin_account_restoration(user).deliver!\n end\n end\n\n end\n\nend", "language": "ruby" } ] } [/block]