Skip to content
Scalekit Docs
Talk to an Engineer Dashboard

Interceptors in practice: block signups, add token claims

Authentication interceptors enable you to apply custom business logic at critical points in the authentication flow. This guide demonstrates two common use cases: blocking signups from restricted IP addresses and adding custom claims to session tokens.

These use cases leverage different trigger points to showcase the flexibility of interceptors:

  • PRE_SIGNUP - Runs before a user creates a new organization, ideal for blocking suspicious signups
  • PRE_SESSION_CREATION - Runs before session tokens are issued, perfect for adding custom claims

Block signups from restricted IP addresses

Section titled “Block signups from restricted IP addresses”

Prevent new user signups from specific IP addresses or geographic regions during the registration process. This is useful for:

  • Blocking signups from known malicious IPs or VPNs
  • Enforcing geographic restrictions for compliance
  • Preventing automated bot registrations
  • Implementing domain-based allowlist/blocklist policies
  1. In the Scalekit dashboard, navigate to the Interceptors tab and create a new interceptor:

    • Name: “Block signups from restricted IPs”
    • Trigger point: PRE_SIGNUP
    • Endpoint URL: https://yourdomain.com/auth/interceptor/pre-signup
    • Timeout: 5 seconds
    • Fallback behavior: ALLOW (to avoid blocking legitimate users if your service is down)
  2. When a user attempts to sign up, Scalekit sends a POST request to your endpoint. The request includes IP address and location information in the interceptor_context:

    POST /auth/interceptor/pre-signup
    {
    "display_name": "Block signups from restricted IPs",
    "trigger_point": "PRE_SIGNUP",
    "interceptor_context": {
    "environment_id": "env_92561807204567213",
    "user_email": "john.doe@example.com",
    "connection_details": [
    {
    "id": "conn_92561808744978132",
    "type": "OAUTH",
    "provider": "GOOGLE"
    }
    ],
    "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
    "device_type": "Desktop",
    "ip_address": "203.0.113.24",
    "region": "CN",
    "city": "Beijing",
    "triggered_at": "2025-10-08T15:22:42.381Z"
    },
    "data": {
    // User is null for new signups. If user exists in another org, user details will be present
    "user": null
    }
    }
    FieldTypeDescription
    ip_addressstringThe IP address from which the signup request originated
    regionstringTwo-letter country code (ISO 3166-1 alpha-2)
    citystringCity name from which the request originated
    user_emailstringEmail address of the user attempting to sign up
    connection_detailsarrayAuthentication method being used for signup
  3. Create an endpoint that validates the IP address against your blocklist and returns a decision:

    /auth/interceptor/pre-signup
    app.post('/auth/interceptor/pre-signup', async (req, res) => {
    const { interceptor_context, data } = req.body;
    // Extract IP address and region from the request
    const ipAddress = interceptor_context.ip_address;
    const region = interceptor_context.region;
    const userEmail = interceptor_context.user_email;
    // Define your IP blocklist (you can also check against a database)
    const blockedIPs = ['203.0.113.24', '198.51.100.42'];
    const blockedRegions = ['XX', 'YY']; // Example: blocked region codes
    // Check if IP is blocked
    if (blockedIPs.includes(ipAddress)) {
    return res.json({
    decision: 'DENY',
    error: {
    message: 'Signups from your IP address are not allowed due to security policy'
    }
    });
    }
    // Check if region is blocked
    if (blockedRegions.includes(region)) {
    return res.json({
    decision: 'DENY',
    error: {
    message: 'Signups from your location are restricted due to compliance requirements'
    }
    });
    }
    // Log the signup attempt for audit purposes
    console.log(`Allowed signup for ${userEmail} from ${ipAddress} (${region})`);
    // Allow signup to proceed
    return res.json({
    decision: 'ALLOW'
    });
    });
  4. Navigate to the Test tab in the Scalekit dashboard to verify your implementation:

    • Modify the ip_address field in the test payload to match a blocked IP
    • Click Send request and verify you receive a DENY decision
    • Change the IP to an allowed address and verify you receive an ALLOW decision
  5. Once testing is complete, toggle the Enable switch to activate the interceptor in production.

Add custom claims to access tokens and ID tokens issued by Scalekit. Use this to include:

  • User subscription tier or plan information
  • Feature flags and entitlements
  • Application-specific metadata
  • Custom roles or permissions
  1. Create a new interceptor in the Scalekit dashboard:

    • Name: “Add custom claims to session tokens”
    • Trigger point: PRE_SESSION_CREATION
    • Endpoint URL: https://yourdomain.com/auth/interceptor/pre-session-creation
    • Timeout: 5 seconds
    • Fallback behavior: ALLOW
  2. Scalekit sends user information that you can use to determine which claims to add:

    POST /auth/interceptor/pre-session-creation
    {
    "display_name": "Add custom claims to session tokens",
    "trigger_point": "PRE_SESSION_CREATION",
    "interceptor_context": {
    "environment_id": "env_92561807204567213",
    "user_id": "usr_93418238346728951",
    "user_email": "john.doe@example.com",
    "organization_id": "org_93418204671239864",
    "triggered_at": "2025-10-08T15:22:42.381Z"
    },
    "data": {
    "user": {
    "id": "usr_93418238346728951",
    "name": "John Doe",
    "email": "john.doe@example.com",
    "email_verified": true,
    "memberships": [
    {
    "organization_id": "org_93418204671239864",
    "status": "ACTIVE"
    }
    ]
    }
    }
    }
  3. Create an endpoint that fetches user metadata from your database and returns custom claims:

    /auth/interceptor/pre-session-creation
    app.post('/auth/interceptor/pre-session-creation', async (req, res) => {
    const { interceptor_context, data } = req.body;
    const userId = interceptor_context.user_id;
    const organizationId = interceptor_context.organization_id;
    try {
    // Fetch user subscription and permissions from your database
    // This is where you'd query your own database or service
    const userMetadata = await fetchUserMetadata(userId, organizationId);
    // Build custom claims based on your business logic
    const customClaims = {
    // Subscription information
    plan: userMetadata.subscription.plan, // 'free', 'pro', 'enterprise'
    plan_expires_at: userMetadata.subscription.expiresAt,
    // Feature entitlements
    features: userMetadata.features, // ['analytics', 'api_access', 'advanced_reports']
    // Organization metadata
    org_role: userMetadata.organizationRole, // 'admin', 'member', 'viewer'
    // Custom attributes
    department: userMetadata.department,
    cost_center: userMetadata.costCenter
    };
    // Return ALLOW decision with custom claims
    return res.json({
    decision: 'ALLOW',
    response: {
    claims: customClaims
    }
    });
    } catch (error) {
    // Log error for debugging
    console.error('Error fetching user metadata:', error);
    // Allow authentication to proceed without custom claims
    // Alternatively, you could DENY if claims are critical
    return res.json({
    decision: 'ALLOW'
    });
    }
    });
    // Example function to fetch user metadata
    async function fetchUserMetadata(userId, organizationId) {
    // Query your database for user subscription and permissions
    // This is a placeholder - implement your actual data fetching logic
    return {
    subscription: {
    plan: 'pro',
    expiresAt: '2025-12-31T23:59:59Z'
    },
    features: ['analytics', 'api_access', 'advanced_reports'],
    organizationRole: 'admin',
    department: 'Engineering',
    costCenter: 'R&D-001'
    };
    }
  4. Your endpoint should return custom claims in the response.claims object:

    Response body
    {
    "decision": "ALLOW",
    "response": {
    "claims": {
    "plan": "pro",
    "plan_expires_at": "2025-12-31T23:59:59Z",
    "features": ["analytics", "api_access", "advanced_reports"],
    "org_role": "admin",
    "department": "Engineering",
    "cost_center": "R&D-001"
    }
    }
    }
  5. After authentication, the custom claims are included in both the access token and ID token. You can access them by decoding the tokens:

    Accessing custom claims
    // The claims are automatically included in the ID token
    const { user, idToken } = await scalekitClient.authenticateWithCode(code);
    // Decode the ID token to access custom claims
    const decodedToken = jwt.decode(idToken);
    console.log(decodedToken.plan); // 'pro'
    console.log(decodedToken.features); // ['analytics', 'api_access', 'advanced_reports']
    console.log(decodedToken.org_role); // 'admin'
  6. Use the Test tab in the dashboard to verify your implementation returns the expected claims.

  7. Toggle the Enable switch to activate the interceptor and start adding custom claims to all session tokens.