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 signupsPRE_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
-
Register the interceptor
Section titled “Register the interceptor”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)
-
Understand the request payload
Section titled “Understand the request payload”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}}Field Type Description ip_address
string The IP address from which the signup request originated region
string Two-letter country code (ISO 3166-1 alpha-2) city
string City name from which the request originated user_email
string Email address of the user attempting to sign up connection_details
array Authentication method being used for signup -
Implement the endpoint
Section titled “Implement the endpoint”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 requestconst 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 blockedif (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 blockedif (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 purposesconsole.log(`Allowed signup for ${userEmail} from ${ipAddress} (${region})`);// Allow signup to proceedreturn res.json({decision: 'ALLOW'});});/auth/interceptor/pre-signup @app.post('/auth/interceptor/pre-signup')async def pre_signup(request: Request):body = await request.json()interceptor_context = body['interceptor_context']# Extract IP address and region from the requestip_address = interceptor_context['ip_address']region = interceptor_context['region']user_email = interceptor_context['user_email']# Define your IP blocklist (you can also check against a database)blocked_ips = ['203.0.113.24', '198.51.100.42']blocked_regions = ['XX', 'YY'] # Example: blocked region codes# Check if IP is blockedif ip_address in blocked_ips:return {'decision': 'DENY','error': {'message': 'Signups from your IP address are not allowed due to security policy'}}# Check if region is blockedif region in blocked_regions:return {'decision': 'DENY','error': {'message': 'Signups from your location are restricted due to compliance requirements'}}# Log the signup attempt for audit purposesprint(f'Allowed signup for {user_email} from {ip_address} ({region})')# Allow signup to proceedreturn {'decision': 'ALLOW'} -
Test the interceptor
Section titled “Test the interceptor”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
- Modify the
-
Enable the interceptor
Section titled “Enable the interceptor”Once testing is complete, toggle the Enable switch to activate the interceptor in production.
Modify claims in session tokens
Section titled “Modify claims in session tokens”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
-
Register the interceptor
Section titled “Register the interceptor”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
-
Understand the request payload
Section titled “Understand the request payload”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"}]}}} -
Implement the endpoint
Section titled “Implement the endpoint”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 serviceconst userMetadata = await fetchUserMetadata(userId, organizationId);// Build custom claims based on your business logicconst customClaims = {// Subscription informationplan: userMetadata.subscription.plan, // 'free', 'pro', 'enterprise'plan_expires_at: userMetadata.subscription.expiresAt,// Feature entitlementsfeatures: userMetadata.features, // ['analytics', 'api_access', 'advanced_reports']// Organization metadataorg_role: userMetadata.organizationRole, // 'admin', 'member', 'viewer'// Custom attributesdepartment: userMetadata.department,cost_center: userMetadata.costCenter};// Return ALLOW decision with custom claimsreturn res.json({decision: 'ALLOW',response: {claims: customClaims}});} catch (error) {// Log error for debuggingconsole.error('Error fetching user metadata:', error);// Allow authentication to proceed without custom claims// Alternatively, you could DENY if claims are criticalreturn res.json({decision: 'ALLOW'});}});// Example function to fetch user metadataasync function fetchUserMetadata(userId, organizationId) {// Query your database for user subscription and permissions// This is a placeholder - implement your actual data fetching logicreturn {subscription: {plan: 'pro',expiresAt: '2025-12-31T23:59:59Z'},features: ['analytics', 'api_access', 'advanced_reports'],organizationRole: 'admin',department: 'Engineering',costCenter: 'R&D-001'};}/auth/interceptor/pre-session-creation @app.post('/auth/interceptor/pre-session-creation')async def pre_session_creation(request: Request):body = await request.json()interceptor_context = body['interceptor_context']user_id = interceptor_context['user_id']organization_id = interceptor_context['organization_id']try:# Fetch user subscription and permissions from your database# This is where you'd query your own database or serviceuser_metadata = await fetch_user_metadata(user_id, organization_id)# Build custom claims based on your business logiccustom_claims = {# Subscription information'plan': user_metadata['subscription']['plan'],'plan_expires_at': user_metadata['subscription']['expires_at'],# Feature entitlements'features': user_metadata['features'],# Organization metadata'org_role': user_metadata['organization_role'],# Custom attributes'department': user_metadata['department'],'cost_center': user_metadata['cost_center']}# Return ALLOW decision with custom claimsreturn {'decision': 'ALLOW','response': {'claims': custom_claims}}except Exception as error:# Log error for debuggingprint(f'Error fetching user metadata: {error}')# Allow authentication to proceed without custom claims# Alternatively, you could DENY if claims are criticalreturn {'decision': 'ALLOW'}async def fetch_user_metadata(user_id: str, organization_id: str):"""Fetch user metadata from your database"""# Query your database for user subscription and permissions# This is a placeholder - implement your actual data fetching logicreturn {'subscription': {'plan': 'pro','expires_at': '2025-12-31T23:59:59Z'},'features': ['analytics', 'api_access', 'advanced_reports'],'organization_role': 'admin','department': 'Engineering','cost_center': 'R&D-001'} -
Expected response format
Section titled “Expected response format”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"}}} -
Access custom claims in your application
Section titled “Access custom claims in your application”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 tokenconst { user, idToken } = await scalekitClient.authenticateWithCode(code);// Decode the ID token to access custom claimsconst decodedToken = jwt.decode(idToken);console.log(decodedToken.plan); // 'pro'console.log(decodedToken.features); // ['analytics', 'api_access', 'advanced_reports']console.log(decodedToken.org_role); // 'admin' -
Test the interceptor
Section titled “Test the interceptor”Use the Test tab in the dashboard to verify your implementation returns the expected claims.
-
Enable the interceptor
Section titled “Enable the interceptor”Toggle the Enable switch to activate the interceptor and start adding custom claims to all session tokens.