2014/12/10
10 Dec, 2014

[Article] Customize JSON Web Token Generation with WSO2 API Manager

  • Sanjeewa Malalgoda
  • Director - Engineering | Architect at WSO2 - WSO2
Archived Content
This article is provided for historical perspective only, and may not reflect current conditions. Please refer to relevant product page for more up-to-date product information and resources.

Introduction

WSO2 API Manager is a complete solution for publishing APIs, creating and managing a developer community, and for scalably routing API traffic. It leverages proven, production-ready, integration, security and governance components from WSO2 Enterprise Service Bus, WSO2 Identity Server, and WSO2 Governance Registry. Moreover, it is powered by WSO2 Business Activity Monitor, thereby making WSO2 API Manager ready for any large-scale deployments right away.

This article will discuss Jason Web Token and its use in API management. It will will mainly focus on how to use an external JAVA implementation for JWT generating logic. In the WSO2 API Manager default product JWT generation, we retrieve basic application specific details, subscription details, and user information available on the system. However, when we have more complex use cases we might need to send more information with JWT. For this, we have an extension point to implement custom JWT generator according to the specific requirement.

Applies to

WSO2 API Manager Version 1.8.0 and above

Table of contents

  1. JSON Web Token
  2. How to use JWT
  3. What is new in WSO2 API Manager
  4. Customize JWT using extension point
  5. Conclusion
For more information on WSO2 API Manager read our case study on how
or read our white paper on

JSON Web Token

A JSON Web Token (JWT) represents claims to be transferred between two parties. The claims in a JWT are 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, enabling the claims to be digitally signed. A JWToken is self-­contained, so when we create one, it will have all the necessary pieces needed inside it.

To authenticate endusers, the API manager passes attributes of the API invoker to the back-end API implementation. JWT is used to represent claims that are transferred between the enduser and the backend. A claim is an attribute of the user that is mapped to the underlying user store. A set of claims are called a dialect (e.g. https://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 header will contain three main components.

What are those pieces? The JWT token string can be divided into three parts.

A header

A payload

A signature

In most production deployments, service calls go through the API manager or a proxy service. If we enable JWT generation in WSO2 API Manager, then each API request will carry a JWT to the back-end service. When the request goes through the API manager, we can append the JWT as a transport header to the outgoing message. So, the back-end service can fetch JWT and retrieve required information about the user, application, or token. There are two kinds of access tokens we use to invoke APIs in WSO2 API Manager.

  1. Application access token: Generate as application owner and there is no associated user for this token (the actual user will be the application owner). In this case, all information will be fetched from the application owner, so the JWT will not have real meaning/usage when we use the application access token.
  2. User access token: User access tokens are always bound to the user who generated token. So, when you access the API with user access token, the JWT will generate user details. On the back-end server side, we can use this to fetch user details.

Sample JWT message

{ 
"iss":"wso2.org/products/am","exp":1407285607264, 
"https://wso2.org/claims/subscriber":"[email protected]", 
"https://wso2.org/claims/applicationid":"2", 
"https://wso2.org/claims/applicationname":"DefaultApplication", 
"https://wso2.org/claims/applicationtier":"Unlimited", 
"https://wso2.org/claims/apicontext":"/t/xxx.xxx/aaa/bbb", 
"https://wso2.org/claims/version":"1.0.0", 
"https://wso2.org/claims/tier":"Unlimited", 
"https://wso2.org/claims/keytype":"PRODUCTION", 
"https://wso2.org/claims/usertype":"APPLICATION", 
"https://wso2.org/claims/enduser":"anonymous", 
"https://wso2.org/claims/enduserTenantId":"1", 
"https://wso2.org/claims/emailaddress":"[email protected]", 
"https://wso2.org/claims/fullname":"xxx", 
"https://wso2.org/claims/givenname":"xxx", 
"https://wso2.org/claims/lastname":"xxx", 
"https://wso2.org/claims/role":"admin,subscriber,Internal/everyone"  
} 

As you can see JWT contain claims associated with

  1. User (enduserTenantId, full name, given name, last name, role, email address, user type)
  2. API (API context, version, tier), Subscription (keytype, subscriber)
  3. Application (application ID, application name, application tier)
However, in some production deployments, we might need to generate custom attributes and embedded to the JWT.

 

How to use JWT

Since JWT is the transport header, we can design our back-end servers to fetch it easily. Let's consider one example to demonstrate how we can use it with WSO2 ESB. For this, we need to engage class mediator, which reads JWT and adds properties in JWT to synapse messagecontext. Therefore, if you are trying to access JWT properties from a proxy service or any other ESB service, first you need to add a class mediator or required logic to the message flow.

Refer to this document (https://docs.wso2.com/display/ESB480/Class+Mediator) to learn more about class mediators in WSO2 ESB. Once we engage a sample class mediator to the message flow, then all properties in JWT can be added to synapse messagecontext. Hence, we can use them as and when required. For this example, let's consider a claim named role that is available inside JWT. You can apply the same for any other property name in the same manner.

What's new in WSO2 API Manager?

In older version of WSO2 API Manager, we had the capability to plugin external claim retriever class. With that, we can generate claims and add them to JWT based on our requirement. However, some complex use cases might need to generate JWT according to custom requirements. Therefore, moving forward, from WSO2 API Manager 1.8.0 onwards, we will allow users to plugin external JWT generation implementation. With this, we can extend JWT generator and implement JWT generation logic there. Moreover, we introduce accessToken, keyValidationInfoDTO, apiContext and version to generateToken() method available in AbstractJWTGenerator class which is implemented by TokenGenerator. With this change, custom JWT generator can generate JWT based on the API access token, key Validation Info DTO, API Context, and API version. We did not change the method signature of populateStandardClaims(). The following have been changed.

  • TokenGenerator.generateToken(APIKeyValidationInfoDTO keyValidationInfoDTO, String apiContext,String version, String accessToken) throws APIManagementException
  • AbstractJWTGenerator.buildBody(APIKeyValidationInfoDTO keyValidationInfoDTO, String apiContext, String version, String accessToken)
  • AbstractJWTGenerator.populateCustomClaims(APIKeyValidationInfoDTO keyValidationInfoDTO, String apiContext,String version, String accessToken)

New method:

public String generateToken(APIKeyValidationInfoDTO keyValidationInfoDTO, String apiContext,String version, String accessToken) throws APIManagementException.

Customoze JWT using extension point

When we need to generate a Sample CustomTokenGenerator code, it would be something like what's shown below. There you can implement your own claim generator. For this example, we will implement populateCustomClaims() and generate some custom claims and add them to JWT.

import org.wso2.carbon.apimgt.impl.APIConstants;
import org.wso2.carbon.apimgt.impl.dto.APIKeyValidationInfoDTO;
import org.wso2.carbon.apimgt.impl.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;
    }
}
 

In the above example, we added a current time stamp value and some random string to JWT body as Build this class and add jar file to /repository/components/lib/ directory of the product. Then add the following element to specify which implementation should be used as the JWTGenerator. This should come under the APIConsumerAuthentication section in api-manager.xml. Also, JWT generation should be enabled at this point.

<APIConsumerAuthentication>
....
<TokenGeneratorImpl>org.wso2.carbon.test.CustomTokenGenerator</TokenGeneratorImpl>
....
</APIConsumerAuthentication>

Restart the server with :

Linux/Unix: sh wso2server.sh Windows: wso2server.bat

When you invoke APIs, you will see access token being printed.

 

Conclusion

JWT is a compact URL-safe means of representing claims to be transferred between two parties. In WSO2 API Manager, we can pass end user/application/subscription details(claims) to the back-end service as JWT. From WSO2 API Manager 1.8.0 onwards, we will allow users to implement custom JWT generator and use it with the product. With this, we can integrate JWT generation with external systems or we can retrieve some parameters/data from external systems. Based on the requirement, we can implement complete JWT generation logic or a part of it, e.g. if we need to include some additional parameter to the default JWT created in the system, we can implement the populateCustomClaims() method only; or if we need to generate the entire JWT, we can implement that as well. Thereby, we can pass the required additional data easily to the back-end with JWT.

 

References

[1] https://openid.net/specs/draft-jones-json-web-token-07.html#anchor3

 

About Author

  • Sanjeewa Malalgoda
  • Director - Engineering | Architect at WSO2
  • WSO2