Gate Access API
The Gate Access API provides authenticated access to exclusive content for Colored Key NFT holders. This endpoint implements two alternative authentication system supporting both modern SIWE (Sign-In with Ethereum) and legacy signature verification, with an optional (but active by default) middleware layer providing additional domain validation security.
Overview
While the Token Gate Verification API (covered previously) handles one-time access with email notifications, the Gate Access API focuses on real-time content delivery. Think of it as the difference between unlocking a door (token gate verification) versus actually entering and experiencing what’s inside (gate access). This API serves the actual gated content - whether that’s exclusive music, special messages, or premium features - directly to verified token holders.
The Architecture of Trust
The Gate Access system implements defense in depth through multiple layers of verification:
- Middleware Pre-validation (Optional) - Domain consistency checks before the request reaches the API
- Input Validation - Ensures request data is properly formatted and complete
- Rate Limiting - Prevents abuse and ensures fair access
- Signature Verification - Cryptographic proof of wallet ownership
- Blockchain Verification - Real-time confirmation of token ownership
- Database Checks - Ensures tokens haven’t been previously used (if applicable)
- Content Delivery - Secure transmission of exclusive content
This layered approach means that even if one security measure is bypassed, others remain in place to protect the gated content.
Understanding Middleware Redundancy
The middleware layer represents an interesting architectural decision: it provides security checks that the API also performs. This redundancy isn’t accidental - it’s a deliberate design choice that offers several benefits:
- Performance - Middleware can reject invalid requests before they consume API resources
- Flexibility - The API remains fully functional even if middleware is disabled
- Progressive Enhancement - Systems can start with just the API and add middleware later
- Debugging - Issues can be isolated to either the middleware or API layer
Think of it like having both a security guard at the building entrance (middleware) and keycard access at each office door (API). Either can work alone, but together they provide better security and user experience.
Related Endpoints
Nonce Generation Endpoint
The Gate Access API works in conjunction with the Nonce endpoint for SIWE authentication:
Property | Value |
---|---|
URL | /api/nonce |
Method | GET |
Success Response | { nonce: string } |
Nonce TTL | 5 minutes (configurable) |
Rate Limit | 10 requests per 60 seconds |
Rate Limit Headers | X-RateLimit-Limit , X-RateLimit-Remaining , Retry-After |
The nonce endpoint generates cryptographically secure nonces for SIWE authentication. Each nonce is unique per user identifier and expires after the configured TTL.
Nonce Endpoint Responses
Success Response (200 OK):
{
"nonce": "k8Jd93kdo0Sdk39dkD9dk3mdk93kd9Dk"
}
SIWE Not Enabled (501 Not Implemented):
{
"error": "SIWE not enabled"
}
Rate Limit Exceeded (429 Too Many Requests):
{
"error": "Too many requests",
"limit": 10,
"remaining": 0,
"retryAfter": 45
}
With HTTP headers:
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 0
Retry-After: 45
Internal Server Error (500):
{
"error": "Failed to generate nonce"
}
This error occurs when the nonce generation process fails unexpectedly.
Endpoint Details
Property | Value |
---|---|
URL | /api/gate-access |
Method | POST |
Content-Type | application/json |
Middleware | Optional domain validation layer |
Authentication | SIWE or Legacy signature verification |
Network-Aware Database Operations
This API leverages RitoSwap’s sophisticated network routing infrastructure to ensure all operations target the correct blockchain network and corresponding database table. The system automatically handles network detection and routing, allowing the API to work seamlessly across Ethereum mainnet, Sepolia testnet, and local RitoNet development networks.
Automatic Network Routing
The API uses two key utilities from the prismaNetworkUtils module to handle multichain operations:
Database Operations via getTokenModel()
This function returns a unified interface that automatically routes database queries to the correct network-specific table:
import { getTokenModel } from '@/app/lib/prisma/prismaNetworkUtils'
const tokenModel = getTokenModel()
// Automatically queries token_ethereum, token_sepolia, or token_ritonet
const token = await tokenModel.findUnique({
where: { tokenId }
})
Blockchain Configuration via getChainConfig()
This function provides the correct RPC endpoints and chain metadata for the active network:
import { getChainConfig } from '@/app/lib/prisma/prismaNetworkUtils'
import { createPublicClient, http } from 'viem'
const chainConfig = getChainConfig()
const publicClient = createPublicClient({
chain: chainConfig.chain,
transport: http(chainConfig.transport)
})
Network Detection Priority
The system determines the active network through environment variables in the following order of precedence:
- RitoNet (Local Development) - Active when
NEXT_PUBLIC_RITONET=true
- Sepolia (Testnet) - Active when
NEXT_PUBLIC_SEPOLIA=true
- Ethereum (Mainnet) - Default when no network flags are set
This precedence ensures development and testing environments take priority, preventing accidental mainnet operations during development.
Cross-Network Isolation
Each blockchain network maintains its own isolated database table with identical structure:
- Ethereum Mainnet →
token_ethereum
table - Sepolia Testnet →
token_sepolia
table - RitoNet Local →
token_ritonet
table
This isolation prevents cross-network data conflicts while enabling network-specific optimizations and simplified debugging. The same token ID can exist on different networks representing entirely different assets, and this architecture ensures they never interfere with each other.
The network routing happens transparently to the API logic. Developers write network-agnostic code while the infrastructure handles proper routing behind the scenes. For detailed information about the network utilities, see the Network-Aware Database Utils documentation.
Request Format
The Gate Access API provides authenticated access to exclusive content for Colored Key NFT holders. It supports two authentication modes, modern SIWE (Sign-In with Ethereum) or legacy signature verification, and includes a middleware that runs on every POST to /api/gate-access, enforcing domain validation only when SIWE is enabled and no hardcoded domain is set.
SIWE Authentication Request
interface SiweGateAccessRequest {
address: string; // Ethereum address (0x...)
signature: string; // SIWE message signature
tokenId: number; // Token ID for access
message: string; // Complete SIWE message
nonce: string; // Nonce from /api/nonce endpoint
}
Legacy Authentication Request
interface LegacyGateAccessRequest {
address: string; // Ethereum address (0x...)
signature: string; // Legacy message signature
tokenId: number; // Token ID for access
timestamp: number; // Unix timestamp (milliseconds)
}
Request Parameters
Parameter | Type | Required | Description |
---|---|---|---|
address | string | Yes | Ethereum address claiming token ownership. Format: 0x... (42 characters) |
signature | string | Yes | Cryptographic signature proving address control. Format depends on auth mode. |
tokenId | number | Yes | ID of the Colored Key NFT being used for access |
message | string | SIWE only | Complete EIP-4361 SIWE message including domain, nonce, and statement |
nonce | string | SIWE only | Cryptographic nonce obtained from /api/nonce endpoint |
timestamp | number | Legacy only | Unix timestamp when signature was created. Must be within 5 minutes. |
Authentication Modes
The API intelligently detects which authentication mode to use based on the request parameters:
SIWE Authentication
Sign-In with Ethereum (SIWE)
When SIWE is enabled and the request includes both message
and nonce
fields, the API uses the modern SIWE flow. This provides several security advantages:
- Domain Binding - Messages are tied to specific domains, preventing cross-site attacks
- Nonce Verification - Each authentication attempt requires a fresh nonce
- Standard Format - EIP-4361 compliance ensures wallet compatibility
- Rich Metadata - Messages can include statements, URIs, and chain information
The SIWE flow performs these verification steps:
- Validates the nonce hasn’t been used or expired
- Parses and verifies the SIWE message format
- Confirms the signature matches the message and address
- Checks domain consistency (enhanced by middleware)
Example SIWE message:
ritoswap.com wants you to sign in with your Ethereum account:
0x70997970C51812dc3A010C7d01b50e0d17dc79C8
Access gated content for token #42
URI: https://ritoswap.com
Version: 1
Chain ID: 1
Nonce: k8Jd93kdo0Sdk39dkD9dk3mdk93kd9Dk
Issued At: 2024-03-15T10:30:00.000Z
Response Formats
Success Response
When all verifications pass, the API returns the gated content:
{
"success": true,
"access": "granted",
"content": {
"welcomeText": "Welcome, esteemed key holder!",
"textSubmissionAreaHtml": "<div class='submission-area'>...</div>",
"audioData": {
"headline": "Exclusive Audio",
"imageSrc": "/audio-cover.jpg",
"imageAlt": "Audio cover",
"description": "Exclusive content for token holders",
"title": "Token Holder Music",
"audioSrc": "/audio/exclusive.mp3",
"error": false
},
"styles": ".submission-area { ... }",
"script": "// Interactive features"
}
}
HTTP Status Code: 200 OK
The content object structure may vary based on what exclusive content is configured for token holders.
Partial Success Response
If content generation partially fails (e.g., audio unavailable), the API still grants access but provides fallback content:
{
"success": true,
"access": "granted",
"content": {
"welcomeText": "Welcome, esteemed key holder!",
"textSubmissionAreaHtml": "<div class='submission-area'>...</div>",
"audioData": {
"headline": "Exclusive Audio",
"imageSrc": "/audio-placeholder.jpg",
"imageAlt": "Audio unavailable",
"description": "Audio content temporarily unavailable",
"title": "Token Holder Audio",
"audioSrc": "",
"error": true
},
"styles": ".submission-area { padding: 20px; }",
"script": "console.log('Loaded without audio');",
"audioError": true,
"errorMessage": "Audio temporarily unavailable"
}
}
Note: On audio/content generation failure, the route still returns 200 with fallback data and audioError: true.
This graceful degradation ensures users can still access core content even if some components (like audio) fail to generate. The API will always attempt to provide as much content as possible rather than failing entirely.
Error Responses
The API provides specific error messages for different failure scenarios:
Bad Request
{
"error": "Invalid JSON in request body"
}
HTTP Status Code: 400 Bad Request
Common 400 errors:
"Invalid JSON in request body"
- Malformed JSON in request"Missing or invalid address field"
- Address field missing or not a string"Missing or invalid signature field"
- Signature field missing or not a string"Missing or invalid tokenId field"
- TokenId field missing or not a number"Missing or invalid timestamp field"
- Timestamp missing in legacy flow"Invalid message field"
- Message not a string in SIWE flow"Invalid nonce field"
- Nonce not a string in SIWE flow"Signature expired"
- Legacy timestamp older than 5 minutes"Missing host header"
- Host header missing (middleware only)
Rate Limit Exceeded
{
"error": "Too many requests",
"limit": 5,
"remaining": 0,
"retryAfter": 45
}
HTTP Status Code: 429 Too Many Requests
HTTP Headers:
X-RateLimit-Limit: 5
X-RateLimit-Remaining: 0
Retry-After: 45
The API returns both JSON body fields and HTTP headers to support different client implementations. Use whichever approach works best for your rate limiting logic.
Authentication Failures
{
"error": "Invalid signature"
}
HTTP Status Code: 401 Unauthorized
Common authentication errors:
"Invalid or expired nonce"
- SIWE nonce validation failed"Invalid message format"
- SIWE message parsing failed"Invalid signature"
- Signature doesn’t match address
Authorization Failures
{
"error": "You do not own this token"
}
HTTP Status Code: 403 Forbidden
Common authorization errors:
"You do not own this token"
- Blockchain verification failed"This token has already been used"
- Token previously used for access"Domain mismatch"
- SIWE domain doesn’t match request origin (middleware)
Not Found
{
"error": "Token not found in database"
}
HTTP Status Code: 404 Not Found
This occurs when the token exists on-chain but hasn’t been synchronized to the database.
Internal Server Error
{
"error": "Internal server error"
}
HTTP Status Code: 500 Internal Server Error
This indicates an unexpected exception occurred before or during request handling (e.g., internal dependency crash). Clients should implement exponential backoff and retry logic.
Content Generation Failure (after auth)
Under normal content-generation failures (e.g., audio pipeline issues), the API does not return 500. It responds with 200 OK and provides fallback content:
{
"success": true,
"access": "granted",
"content": {
"welcomeText": "Welcome, esteemed key holder!",
"textSubmissionAreaHtml": "<div class='submission-area'>...</div>",
"audioData": {
"headline": "Exclusive Audio",
"imageSrc": "/audio-placeholder.jpg",
"imageAlt": "Audio unavailable",
"description": "Audio content temporarily unavailable",
"title": "Token Holder Audio",
"audioSrc": "",
"error": true
},
"audioError": true,
"errorMessage": "Audio temporarily unavailable"
}
}
A 500 with "Failed to generate content"
is returned only if both the primary content generation and the built-in fallback construction fail:
{
"error": "Failed to generate content"
}
HTTP Status Code: 500 Internal Server Error
Middleware Layer
The middleware is always active for POST requests to /api/gate-access
, but domain validation only runs under specific SIWE conditions.
Domain Validation Behavior
When SIWE is enabled and there is no hardcoded domain set, the middleware verifies that the domain in the signed SIWE message matches the request’s Host header.
This prevents replay attacks where a valid SIWE message for one domain is reused on another.
Host check happens first — before the request body is read, the middleware verifies that a Host
header is present.
If missing, it responds with 400
and never calls request.json()
.
Middleware logic flow
- If method is not
POST
, skip. - If SIWE is not enabled, skip.
- If
NEXT_PUBLIC_DOMAIN
is set to any value other than missing, empty string, or exactly'false'
, skip domain check. - Read
Host
header — if missing, return400
. - If
message
ornonce
is missing in the body, skip domain check. - Parse JSON body:
- If
request.json()
throws (malformed JSON), log and skip without rejecting.
- If
- Parse SIWE message:
- If it doesn’t match the
" wants you to sign in"
pattern or domain cannot be extracted, skip without rejecting.
- If it doesn’t match the
- Compare extracted SIWE domain to the raw
Host
header (exact string match).- If different, return
403
. - Note: This is a strict match — if
Host
includes a port (e.g.,localhost:3000
) but the SIWE domain does not, it will be treated as a mismatch.
- If different, return
When Domain Validation Runs
All of the following must be true:
- HTTP method is POST
- SIWE is enabled (
NEXT_PUBLIC_ACTIVATE_REDIS === 'true'
and bothUPSTASH_REDIS_API
andUPSTASH_REDIS_API_KEY
are set and not'false'
) - No hardcoded domain (
NEXT_PUBLIC_DOMAIN
is missing, empty, or exactly'false'
) - Request contains both
message
andnonce
in the body - A valid
Host
header is present
If any condition fails, the middleware still passes the request to the API, but without domain checking.
Benefits
- Early Rejection — Invalid SIWE requests are dropped before reading the body when Host is missing.
- Clear Errors — Sends specific responses for missing host or mismatched domain.
- Defense in Depth — Adds protection beyond API-side SIWE validation.
- Performance — Avoids consuming API resources for clearly invalid requests.
Error Responses
Missing Host Header
{ "error": "Missing host header" }
Status: 400 Bad Request
Happens when the request lacks a Host header.
Domain Mismatch
{ "error": "Domain mismatch" }
Status: 403 Forbidden
Happens when the SIWE message’s domain does not exactly match the Host header string.
Middleware Configuration
Example middleware file:
// app/api/gate-access/middleware.ts
// ...domain validation middleware implementation...
export const config = {
matcher: '/api/gate-access'
}
The config
export ensures the middleware applies to /api/gate-access
.
Activation is automatic — it runs for every POST, but domain checking only occurs under the SIWE + conditions above.
Rate Limiting
The Gate Access API implements careful rate limiting to balance security with usability:
Rate Limit Configuration
- Limit: 5 requests per 60 seconds per identifier
- Window: 60-second sliding window based on IP or identifier
- Scope: Specific to the
gateAccess
rate limit namespace
The lower limit (compared to other endpoints) reflects that legitimate users shouldn’t need frequent access to gated content within short time periods. The 60-second window ensures fair access while preventing abuse.
Rate Limit Headers
When rate limited, the API returns both JSON response body fields and HTTP headers:
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 5
X-RateLimit-Remaining: 0
Retry-After: 45
Content-Type: application/json
{
"error": "Too many requests",
"limit": 5,
"remaining": 0,
"retryAfter": 45
}
Clients can use either the headers or the JSON body to implement rate limit handling, depending on their preference.
Rate Limit Strategy
The rate limiting serves multiple purposes:
- Prevents content scraping attempts
- Ensures fair access during high demand
- Protects against denial-of-service attacks
- Maintains quality of service for all users
Security Best Practices
When implementing gate access, follow these security guidelines:
Content Injection Safety
The API returns HTML content that gets injected into your application. Always:
- Sanitize HTML - Use libraries like DOMPurify before rendering
- Sandbox Scripts - Execute any scripts in isolated contexts
- Validate Styles - Ensure CSS doesn’t break your layout
- Content Security Policy - Implement CSP headers to limit execution
Domain Consistency
For SIWE implementations:
- Always use the actual domain users see in their browser
- Don’t hardcode domains unless absolutely necessary
- Let the middleware handle domain validation when possible
- Test thoroughly with different domain configurations
Token State Management
- Cache content appropriately to reduce API calls
- Clear cached content when users disconnect wallets
- Handle token transfers gracefully (ownership can change)
- Implement proper session management for extended access
Error Handling
- Always validate JSON parsing to avoid syntax errors crashing your app
- Implement proper retry logic with exponential backoff for 500 errors
- Respect rate limits using either headers or response body
- Provide clear user feedback for all error scenarios
Testing Strategies
Comprehensive testing ensures reliable gate access functionality:
Unit Testing
Unit Testing
Test individual components of the gate access flow:
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { NextRequest } from 'next/server'
import { POST } from '../route'
describe('Gate Access API', () => {
it('validates input and returns 400 for missing fields', async () => {
const response = await POST(createTestRequest({
address: '0x123',
signature: 'sig'
// Missing tokenId
}))
expect(response.status).toBe(400)
const data = await response.json()
expect(data.error).toContain('Missing or invalid tokenId')
})
it('successfully grants access with SIWE auth', async () => {
// Mock all dependencies
vi.mocked(siweServer.isSiweEnabled).mockReturnValue(true)
vi.mocked(siweServer.verifyNonce).mockResolvedValue(true)
vi.mocked(siweServer.verifySiweMessage).mockResolvedValue({
success: true
})
vi.mocked(verifyMessage).mockResolvedValue(true)
// Mock blockchain and database checks
mockPublicClient.readContract.mockResolvedValue([BigInt(42), true])
mockTokenModel.findUnique.mockResolvedValue({
tokenId: 42,
used: false
})
const response = await POST(createTestRequest({
address: '0x123',
signature: 'sig',
tokenId: 42,
message: 'SIWE message',
nonce: 'test-nonce'
}))
expect(response.status).toBe(200)
const data = await response.json()
expect(data.success).toBe(true)
expect(data.content).toBeDefined()
})
it('enforces rate limiting with proper headers', async () => {
vi.mocked(checkRateLimitWithNonce).mockResolvedValue({
success: false,
limit: 5,
remaining: 0,
reset: Date.now() + 60000
})
const response = await POST(createTestRequest({
address: '0x123',
signature: 'sig',
tokenId: 42
}))
expect(response.status).toBe(429)
expect(response.headers.get('X-RateLimit-Limit')).toBe('5')
expect(response.headers.get('X-RateLimit-Remaining')).toBe('0')
expect(response.headers.get('Retry-After')).toBeDefined()
})
it('handles partial content failure gracefully', async () => {
// Mock successful auth but content generation fails
setupSuccessfulAuth()
vi.mocked(getGatedContent).mockRejectedValue(
new Error('Audio generation failed')
)
const response = await POST(createTestRequest({
address: '0x123',
signature: 'sig',
tokenId: 42,
timestamp: Date.now()
}))
expect(response.status).toBe(200)
const data = await response.json()
expect(data.success).toBe(true)
expect(data.content.audioError).toBe(true)
expect(data.content.welcomeText).toBeDefined()
})
})
Testing Checklist
Ensure comprehensive test coverage for:
- ✅ Both SIWE and legacy authentication flows
- ✅ Input validation for all required fields
- ✅ Proper error responses for malformed JSON
- ✅ Middleware domain validation (when enabled)
- ✅ Middleware host header validation
- ✅ Rate limiting enforcement with headers
- ✅ All error scenarios and status codes (400, 401, 403, 404, 429, 500)
- ✅ Partial content delivery failures
- ✅ Security measures (replay protection, domain checks)
- ✅ Database and blockchain interaction edge cases
Configuration
The Gate Access API behavior is controlled by several environment variables:
SIWE Configuration
# Enable SIWE authentication
NEXT_PUBLIC_ACTIVATE_REDIS=true
UPSTASH_REDIS_API=your-redis-url
UPSTASH_REDIS_API_KEY=your-redis-key
# Optional: Hardcode domain (disables middleware checks)
NEXT_PUBLIC_DOMAIN=ritoswap.com
Content Configuration
The gated content is managed by the getGatedContent()
function, which can be customized to return different content types:
// Example content structure (matches /api/gate-access response)
interface GatedContent {
welcomeText: string;
textSubmissionAreaHtml: string;
audioData?: {
headline: string;
imageSrc: string;
imageAlt: string;
description: string;
title: string;
audioSrc: string;
error: boolean;
};
styles?: string;
script?: string;
// Present when audio falls back
audioError?: boolean;
errorMessage?: string;
}
Troubleshooting
Common Issues and Solutions
“Invalid JSON in request body” error
- Ensure Content-Type header is set to
application/json
- Validate JSON syntax before sending
- Check for trailing commas or unquoted keys
“Domain mismatch” error
- Ensure SIWE message domain matches the request host
- Check for proxy or CDN modifications to the Host header
- Consider hardcoding domain if your setup requires it
“Missing host header” error
- Check reverse proxy configuration
- Ensure proxy forwards the Host header correctly
- Consider using X-Forwarded-Host if needed
Content not displaying
- Verify HTML sanitization isn’t too aggressive
- Check browser console for CSP violations
- Ensure styles are properly scoped to avoid conflicts
- Check for
audioError
flag in partial content scenarios
Intermittent 500 errors
- Check blockchain RPC endpoint reliability
- Monitor database connection pool exhaustion
- Implement proper timeout handling for content generation
- Review server logs for specific error messages
Rate limiting too restrictive
- Adjust limits based on actual usage patterns
- Implement client-side caching to reduce requests
- Consider per-token rate limiting for fairness
- Use rate limit headers for intelligent retry logic
Summary
The Gate Access API represents a sophisticated approach to blockchain-based content gating, combining multiple authentication methods with layered security and graceful error handling. By supporting both modern SIWE and legacy signature verification, it ensures broad compatibility while maintaining high security standards.
The optional middleware layer demonstrates how defense in depth can be achieved without sacrificing flexibility - the API remains fully functional whether the middleware is active or not. This redundancy, combined with comprehensive error handling and partial content delivery capabilities, creates a robust system that gracefully handles edge cases while providing an excellent user experience.
The API’s thoughtful error responses, including proper HTTP status codes and both body and header-based rate limiting information, make it easy to integrate into any client application. The graceful degradation for partial content failures ensures users always receive the best possible experience, even when some components are temporarily unavailable.
Whether you’re building exclusive content experiences, token-gated communities, or premium features for NFT holders, this API provides the secure, flexible foundation needed to deliver content that truly rewards your token holders while protecting your valuable digital assets.