BlogDocumentation
Products
Identity ProtectionIdentity ManagementBlogDocumentation
Vincenzo Iozzo
Vincenzo Iozzo
10 Oct, 2023
Introduction Hooking the authentication journey Blocking requests coming from Tor IPs Allowing requests through but enforcing step-up auth for Tor Addresses Augmenting the token with marketing intelligence data Asynchronous vs Synchronous hooks Conclusion
Tutorial
Context-aware authentication: fight identity fraud and qualify your users

Knowing your users is becoming increasingly important. Whether you're a B2B PLG business trying to convert leads or a fintech business fending off attacks, it's essential to have more context about who is accessing your platform and to customize your behavior accordingly.

In this article, we show how you can leverage SlashID's webhooks to enrich the authentication context, customize the user journey, block malicious users.

Context-aware authentication: fight identity fraud and qualify your users

Introduction

Knowing your users is becoming increasingly important today both to increase revenue and to fend off attacks.

Just last week 23andMe was breached through a credential stuffing attack. While there are multiple ways to defend against this kind of attacks, one of the key tools to use is better intelligence on bots and potential malicious traffic coming to your website.

At the same time, Product Led Growth (PLG) is an increasingly prevalent paradigm in today’s market. In PLG, [email protected] testing out your dashboard could be the VP at a large company that can sign off on a 6-figure deal for your company.

In this article, we show how we can leverage SlashID’s webhooks to enrich the authentication context and customize the user journey and block malicious users.

Specifically, we’ll demonstrate how to store SlashID JWTs, enrich them with risk scoring and attribution information, and block potentially malicious connections originating from Tor.

Hooking the authentication journey

SlashID supports both synchronous and asynchronous webhooks to hook all interactions between a user and SlashID (for example: authentication attempts, registration, changes in attributes and more).

SlashID events are defined using protobuf. These definitions can be used to generate code for unmarshalling and handling SlashID events (for example, as received in webhook requests).

You can create and manage webhooks using a simple REST API - see the full documentation here.

To attach a webhook to an event, we need to make two REST calls:

  1. Register a webhook
  2. Attach the webhook to an event
curl -X POST --location 'https://api.slashid.com/organizations/webhooks' \
--header 'SlashID-OrgID: <ORGANIZATION ID>' \
--header 'SlashID-API-Key: <API KEY>' \
--header 'Content-Type: application/json' \
--data '{
    "target_url": "https://api.example.com/slashid-webhooks/user-created",
    "name": "user created",
    "description": "Webhook to receive notifications when a new user is created"
}'

{
    "result": {

        "description": "Webhook to receive notifications when a new user is created",
        "id": "16475b49-2b12-78d7-9012-cfe0e174dcd3",
        "name": "user created",
        "target_url": "https://api.example.com/slashid-webhooks/user-created"
    }
}
curl -X POST --location 'https://api.slashid.com/organizations/webhooks/16475b49-2b12-78d7-9012-cfe0e174dcd3/triggers' \
--header 'SlashID-OrgID: <ORGANIZATION ID>' \
--header 'SlashID-API-Key: <API KEY>' \
--header 'Content-Type: application/json' \
--data '{
    "trigger_type": "event",
    "trigger_name": "PersonCreated_v1"
}'

Now we have a webhook, we can test whether the hook is triggered through the test-events endpoint:

curl -X POST --location 'https://api.slashid.com/test-events' \
--header 'SlashID-OrgID: <ORGANIZATION ID>' \
--header 'SlashID-API-Key: <API KEY>' \
--header 'Content-Type: application/json' \
--data '[
    {
        "event_name": "PersonCreated_v1"
    }
]'

If the webhook was registered correctly we’ll receive a call from SlashID.

Validating requests

When handling a request to a webhook, it is essential that you verify the contents of the request before processing it further. With SlashID, we have made this simple by using the JSON Web Token (JWT) and JSON Web Key (JWK) standards. The body of the request to the webhook is a signed and encoded JWT (just like our authentication tokens). In order to verify it, you should first retrieve the verification key JSON Web Key Set (JWKS) for your organization using the API. (Note that this endpoint is rate-limited, so we recommend caching the verification key.) You can then use this key to verify the JWT signature, and decode the body.

Here’s an example on how to validate a webhook request:

...
    jwks_client = jwt.PyJWKClient("https://api.slashid.com/organizations/webhooks/verification-jwks", headers={"SlashID-OrgID": "<ORGANIZATION ID>"})

    request_data = request.get_data()
    try:
        header = jwt.get_unverified_header(request_data)
        key = jwks_client.get_signing_key(header["kid"]).key
        token = jwt.decode(request_data, key, audience="<ORGANIZATION ID>", algorithms=["ES256"])
    except Exception as e:
        raise HTTPException(
          status_code=status.HTTP_401_UNAUTHORIZED,
          detail=f"Token {token} is invalid: {e}",
      )

Blocking requests coming from Tor IPs

Malicious actors often use VPNs and Tor to hide their tracks. Generally, traffic coming through a Tor exit node should receive a higher level of scrutiny - whether by blocking traffic or enforcing further authentication verification such as stronger authentication factor or MFA.

To block authentication attempts coming from Tor IP addresses, we can do the following:

  1. Register a synchronous webhook on token_minted
  2. Check if the IP comes from Tor. You can use multiple services for this; in our example, we’ll use seon.io
  3. Block a request if it comes from Tor

We’ll use the same webhook as in the example above, but now we need to attach it to the token_minted synchronous hook:

curl -X POST --location 'https://api.slashid.com/organizations/webhooks/16475b49-2b12-78d7-9012-cfe0e174dcd3/triggers' \
--header 'SlashID-OrgID: <ORGANIZATION ID>' \
--header 'SlashID-API-Key: <API KEY>' \
--header 'Content-Type: application/json' \
--data '{
    "trigger_type": "sync_hook",
    "trigger_name": "token_minted"
}'

We can process the webhook payload as follows (error checking removed for brevity):

...
    jwks_client = jwt.PyJWKClient("https://api.slashid.com/organizations/webhooks/verification-jwks", headers={"SlashID-OrgID": "<ORGANIZATION ID>"})
    webhookURL = ""

    request_data = request.get_data()
    header = jwt.get_unverified_header(request_data)
    key = jwks_client.get_signing_key(header["kid"]).key
    try:
        header = jwt.get_unverified_header(request_data)
        key = jwks_client.get_signing_key(header["kid"]).key
        token = jwt.decode(request_data, key, audience="<ORGANIZATION ID>", algorithms=["ES256"])

        if token['target_url'] != webhookURL:
            raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail=f"Token {token} is invalid: {e}",
        )
    except Exception as e:
        raise HTTPException(
          status_code=status.HTTP_401_UNAUTHORIZED,
          detail=f"Token {token} is invalid: {e}",
      )

    # Now the request has been validated, let's extract the IP address

    ip_address = token['request_metadata']['client_ip_address']
    print(f"Ip address {ip_address}\n")

    # We use the SlashID external credentials to store the Seon API key
    seon_key = requests.get("https://api.slashid.com/organizations/config/external-credentials/856b7dec-d2c3-41ab-a151-c615925433e0", headers={"SlashID-OrgID": "<ORGANIZATION ID>", "SlashID-API-Key": "<SLASHID KEY>"})
    seon_key = seon_key.json()
    seon_api_key_header = seon_key["result"]["json_blob"]

    r = requests.get(f"https://api.seon.io/SeonRestService/ip-api/v1.1/{ip_address}", headers=seon_api_key_header)
    seon_score = r.json()

    ## Check if the IP address is a known Tor IP address and if so, deny the request
    if seon_score['data']['Tor']:
        return HTTPException(
          status_code=status.HTTP_401_UNAUTHORIZED,
          detail=f"Bearer token {token} is invalid: {e}",
      )

Allowing requests through but enforcing step-up auth for Tor Addresses

We follow the same procedure as above, but instead of returning a 401, we return a custom claim to add to the JWT token that’s returned to the frontend:

...

return {
  "tor_address": True,
}

The JWT returned to the frontend will contain a custom claim tor_address and upon token inspection we can use the <StepUpAuth> component to force another factor if the user’s IP comes from Tor.

Augmenting the token with marketing intelligence data

We can use the same approach as above to enrich the JWT token with marketing intelligence data on the lead - we’ll use clearbit for this (error checking removed for brevity):

...
    jwks_client = jwt.PyJWKClient("https://api.slashid.com/organizations/webhooks/verification-jwks", headers={"SlashID-OrgID": "<ORGANIZATION ID>"})

    request_data = request.get_data()

    try:
        header = jwt.get_unverified_header(request_data)
        key = jwks_client.get_signing_key(header["kid"]).key
        token = jwt.decode(request_data, key, audience="<ORGANIZATION ID>", algorithms=["ES256"])
    except Exception as e:
        raise HTTPException(
          status_code=status.HTTP_401_UNAUTHORIZED,
          detail=f"Token {token} is invalid: {e}",
      )

    # Now the request has been validated, let's extract the user handle (email address/phone number)
    handle = token['authentications'][0]['handle']['value']
    print(f"User handle {handle}\n")

    ip_address = token['request_metadata']['client_ip_address']
    print(f"Ip address {ip_address}\n")

    # We use the SlashID external credentials to store the Clearbit API key
    clearbit_key = requests.get("https://api.slashid.com/organizations/config/external-credentials/856b7dec-d2c3-41ab-a151-c615925444b0", headers={"SlashID-OrgID": "<ORGANIZATION ID>", "SlashID-API-Key": "<SLASHID KEY>"})
    clearbit_key = clearbit_key.json()
    clearbit_api_key_header = clearbit_key["result"]["json_blob"]

    ## Retrieve the person by IP address + email address
    r = requests.get(f"https://person.clearbit.com/v2/people/find?email={handle}&ip_address={ip_address}", headers=clearbit_api_key_header)
    clearbit_response = r.json()

    return {
        "inferred_company": clearbit_response['employment']['name'],
        "inferred_seniority": clearbit_response['employment']['seniority'],
        "inferred_title": clearbit_response['employment']['title'],
    }

The token returned in the frontend will contain the inferred_company, inferred_seniority and inferred_title custom claims. You can then customize the UI flow depending on the persona.

Asynchronous vs Synchronous hooks

As discussed, SlashID exposes a number of events that can be hooked through webhooks. The examples above can also be executed asynchronously using the PersonCreated_v1 (registration) or AuthenticationSucceeded_v1 (authentication) events to collect analytics or trigger workflows (e.g.: an email campaign).

Conclusion

In this brief blog post, we’ve shown how you can combine multiple features of SlashID to make informed decisions about your users - whether it’s a custom conversion flow depending on the buyer persona, or a stronger authentication requirement for a potential bot.

We’d love to hear any feedback you may have. Please contact us at [email protected]! Try out SlashID with a free account.

Related articles

Rate Limiting for Large-scale, Distributed Applications and APIs Using GCRA

Tutorial

/ 16 Oct, 2023

Rate Limiting for Large-scale, Distributed Applications and APIs Using GCRA

Rate limiting is a key defense against bots and threats for APIs and backends. Traditional IP-based rate limiting techniques are insufficient today because they can be easily bypassed.

In this article, we discuss the state of the art when it comes to rate limiting and how we have implemented a modern, distributed, identity-based rate limiting plugin for Gate.

Paulo Costa, Vincenzo Iozzo
Paulo Costa, Vincenzo Iozzo
Paulo Costa, Vincenzo Iozzo
No-code anti-phishing protection of internal apps with Passkeys

Tutorial

/ 18 Sep, 2023

No-code anti-phishing protection of internal apps with Passkeys

Phishing is one of the most common causes of data breaches. According to Verizon's DBIR report, over 50% of incidents start with phishing or stolen credentials. WebAuthn/Passkeys are an effective way to stop phishing and credential stealing attempts on their tracks.

In this article, we’ll show how you can use Gate to enforce Passkeys authentication for users without modifying the application code.

Vincenzo Iozzo
Vincenzo Iozzo
Firewalling OpenAI APIs: Data loss prevention and identity access control

Tutorial

/ 14 Sep, 2023

Firewalling OpenAI APIs: Data loss prevention and identity access control

Large Language Models (LLMs) have taken the world by storm, and they are now used for many tasks by consumers and enterprises alike. However, the risk of accidentally disclosing sensitive data to the models is very high as the recent Samsung case shown.

In this article, we’ll show how you can use Gate to detect sensitive data in requests sent to the OpenAI APIs, as well as enforcing access control so that only users with certain roles can access the APIs.

Vincenzo Iozzo
Vincenzo Iozzo

Ready to start a top-tier security upgrade?

Terms · Privacy · System Status
© 2025 SlashID® Inc. All Rights Reserved.

Products

Identity Protection Identity Management

Resources

Blog Get in touch

We use cookies to improve your experience. Read our cookie policy.