JSON Web Token (JWT) is used to represent claims that are transferred between two parties such as the end-user and the backend.
A claim is an attribute of the user that is mapped to the underlying user store. It is encoded as a JavaScript Object Notation (JSON) object that is used as the payload of a JSON Web Signature (JWS) structure or as the plain text of a JSON Web Encryption (JWE) structure. This enables claims to be digitally signed.
A set of claims is called a dialect (e.g.,
http://wso2.org/claims
). The general format of a JWT is {token infor}.{claims list}.{signature}
. The API implementation uses information such as logging, content filtering and authentication/authorization that is stored in this token. The token is Base64-encoded and sent to the API implementation in a HTTP header variable. The JWT is self-contained and is divided into three parts as the header, the payload, and the signature. For more information on JWT, see JSON Web Token (JWT) Overview.
To authenticate end-users, the API Manager passes attributes of the API invoker to the backend API implementation using JWT. In most production deployments, service calls go through the API Manager or a proxy service. If you enable JWT generation in the API Manager, each API request will carry a JWT to the back-end service. When the request goes through the API manager, the JWT is appended as a transport header to the outgoing message. The back-end service fetches the JWT and retrieves the required information about the user, application, or token.
An example of a JWT is given below:
{ "typ":"JWT", "alg":"NONE" }{ "iss":"wso2.org/products/am", "exp":1345183492181, "http://wso2.org/claims/subscriber":"admin", "http://wso2.org/claims/applicationname":"app2", "http://wso2.org/claims/apicontext":"/placeFinder", "http://wso2.org/claims/version":"1.0.0", "http://wso2.org/claims/tier":"Silver", "http://wso2.org/claims/enduser":"sumedha" }
The above JWT token contains the following information.
JWT Header : The header section declares that the encoded object is a JSON Web Token (JWT) and the JWT is in plaintext, that is not signed using any encryption algorithm.
JWT Claims set :
- "iss" - The issuer of the JWT
- "exp" - The token expiration time
- "http://wso2.org/claims/subscriber" - Subscriber to the API, usually the app developer
- "http://wso2.org/claims/applicationname" - Application through which API invocation is done
- "http://wso2.org/claims/apicontext" - Context of the API
- "http://wso2.org/claims/version" - API version
- "http://wso2.org/claims/tier" - Tier/price band for the subscription
- " http://wso2.org/claims/enduser " - Enduser of the app who's action invoked the API
Let's see how to enable and pass information in the JWT or completely alter the JWT generation logic in the API Manager:
Configuring JWT
Before passing end-user attributes, you enable and configure the JWT implementation in the <API-M_HOME>/repository/conf/api-manager.xml
file. The relevant elements are described below. If you do not configure these elements, they take their default values.
Enable JWT in all Gateway and Key Manager nodes. For more information on setting up a distributed deployment of API Manager, see Distributed Deployment of API Manager.
Element | Description | Default Value | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
<EnableJWTGeneration> | Uncomment <EnableJWTGeneration> property and set this value to true to enable JWT. | false | ||||||||
<JWTHeader> | The name of the HTTP header to which the JWT is attached. | X-JWT-Assertion | ||||||||
<ClaimsRetrieverImplClass> | By default, the <ClaimsRetrieverImplClass>org.wso2.carbon.apimgt.impl.token.DefaultClaimsRetriever</ClaimsRetrieverImplClass> By default, the following are encoded to the JWT:
In addition, you can also write your own class by extending the interface
| org.wso2.carbon.apimgt.impl.token.DefaultClaimsRetriever | ||||||||
<ConsumerDialectURI> | The dialect URI under which the user's claims are being looked for. Only works with the default value of the The JWT token contains all claims defined in the In the Authorization code grant flow, backend JWT token contains claims from OIDC dialect even though <ConsumerDialectURI> has been configured as 'http://wso2.org/claims' in the api-manager.xml. The reason is that claims are taken from AuthorizationGrantCache, which contains the OIDC claim dialect values. And this is happening due to a modification done to avoid the getUserClaimValues call to WSO2 user store during JWT generation. So, AuthorizationGrantCache is used for retrieving user claims. So from the WUM update WSO2-CARBON-PATCH-4.4.0-6289 released on 1st April, 2020, in order to remap the OIDC claims into the configured dialect (by <ConsumerDialectURI> value), in JWTGenerator, new configuration <ConvertClaimsToConsumerDialect> can be added to api-manager.xml under <JWTConfiguration> element with the value 'true'. The default behavior (if this configuration is not added) of this configuration is 'false'. Only if the user adds this new configuration with value 'true', this fix will be applied. <ConvertClaimsToConsumerDialect>true</ConvertClaimsToConsumerDialect> This is done to prevent breaking the scenarios of already existing deployments, who might have configured their use cases according to the previous behaviour of backend JWT token claims. So the previous behaviour can be kept too if required. Please note that this configuration <ConvertClaimsToConsumerDialect> is supported only in a pack which includes the above mentioned WUM update. For more information on updating WSO2 API Manager, see Updating WSO2 API Manager. | http://wso2.org/claims | ||||||||
<SignatureAlgorithm> | The signing algorithm used to sign the JWT. The general format of the JWT is This element can have only two values - the default value, which is SHA256withRSA or NONE. | SHA256withRSA | ||||||||
<EnableX5C> | This is available only as a WUM update and is effective from 15th July 2020 (2020-07-15). For more information on updating WSO2 API Manager, see Updating WSO2 API Manager. Add this property and set it to | false |
You can use TCPMon or API Gateway debug logs to capture JWT token header with end-user details. To enable gateway DEBUG logs for wire messages,
- Go to the
<APIM_GATEWAY>/repository/conf
directory and open thelog4j.properties
file with a text editor. - Edit the entries for the two loggers as follows (remove the # in order to enable debug logs):
log4j.logger.org.apache.synapse.transport.http.headers=DEBUG
log4j.logger.org.apache.synapse.transport.http.wire=DEBUG
Customizing the JWT generation
The JWT that is generated by default (see example above) has predefined attributes that are passed to the backend. These include basic application-specific details, subscription details, and user information that are defined in the JWT generation class that comes with the API Manager by the name org.wso2.carbon.apimgt.keymgt.token.JWTGenerator
. If you want to pass additional attributes to the backend with the JWT or completely change the default JWT generation logic, do the following:
Write your own custom JWT implementation class by extending the default
JWTGenerator
class. A typical example of implementing your own claim generator is given below. It implements thepopulateCustomClaims()
method to generate some custom claims and adds them to the JWT.import org.wso2.carbon.apimgt.keymgt.APIConstants; import org.wso2.carbon.apimgt.keymgt.dto.APIKeyValidationInfoDTO; import org.wso2.carbon.apimgt.keymgt.token.JWTGenerator; import org.wso2.carbon.apimgt.api.*; import java.util.Map; public class CustomTokenGenerator extends JWTGenerator { public Map<String, String> populateStandardClaims(TokenValidationContext validationContext) throws APIManagementException { Map<String, String> claims = super.populateStandardClaims(validationContext); boolean isApplicationToken = validationContext.getValidationInfoDTO().getUserType().equalsIgnoreCase(APIConstants.ACCESS_TOKEN_USER_TYPE_APPLICATION) ? true : false; String dialect = getDialectURI(); if (claims.get(dialect + "/enduser") != null) { if (isApplicationToken) { claims.put(dialect + "/enduser", "null"); claims.put(dialect + "/enduserTenantId", "null"); } else { String enduser = claims.get(dialect + "/enduser"); if (enduser.endsWith("@carbon.super")) { enduser = enduser.replace("@carbon.super", ""); claims.put(dialect + "/enduser", enduser); } } } return claims; } public Map<String, String> populateCustomClaims(TokenValidationContext tokenValidationContext) throws APIManagementException { Long time = System.currentTimeMillis(); String text = "This is custom JWT"; Map map = new HashMap(); map.put("current_timestamp", time.toString()); map.put("messge" , text); return map; } }
Click here for a sample Custom JWT Generator.
- Build your class and add the JAR file to the
<API-M_HOME>/repository/components/lib
directory. Add your class in the
<JWTGeneratorImpl>
element of the<API-M_HOME>/repository/conf/api-manager.xml
file.<JWTConfiguration> .... <JWTGeneratorImpl>org.wso2.carbon.test.CustomTokenGenerator</JWTGeneratorImpl> .... </JWTConfiguration>
- Set the
<EnableJWTGeneration>
element to true in theapi-manager.xml
file. - Restart the server.
Changing the JWT encoding to Base64URL encoding
The default JWT generator, org.wso2.carbon.apimgt.impl.token.JWTGenerator
, encodes the value of the JWT using Base64 encoding. However, for certain apps you might need to have it in Base64URL encoding. To encode the JWT using Base64URL encoding, add the URLSafeJWTGenerator
class in the <TokenGeneratorImpl>
element in the <API-M_HOME>/repository/conf/api-manager.xml
file as shown below.
<JWTConfiguration> .... <JWTGeneratorImpl>org.wso2.carbon.apimgt.keymgt.token.URLSafeJWTGenerator</JWTGeneratorImpl> .... </JWTConfiguration>
Expiry time of the JWT
JWT expiry time depends directly on whether caching is enabled in the Gateway Manager or Key Manager. The WSO2 API-M Gateway caching is enabled by default. However, if required, you can enable or disable the caching for the Gateway Manager or the Key Manager using the <EnableGatewayTokenCache>
or <EnableKeyManagerTokenCache>
elements respectively in the <API-M_HOME>/repository/conf/api-manager.xml
file. If caching is enabled for the Gateway Manager or the Key Manager, the JWT expiry time will be the same as the default cache expiry time .
The claims that are retrieved for the JWT Token generation are cached. The expiry time of these JWT claims can be set by setting the JWTClaimCacheExpiry in the api-manager.xml
, under CacheConfigurations element:
<CacheConfigurations> ... <!-- JWT claims Cache expiry in seconds --> <JWTClaimCacheExpiry>900</JWTClaimCacheExpiry> ... </CacheConfigurations>
When both Gateway and Key Manager caches are disabled , the JWT expiry time can be set by adding the JWTExpiryTime property under APIKeyValidator element in
<API-M_HOME>/repository/conf/api-manager.xml:
<APIKeyValidator> ... <JWTExpiryTime>900</JWTExpiryTime> ... </APIKeyValidator>