com.atlassian.confluence.content.render.xhtml.migration.exceptions.UnknownMacroMigrationException: The macro 'next_previous_links' is unknown.

Passing Enduser Attributes to the Backend Using JWT

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 token contains,

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 enduser attributes, you enable and configure the JWT implementation in the <APIM_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.

ElementDescriptionDefault Value
<EnableJWTGeneration>
Uncomment <EnableJWTGeneration> property and set the 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> parameter is commented out in the api-manager.xml file. Enable it to add all user claims in the JWT token:

<ClaimsRetrieverImplClass>org.wso2.carbon.apimgt.impl.token.DefaultClaimsRetriever</ClaimsRetrieverImplClass> 

By default, the following are encoded to the JWT:

  • subscriber name
  • application name 
  • API context
  • API version
  • authorized resource owner name

In addition, you can also write your own class by extending the interface org.wso2.carbon.apimgt.impl.token.ClaimsRetriever and implementing the following methods of the interface:

MethodDescription

void init() throws APIManagementException;

Used to perform initialization tasks. Is executed once, right before the very first request.

SortedMap<String,String> getClaims(String endUserName) throws APIManagementException;

Returns a sorted map of claims. The key of the map indicates the user attribute name and the value indicates the corresponding user attribute value. The order in which these keys and values are encoded depends on the ordering defined by the sorted map.

String getDialectURI(String endUserName);

The dialect URI to which the attribute names returned by the getClaims() method are appended to. For example,
if the getClaims method returns {email:user1@wso2.com, gender:male} and the getDialectURI() returns http://wso2.org/claims , the JWT will contain "http://wso2.org/claims/gender":"male","http://wso2.org/claims/email": "user1@wso2.com" as part of the body.

The default implementation (org.wso2.carbon.apimgt.impl.token.DefaultClaimsRetriever) returns the user's attributes defined under the dialect URI http://wso2.org/claims and the JWT will also be encoded with the same dialect URI. The order of encoding the user's attributes is the natural order of the attributes. If no value is specified, no additional claims will be encoded, except the 6 default attributes.

org.wso2.carbon.apimgt.impl.token.DefaultClaimsRetriever
<ConsumerDialectURI>  

The dialect URI under which the user's claims are be looked for. Only works with the default value of the <ClaimsRetrieverImplClass> element defined above.

The JWT token contains all claims define in the <ConsumerDialectURI> element. The default value of this element is http://wso2.org/claims . To get a list of users to be included in the JWT, simply uncomment this element after enabling the JWT. It will include all claims in http://wso2.org/claims to the JWT token.

http://wso2.org/claims
<SignatureAlgorithm>  

The signing algorithm used to sign the JWT. The general format of the JWT is {token infor}.{claims list}.{signature}. When NONE is specified as the algorithm, signing is turned off and the JWT looks as {token infor}.{claims list} with two strings delimited by a period and a period at the end.

This element can have only two values - the default value, which is SHA256withRSA or NONE.

SHA256withRSA

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,

  1. Go to the <APIM_GATEWAY>/repository/conf directory and open the log4j.properties file with a text editor.
  2. Edit the entries for the two loggers as follows:
    #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:

  1. 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 the populateCustomClaims()  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 populateStandardClaims(APIKeyValidationInfoDTO keyValidationInfoDTO, String apiContext, String version)
                throws APIManagementException {
            Map claims = super.populateStandardClaims(keyValidationInfoDTO, apiContext, version);
            boolean isApplicationToken =
                    keyValidationInfoDTO.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 populateCustomClaims(APIKeyValidationInfoDTO keyValidationInfoDTO, String apiContext, String version, String accessToken)
                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;
        }
    }
  2. Build your class and add the JAR file to the <APIM_HOME>/repository/components/lib directory.
  3. Add your class in the <JWTGeneratorImpl> element of the <APIM_HOME>/repository/conf/api-manager.xml file. 

    <JWTConfiguration>
       ....
       <JWTGeneratorImpl>org.wso2.carbon.test.CustomTokenGenerator</JWTGeneratorImpl>
       ....
    </JWTConfiguration>
  4. Set the <EnableJWTGeneration> element to true in the api-manager.xml file. 
  5. 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 <APIM_HOME>/repository/conf/api-manager.xml file as shown below.

<JWTConfiguration>
   ....
   <JWTGeneratorImpl>org.wso2.carbon.apimgt.keymgt.token.URLSafeJWTGenerator</JWTGeneratorImpl>
   ....
</JWTConfiguration>

Setting the expiry time of the JWT

Setting the JWT expiry time has no effect if the 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 <APIM_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 that defined in <TokenCacheExpiry> property in <API-M_HOME>/repository/conf/api-manager.xml file. However, if caching is not enabled for the Gateway and Key Manager nodes, you can set the JWT expiry time by adding the expiry time in seconds in the <JWTExpiryTime> element, which is in the <APIM_HOME>/repository/conf/api-manager.xml file. 

Example:

<APIKeyValidator>
	...
	<JWTExpiryTime>60</JWTExpiryTime>
	...
</APIKeyValidator>
com.atlassian.confluence.content.render.xhtml.migration.exceptions.UnknownMacroMigrationException: The macro 'next_previous_links2' is unknown.