Chapter 9: Security Configuration
Overview
Security in WSO2 spans transport layer encryption, authentication, authorization, message-level protection, and threat mitigation. This chapter covers practical configuration for each layer.
Security Layers
┌─────────────────────────────────────────┐
│ 1. Network Security (Firewall, WAF) │
├─────────────────────────────────────────┤
│ 2. Transport Security (TLS/mTLS) │
├─────────────────────────────────────────┤
│ 3. Authentication (OAuth2, JWT, SAML) │
├─────────────────────────────────────────┤
│ 4. Authorization (Scopes, RBAC, XACML) │
├─────────────────────────────────────────┤
│ 5. Message Security (Encryption, Sign) │
├─────────────────────────────────────────┤
│ 6. Threat Protection (Rate limit, WAF) │
└─────────────────────────────────────────┘
Transport Security (TLS)
Keystore Configuration
WSO2 uses Java Keystores (JKS) for TLS certificates.
Default Keystores:
| Keystore | File | Purpose |
|---|---|---|
| Primary | wso2carbon.jks | Server identity (TLS) |
| Truststore | client-truststore.jks | Trusted CA certificates |
| Internal | internal.jks | Internal encryption |
Creating a Production Keystore:
# Generate key pair
keytool -genkey -alias apim.example.com \
-keyalg RSA -keysize 2048 \
-keystore apim.jks -storepass changeit \
-dname "CN=apim.example.com,O=MyOrg,L=London,C=UK" \
-validity 365
# Generate CSR for CA signing
keytool -certreq -alias apim.example.com \
-keystore apim.jks -storepass changeit \
-file apim.csr
# Import CA-signed certificate
keytool -import -alias apim.example.com \
-keystore apim.jks -storepass changeit \
-file apim-signed.crt
# Import to truststore
keytool -import -alias apim.example.com \
-keystore client-truststore.jks -storepass wso2carbon \
-file apim-signed.crt
deployment.toml:
[keystore.primary]
file_name = "apim.jks"
password = "changeit"
alias = "apim.example.com"
key_password = "changeit"
[truststore]
file_name = "client-truststore.jks"
password = "wso2carbon"
[keystore.internal]
file_name = "internal.jks"
password = "changeit"
alias = "internal"
key_password = "changeit"
Mutual TLS (mTLS)
Client and server both verify each other's certificates.
# Enable mTLS on gateway
[transport.https.sslHostConfig.properties]
certificateVerification = "required"
# Options: "required", "optional", "none"
[transport.https.sslHostConfig.certificate]
certificate_key_file = "/path/to/server.key"
certificate_file = "/path/to/server.crt"
certificate_chain_file = "/path/to/ca-chain.crt"
Import client certificate to truststore:
keytool -import -alias client-app \
-keystore client-truststore.jks \
-storepass wso2carbon \
-file client-certificate.crt
TLS Version and Cipher Configuration
# Disable weak protocols
[transport.https.sslHostConfig.properties]
protocols = "+TLSv1.2,+TLSv1.3"
# Restrict cipher suites
[transport.https.sslHostConfig]
ciphers = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384"
OAuth 2.0 Security
Token Types
| Token Type | Description | Recommended For |
|---|---|---|
| Opaque | Random string, validated by Key Manager | Internal APIs |
| JWT | Self-contained, verifiable without calling IS | Distributed systems, microservices |
JWT Token Configuration
# Enable JWT tokens
[apim.jwt]
enable = true
encoding = "base64"
claim_dialect = "http://wso2.org/claims"
header = "X-JWT-Assertion"
signing_algorithm = "SHA256withRSA"
[apim.jwt.token_generation]
impl = "org.wso2.carbon.apimgt.keymgt.token.JWTGenerator"
JWT Token Structure:
{
"header": {
"alg": "RS256",
"typ": "JWT",
"kid": "gateway_certificate_alias"
},
"payload": {
"sub": "admin@carbon.super",
"aud": "http://org.wso2.apimgt/gateway",
"iss": "https://apim.example.com/oauth2/token",
"exp": 1739500800,
"iat": 1739497200,
"jti": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"scope": "read_data write_data",
"application": {
"id": 1,
"name": "MyApp",
"tier": "Unlimited",
"owner": "admin"
}
}
}
Backend JWT Forwarding
Pass a signed JWT to backend services so they can verify the caller without calling back to the gateway.
[apim.jwt]
enable = true
header = "X-JWT-Assertion"
# Backend can validate JWT using gateway's public key
Backend verification (Java example):
String jwtHeader = request.getHeader("X-JWT-Assertion");
DecodedJWT jwt = JWT.decode(jwtHeader);
// Verify signature with gateway's public key
RSAPublicKey publicKey = getGatewayPublicKey();
Algorithm algorithm = Algorithm.RSA256(publicKey, null);
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer("https://apim.example.com/oauth2/token")
.build();
verifier.verify(jwtHeader);
String user = jwt.getSubject();
String scopes = jwt.getClaim("scope").asString();
API Key Security
Simpler alternative to OAuth for low-risk APIs.
Enabling API Keys
Publisher Portal → Select API → Runtime Configuration
→ Application Level Security → API Key → Enable
Using API Keys:
# Via header
curl -H "apikey: eyJ4NXQiOiJNell4TW1Ga09..." \
https://gateway:8243/employees/1.0.0/employees
# Via query parameter
curl "https://gateway:8243/employees/1.0.0/employees?apikey=eyJ4NXQiOiJNell4TW1Ga09..."
API Key vs OAuth 2.0
| Feature | API Key | OAuth 2.0 |
|---|---|---|
| Complexity | Simple | Moderate |
| Token refresh | No (long-lived) | Yes (refresh token) |
| Scopes | No | Yes |
| User context | No | Yes |
| Revocation | Manual | API call |
| Best for | Internal, low-risk | External, sensitive |
Scope-Based Authorization
Defining Scopes
# In API definition
x-wso2-security:
scopes:
- name: employee:read
description: Read employee data
roles: hr_viewer, hr_admin
- name: employee:write
description: Create/update employees
roles: hr_admin
- name: employee:delete
description: Delete employees
roles: hr_admin
paths:
/employees:
get:
x-wso2-scopes: [employee:read]
post:
x-wso2-scopes: [employee:write]
/employees/{id}:
delete:
x-wso2-scopes: [employee:delete]
Requesting Scoped Tokens
# Request token with specific scopes
curl -X POST https://localhost:9443/oauth2/token \
-H "Authorization: Basic <base64(key:secret)>" \
-d "grant_type=client_credentials&scope=employee:read employee:write"
# Response includes granted scopes
{
"access_token": "...",
"scope": "employee:read employee:write",
"token_type": "Bearer",
"expires_in": 3600
}
CORS Configuration
[apim.cors]
enable = true
allow_origins = ["https://myapp.com", "https://admin.myapp.com"]
allow_methods = ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"]
allow_headers = ["Authorization", "Content-Type", "X-Requested-With"]
allow_credentials = true
max_age = 3600
Per-API CORS (in Publisher):
API → Runtime Configuration → CORS Configuration
Allow Origins: https://myapp.com
Allow Methods: GET, POST
Allow Headers: Authorization, Content-Type
Threat Protection
Regular Expression Threat Protection
Block injection attacks in headers, query params, and body.
<!-- Threat protection policy -->
<threatProtection>
<header>
<pattern>.*[<>].*</pattern> <!-- Block angle brackets in headers -->
</header>
<queryParam>
<pattern>.*[;'"\\].*</pattern> <!-- Block SQL injection chars -->
</queryParam>
</threatProtection>
JSON Threat Protection
Limit JSON payload characteristics to prevent DoS.
[apim.threat_protection.json]
max_property_count = 100
max_string_length = 10000
max_array_element_count = 500
max_depth = 10
max_key_length = 100
XML Threat Protection
[apim.threat_protection.xml]
dtd_enabled = false
external_entities_enabled = false
max_depth = 30
max_element_count = 10000
max_attribute_count = 50
max_attribute_length = 1024
entity_expansion_limit = 100
max_children_per_element = 500
Rate Limiting as Security
# Global rate limits
[apim.throttling]
enable_data_publishing = true
enable_policy_deploy = true
# IP-based blocking
[apim.throttling.blacklist_condition]
enabled = true
# Burst control
[apim.throttling.properties]
enable_header_based_throttling = true
enable_jwt_claim_based_throttling = true
enable_query_param_based_throttling = true
Custom Rate Limit Policy (per-resource):
Publisher → API → Resources → GET /employees
Throttling Policy: Custom
Request Count: 100
Time Unit: minute
Condition Groups:
- Header "X-Client-Type" = "mobile" → 50 per minute
- IP Range 10.0.0.0/8 → 500 per minute
Security for Micro Integrator
Securing REST APIs
<api name="SecuredAPI" context="/secured" xmlns="http://ws.apache.org/ns/synapse">
<resource methods="GET" uri-template="/data">
<inSequence>
<!-- Validate API key from header -->
<property name="apiKey" expression="get-property('transport', 'X-API-Key')"/>
<filter xpath="$ctx:apiKey != 'expected-secret-key'">
<then>
<payloadFactory media-type="json">
<format>{"error": "Unauthorized"}</format>
</payloadFactory>
<property name="HTTP_SC" value="401" scope="axis2"/>
<respond/>
</then>
</filter>
<!-- Process authenticated request -->
<call>
<endpoint key="BackendEndpoint"/>
</call>
<respond/>
</inSequence>
</resource>
</api>
Securing with JWT Validation
<api name="JWTSecuredAPI" context="/jwt-secured" xmlns="http://ws.apache.org/ns/synapse">
<resource methods="GET" uri-template="/data">
<inSequence>
<property name="authHeader" expression="get-property('transport', 'Authorization')"/>
<!-- Extract JWT token -->
<script language="js">
<![CDATA[
var auth = mc.getProperty('authHeader');
if (auth && auth.startsWith('Bearer ')) {
mc.setProperty('jwtToken', auth.substring(7));
} else {
mc.setProperty('jwtToken', '');
}
]]>
</script>
<!-- Validate JWT -->
<class name="com.example.JWTValidatorMediator"/>
<filter xpath="$ctx:jwtValid != 'true'">
<then>
<payloadFactory media-type="json">
<format>{"error": "Invalid or expired token"}</format>
</payloadFactory>
<property name="HTTP_SC" value="401" scope="axis2"/>
<respond/>
</then>
</filter>
<call>
<endpoint key="BackendEndpoint"/>
</call>
<respond/>
</inSequence>
</resource>
</api>
Security Best Practices Checklist
Production Hardening
| Task | Configuration |
|---|---|
| Change default passwords | super_admin.username, super_admin.password |
| Replace default keystore | Generate proper certs, never use wso2carbon.jks in production |
| Disable HTTP transport | Remove port 8280, force HTTPS only |
| Enable TLS 1.2+ only | Disable SSLv3, TLS 1.0, TLS 1.1 |
| Restrict management console | Bind to internal network IP only |
| Enable audit logging | [audit_log] enable = true |
| Disable unused features | Remove unnecessary Carbon features |
| Set password policies | Min length, complexity, expiry |
Disable HTTP (Force HTTPS)
# Remove or comment out HTTP listener
# [[transport.http]]
# listener.port = 8280
# Only keep HTTPS
[[transport.https]]
listener.port = 8243
Restrict Management Console Access
[admin_console]
enable = true
[transport.https.properties]
# Bind to internal IP only
bind_address = "10.0.1.5"
Password Policies
[identity_mgt.password_policy]
min_length = 12
max_length = 64
pattern = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{12,}$"
error_msg = "Password must be 12+ chars with upper, lower, digit, and special character"
[identity_mgt.account_locking]
enable = true
max_failed_attempts = 5
auto_unlock_time_in_minutes = 30
Security Headers
<!-- Add security headers to all responses -->
<sequence name="securityHeaders" xmlns="http://ws.apache.org/ns/synapse">
<property name="Strict-Transport-Security"
value="max-age=31536000; includeSubDomains"
scope="transport"/>
<property name="X-Content-Type-Options"
value="nosniff"
scope="transport"/>
<property name="X-Frame-Options"
value="DENY"
scope="transport"/>
<property name="Content-Security-Policy"
value="default-src 'self'"
scope="transport"/>
<property name="X-XSS-Protection"
value="1; mode=block"
scope="transport"/>
</sequence>
Key Takeaways
- Never use default keystores or credentials in production
- TLS 1.2+ should be the minimum; disable all older protocols
- JWT tokens are preferred for distributed architectures; opaque tokens for simple setups
- Scope-based authorization maps API operations to fine-grained permissions
- JSON/XML threat protection guards against injection and DoS attacks
- Mutual TLS provides the strongest client authentication
- Security headers prevent common web vulnerabilities
- Rate limiting is both a performance and security control
Next Steps
Continue to Chapter 10: Deployment Patterns to learn about production deployment, clustering, and scaling.