Overview
JWT (JSON Web Token) authentication provides a stateless, scalable authentication mechanism for Ant Media Server. Unlike traditional tokens, JWTs are self-contained and cryptographically signed, eliminating the need for server-side token storage.
JWT Filter
The JWTFilter class (filter/JWTFilter.java:31) handles JWT validation for REST API endpoints and streaming requests.
How JWT Filter Works
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
String jwtToken = httpRequest.getHeader("Authorization");
// Extract Bearer token
if (jwtToken != null && jwtToken.toLowerCase().startsWith("bearer")) {
jwtToken = jwtToken.substring("Bearer".length()).trim();
}
// Validate JWT
if (!appSettings.isJwtControlEnabled() || checkJWT(jwtToken)) {
chain.doFilter(request, response);
} else {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Invalid App JWT Token");
}
}
Enabling JWT Authentication
Via REST API
curl -X PUT "https://your-server/rest/v2/applications/settings/LiveApp" \
-H "Content-Type: application/json" \
-d '{
"jwtControlEnabled": true,
"jwtSecretKey": "your-secret-key-min-32-chars"
}'
Enable for Streaming
For stream-level JWT authentication:
curl -X PUT "https://your-server/rest/v2/applications/settings/LiveApp" \
-H "Content-Type: application/json" \
-d '{
"playJwtControlEnabled": true,
"jwtSecretKey": "your-secret-key-min-32-chars"
}'
JWT Validation Methods
Ant Media Server supports two JWT validation approaches:
1. Secret Key Validation (HMAC-SHA256)
Validate JWTs signed with a shared secret:
public static boolean isJWTTokenValid(String jwtSecretKey, String jwtToken) {
try {
Algorithm algorithm = Algorithm.HMAC256(jwtSecretKey);
JWTVerifier verifier = JWT.require(algorithm).build();
verifier.verify(jwtToken);
return true;
} catch (JWTVerificationException ex) {
logger.error("JWT token is not valid");
return false;
}
}
See filter/JWTFilter.java:105-121 for the implementation.
2. JWKS Validation (RSA-256)
Validate JWTs using JSON Web Key Sets (JWKS) with public key cryptography:
private static boolean isJWKSTokenValid(String jwksURL, String jwtString) {
try {
DecodedJWT jwt = JWT.decode(jwtString);
JwkProvider provider = new UrlJwkProvider(jwksURL);
Jwk jwk = provider.get(jwt.getKeyId());
Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) jwk.getPublicKey(), null);
algorithm.verify(jwt);
return true;
} catch (JWTVerificationException | JwkException ex) {
logger.error(ex.toString());
return false;
}
}
See filter/JWTFilter.java:85-102 for the implementation.
Configuration
Using Secret Key
Configure a shared secret for HMAC signing:
{
"jwtControlEnabled": true,
"playJwtControlEnabled": true,
"jwtSecretKey": "your-very-secure-secret-key-at-least-32-characters"
}
Using JWKS URL
Configure a JWKS endpoint for RSA validation:
{
"jwtControlEnabled": true,
"playJwtControlEnabled": true,
"jwksURL": "https://your-auth-server.com/.well-known/jwks.json"
}
If both jwksURL and jwtSecretKey are configured, JWKS validation takes precedence. The filter checks JWKS first, then falls back to secret key validation.
Generating JWT Tokens
Basic JWT Token
Generate a JWT with expiration:
public static String generateJwtToken(String jwtSecretKey, long expireDateUnixTimeStampMs) {
Date expireDateType = new Date(expireDateUnixTimeStampMs);
Algorithm algorithm = Algorithm.HMAC256(jwtSecretKey);
return JWT.create()
.withExpiresAt(expireDateType)
.sign(algorithm);
}
See filter/JWTFilter.java:193-213 for implementation.
JWT with Issuer
Include an issuer claim:
String jwtToken = generateJwtToken(
"your-secret-key",
System.currentTimeMillis() + 3600000, // 1 hour
"ant-media-server"
);
Validate with issuer check:
public static boolean isJWTTokenValid(String jwtSecretKey, String jwtToken, String issuer) {
Algorithm algorithm = Algorithm.HMAC256(jwtSecretKey);
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer(issuer)
.build();
verifier.verify(jwtToken);
return true;
}
See filter/JWTFilter.java:125-142.
JWT with Custom Claims
Add custom claims for fine-grained control:
public static String generateJwtToken(
String jwtSecretKey,
long expireDateUnixTimeStampMs,
String claimName,
String claimValue
) {
Date expireDateType = new Date(expireDateUnixTimeStampMs);
Algorithm algorithm = Algorithm.HMAC256(jwtSecretKey);
return JWT.create()
.withExpiresAt(expireDateType)
.withClaim(claimName, claimValue)
.sign(algorithm);
}
Example with subscriber ID:
String token = generateJwtToken(
"secret-key",
System.currentTimeMillis() + 3600000,
"subscriberId",
"[email protected]"
);
Validate with claim:
boolean valid = isJWTTokenValid(
"secret-key",
token,
"subscriberId",
"[email protected]"
);
See filter/JWTFilter.java:156-172 for claim validation.
Using JWT Tokens
REST API Requests
Include JWT in the Authorization header:
curl -X GET "https://your-server/rest/v2/broadcasts/list/0/10" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Stream Playback
Pass JWT as a query parameter:
https://your-server/LiveApp/streams/stream1.m3u8?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
WebRTC Publishing
const webRTCAdaptor = new WebRTCAdaptor({
websocket_url: "wss://your-server/LiveApp/websocket",
mediaConstraints: {
video: true,
audio: true
},
callback: function(info, obj) {
if (info === "initialized") {
// Publish with JWT token
webRTCAdaptor.publish(
streamId,
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
);
}
}
});
Creating JWT Tokens via REST API
Stream-Specific JWT
Create a JWT token for a specific stream:
curl -X POST "https://your-server/rest/v2/broadcasts/{streamId}/jwt-token" \
-H "Content-Type: application/json" \
-d '{
"type": "play",
"expireDate": 1735689600000,
"roomId": ""
}'
Response:
{
"tokenId": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdHJlYW1JZCI6InN0cmVhbTEiLCJ0eXBlIjoicGxheSIsImV4cCI6MTczNTY4OTYwMH0.signature",
"streamId": "stream1",
"expireDate": 1735689600000,
"type": "play"
}
Using ITokenService
Programmatically create JWT tokens:
ITokenService tokenService = getTokenService();
long expireDate = System.currentTimeMillis() + (60 * 60 * 1000); // 1 hour
Token jwtToken = tokenService.createJwtToken(
"stream1", // streamId
expireDate, // expiration
Token.PLAY_TOKEN, // type
null // roomId
);
String tokenId = jwtToken.getTokenId();
See security/ITokenService.java:111 for the interface definition.
JWT Structure
A typical Ant Media Server JWT contains:
{
"alg": "HS256",
"typ": "JWT"
}
Payload
{
"streamId": "stream1",
"type": "play",
"exp": 1735689600,
"iat": 1735686000,
"subscriberId": "[email protected]"
}
Signature
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
Cluster Communication
In cluster mode, nodes use JWT for internal communication:
boolean valid = RestProxyFilter.isNodeCommunicationTokenValid(
httpRequest.getHeader("ClusterAuthorization"),
appSettings.getClusterCommunicationKey(),
httpRequest.getRequestURI()
);
This allows edge nodes to authenticate with origin nodes (see filter/JWTFilter.java:60-62).
Security Best Practices
Use a strong secret key of at least 32 characters. Weak secrets can be brute-forced, compromising all your tokens.
Always validate the JWT expiration claim. Expired tokens should be rejected to prevent replay attacks.
Use HTTPS for all communications. JWTs transmitted over HTTP can be intercepted and used by attackers.
Rotate your JWT secret keys periodically. Key rotation limits the impact of potential key compromise.
For production environments, prefer JWKS with RSA-256 over shared secrets. Public key cryptography provides better security for distributed systems.
Advantages Over Simple Tokens
Stateless Authentication
JWTs don’t require server-side storage:
- No database lookups for validation
- Better scalability in cluster deployments
- Reduced server memory usage
JWTs can carry claims about the user:
- Subscriber ID
- Permissions
- Custom metadata
Cryptographic Security
JWTs are cryptographically signed:
- Tampering detection
- Authenticity verification
- Support for multiple algorithms
Industry Standard
JWT is a widely adopted standard:
- Interoperability with OAuth 2.0 / OpenID Connect
- Extensive library support
- Well-documented security considerations
Integration with Third-Party Auth
JWT authentication allows integration with external identity providers:
Auth0 Integration
- Configure Auth0 JWKS URL:
{
"jwksURL": "https://your-tenant.auth0.com/.well-known/jwks.json"
}
- Include Auth0 token in requests:
fetch('https://your-server/rest/v2/broadcasts', {
headers: {
'Authorization': `Bearer ${auth0Token}`
}
});
Keycloak Integration
- Configure Keycloak JWKS:
{
"jwksURL": "https://keycloak.example.com/realms/your-realm/protocol/openid-connect/certs"
}
- Use Keycloak-issued tokens for API access
Troubleshooting
Invalid JWT Token Errors
Common causes:
- Expired Token: Check the
exp claim
- Wrong Secret: Verify
jwtSecretKey matches
- Malformed Token: Ensure proper JWT format (header.payload.signature)
- Algorithm Mismatch: Verify the signing algorithm
JWKS Validation Failures
- Invalid JWKS URL: Verify the URL is accessible
- Key ID Mismatch: Ensure JWT
kid matches JWKS keys
- Network Issues: Check connectivity to JWKS endpoint
- Certificate Problems: Verify SSL certificates are valid
Ensure the header format is correct:
Authorization: Bearer <token>
Not:
Next Steps