Documentation Index
Fetch the complete documentation index at: https://ngrok.com/docs/llms.txt
Use this file to discover all available pages before exploring further.
When using your own provider API keys (BYOK), you need to add your own authorization layer to prevent unauthorized access. If you’re using AI Gateway API Keys, authorization is built-in—see Securing Endpoints.
The problem
Without authorization, a gateway configured with server-side API keys is open to abuse:
on_http_request:
- type: ai-gateway
config:
providers:
- id: openai
api_keys:
- value: ${secrets.get('openai', 'api-key')}
Anyone who discovers your gateway URL can make unlimited requests using your API keys. You need to add your own authorization layer.
Solution: API key authorization
Require clients to provide an access token that you control. Generate your own tokens, store them in ngrok Vaults & Secrets, and validate them in your Traffic Policy before processing requests.
Create a secret for your access token
Create a vault and secret to store your access token:# Create a vault for gateway secrets
ngrok api vaults create --name "gateway-auth"
# Create a secret with your access token (use a long, random string)
ngrok api secrets create \
--name "access-token" \
--value "your-secret-access-token-here" \
--vault-id "vault_xxxxxxxxxxxxx"
Generate a secure access token using: openssl rand -base64 32
Add authorization to your Traffic Policy
Use an expression to compare the Authorization header against your secret and reject unauthorized requests:on_http_request:
# Reject unauthorized requests
- expressions:
- req.headers['authorization'][0] != 'Bearer ' + secrets.get('gateway-auth', 'access-token')
actions:
- type: custom-response
config:
status_code: 401
headers:
content-type: application/json
body: '{"error": {"message": "Invalid or missing authorization token", "type": "authentication_error"}}'
# Process authorized requests through the gateway
- actions:
- type: ai-gateway
config:
providers:
- id: openai
api_keys:
- value: ${secrets.get('openai', 'api-key')}
- id: anthropic
api_keys:
- value: ${secrets.get('anthropic', 'api-key')}
Update your client configuration
Clients must now include the access token in their requests:import OpenAI from 'openai';
const client = new OpenAI({
baseURL: "https://your-gateway.ngrok.app/v1",
apiKey: "your-secret-access-token-here" // Your gateway access token
});
const response = await client.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: "Hello!" }]
});
Or with curl:curl https://your-gateway.ngrok.app/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-secret-access-token-here" \
-d '{
"model": "gpt-4o",
"messages": [{"role": "user", "content": "Hello!"}]
}'
Multiple access tokens
Support multiple clients with different access tokens. Use set-vars to define the list of valid tokens, then check membership with in:
on_http_request:
# Define valid authorization headers
- actions:
- type: set-vars
config:
vars:
- valid_auth_headers:
- ${'Bearer ' + secrets.get('gateway-auth', 'client-a-token')}
- ${'Bearer ' + secrets.get('gateway-auth', 'client-b-token')}
- ${'Bearer ' + secrets.get('gateway-auth', 'client-c-token')}
# Reject if token is not in the valid list
- expressions:
- "!(req.headers['authorization'][0] in vars.valid_auth_headers)"
actions:
- type: custom-response
config:
status_code: 401
headers:
content-type: application/json
body: '{"error": {"message": "Invalid or missing authorization token", "type": "authentication_error"}}'
- actions:
- type: ai-gateway
config:
providers:
- id: openai
api_keys:
- value: ${secrets.get('openai', 'api-key')}
This approach makes it easy to add or remove client tokens by updating the list.
If you prefer using an X-API-Key header instead of Authorization: Bearer:
on_http_request:
- expressions:
- req.headers['x-api-key'][0] != secrets.get('gateway-auth', 'access-token')
actions:
- type: custom-response
config:
status_code: 401
headers:
content-type: application/json
body: '{"error": {"message": "Invalid or missing API key", "type": "authentication_error"}}'
- actions:
- type: ai-gateway
config:
providers:
- id: openai
api_keys:
- value: ${secrets.get('openai', 'api-key')}
Configure the OpenAI SDK to use a custom header:
import OpenAI from 'openai';
const client = new OpenAI({
baseURL: "https://your-gateway.ngrok.app/v1",
apiKey: "not-used", // Required but not used
defaultHeaders: {
"X-API-Key": "your-secret-access-token-here"
}
});
Adding rate limiting
Combine authorization with rate limiting for additional protection:
on_http_request:
# Reject unauthorized requests
- expressions:
- req.headers['authorization'][0] != 'Bearer ' + secrets.get('gateway-auth', 'access-token')
actions:
- type: custom-response
config:
status_code: 401
headers:
content-type: application/json
body: '{"error": {"message": "Invalid or missing authorization token", "type": "authentication_error"}}'
# Apply rate limiting to authorized requests
- actions:
- type: rate-limit
config:
name: ai-gateway-limit
algorithm: sliding_window
capacity: 100
rate: 100/min
bucket_key:
- req.headers['authorization']
# Process through AI Gateway
- actions:
- type: ai-gateway
config:
providers:
- id: openai
api_keys:
- value: ${secrets.get('openai', 'api-key')}
Combining with IP restrictions
For additional security, restrict access to specific IP addresses using CIDR notation:
on_http_request:
# Restrict to specific IPs (CIDR notation required)
- actions:
- type: restrict-ips
config:
enforce: true
allow:
- 203.0.113.0/24 # Allow entire subnet
- 198.51.100.50/32 # Allow single IP
# Reject unauthorized requests
- expressions:
- req.headers['authorization'][0] != 'Bearer ' + secrets.get('gateway-auth', 'access-token')
actions:
- type: custom-response
config:
status_code: 401
headers:
content-type: application/json
body: '{"error": {"message": "Invalid or missing authorization token", "type": "authentication_error"}}'
- actions:
- type: ai-gateway
config:
providers:
- id: openai
api_keys:
- value: ${secrets.get('openai', 'api-key')}
Alternative: JWT validation
For more complex authentication scenarios, use the jwt-validation action to validate JWTs from your identity provider:
on_http_request:
# Validate JWT from your identity provider
- actions:
- type: jwt-validation
config:
issuer:
allow_list:
- value: https://your-auth-provider.com/
audience:
allow_list:
- value: your-ai-gateway
http:
tokens:
- type: access_token
method: header
name: Authorization
prefix: "Bearer "
jws:
allowed_algorithms:
- RS256
keys:
sources:
additional_jkus:
- https://your-auth-provider.com/.well-known/jwks.json
# Process authorized requests
- actions:
- type: ai-gateway
config:
providers:
- id: openai
api_keys:
- value: ${secrets.get('openai', 'api-key')}
Rotating access tokens
To rotate your access token without downtime:
- Add the new token to your secrets
- Update your Traffic Policy to accept both tokens temporarily
- Update clients to use the new token
- Remove the old token from your policy and secrets
# Temporary policy accepting both tokens during rotation
- actions:
- type: set-vars
config:
vars:
- valid_auth_headers:
- ${'Bearer ' + secrets.get('gateway-auth', 'old-token')}
- ${'Bearer ' + secrets.get('gateway-auth', 'new-token')}
- expressions:
- "!(req.headers['authorization'][0] in vars.valid_auth_headers)"
actions:
- type: custom-response
config:
status_code: 401
headers:
content-type: application/json
body: '{"error": {"message": "Invalid or missing authorization token", "type": "authentication_error"}}'
Best practices
- Use strong tokens - Generate access tokens with at least 32 bytes of entropy
- Never share tokens - Issue different tokens to different clients
- Rotate regularly - Change access tokens periodically
- Monitor usage - Use observability to detect unusual patterns
- Layer defenses - Combine authorization with rate limiting and IP restrictions
- Use HTTPS - Always use HTTPS endpoints (ngrok handles this automatically)
Next steps