Skip to Content
Technical Articles
Author's profile photo stephen xue

How to Generate HS256 JWT token in API Management

Introduction

Different from generating an OAuth2 token in SAP API Management, there are quite a few ways to generate JWT token in the platform.

From the encryption type perspective, there are two ways:

  • HS256, synchronous algorithm
  • RS256, Asynchronous algorithm

For generating a token, RS256 needs a key-pair while HS256 needs a static string. Therefore, for RS256, the implementation method is a bit straightforward, whereas for HS256, there are a few options for choosing the ‘static string’. It could be:

  • a static string in the Key Value Map;
  • the application key;
  • the application secret;
  • etc.

In the blog below, I will introduce one solution using HS256.

You can directly download policy template from here.

Token Generate Mechanism

Once subscribed an application in the developer portal, an application key and secret will be generated for the service consumer. The consumer should keep the key/secret in a safe place as a sort of credential.

For getting the JWT token, the service consumer provides the application key. In the API Management, after validating the application key, get the client_secret from variable and use the client_secret to generate the JWT token.

For validate the JWT token, the service consumer provides the client_secret and the JWT, of course. The API Management validates the JWT token by using the client_secret provided.

The advantage of the solution is, it gets use of application key/secret pair generated in separated calls(generate/validate) , which is safer than a static string. Compared with RS256,  it doesn’t need a private/public key pair for the development which makes the solution easier.

 

Implementation Steps

Generate JWT Token

Create an echo ifow in CPI.

If you are in Neo, this step can be ignored. However I just found in Cloud Foundry, without an available target endpoint, the API proxy will always result in a HTTP 503 ‘Serviceunavailable’ error. However this sympton doesn’t exist in Neo.

The echo iflow can be very simple as below

This is the configuration of the HTTPS sender adapter. Remove the flag for csrf token.

write the URL down. it will be used later.

Configure API Proxy for generating token

Get into the API portal, create a new proxy

set the echo iflow URL has the target URL.

Alternatively, you can use  https://httpbin.org as the target. Then you do not need to fill up the authentication part. This is how it looks like

click create button.

add a resource for GET as below

 

This is an purely optional step. open the API Designer and make the modification to the YAML script as below

 

this is how the service looks like after the script has been added in the swagger hub style view

Add Policies

  1. Add a ValidAPIKey Policy

Add the policty to the ‘Incoming Request’ stream of Preflow as below

modify the script as below to derive value from HTTP header property: X-Api-Key

 <!--Specify in the APIKey element where to look for the variable containing the api key--> 
<VerifyAPIKey async='true' continueOnError='false' enabled='true' xmlns='http://www.sap.com/apimgmt'>
	<APIKey ref='request.header.X-Api-Key'/>
</VerifyAPIKey>

 

2. Add an Assign Message policy to get key for JWT

Once the ValidAPIKey policy has been successfully processed, it will store the applicaiton key into variable client_secret. The Assign Message policy here derives the secret from the variable and populates it into the variable private.key for generating JWT token in the next step.

make sure the type is for request;

the script is to read value from varible client_secret and assign it to the variable private.key

Note: the variable format is verifyapikey.<the policy name>.client_secret

Since the policy making validation check in API key is CheckAPIKey, the name of variable is is

verifyapikey.CheckAPIKey.client_secret

<!-- This policy can be used to create or modify the standard HTTP request and response messages -->
<AssignMessage async="false" continueOnError="false" enabled="true" xmlns='http://www.sap.com/apimgmt'>
    <!-- Sets a new value to the existing parameter -->
    <AssignVariable>
        <Name>private.key</Name>
        <Ref>verifyapikey.CheckAPIKey.client_secret</Ref>
    </AssignVariable>
    <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
    <AssignTo createNew="false" type="request"/>
</AssignMessage>

 

3. Add a GenerateJWT Policy

This time the stream is Outgoing Response

now in the policy, the agorithm is HS256 as described in the beginning.  A token will be generated based on the value stored in the private.key.

The token will be stored in the variable jwt-variable

This is the script

<!-- Generate JWT TOken -->
<GenerateJWT async="false" continueOnError="false" enabled="true" xmlns="http://www.sap.com/apimgmt">
    <Algorithm>HS256</Algorithm>
    <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
    <SecretKey>
    		<Value ref="private.key"/>
    </SecretKey>
    <ExpiresIn>1h</ExpiresIn>
    <Subject>Sandbox JWT Token</Subject>
    <Issuer>urn://Man in Black</Issuer>
    <Audience>dummy</Audience>
    <Id/>
    <AdditionalClaims>
    		<Claim name="additional-claim-name" type="string">additional-claim-value-goes-here</Claim>
    </AdditionalClaims>
    <OutputVariable>jwt-variable</OutputVariable>
</GenerateJWT>

 

4. Add an Assign Message Policy to put the JWT Token in the response payload

Since in the last step, the generated token is stored in the variable jwt-variable. A policy is needed to duplicate the value into the response payload so that the service consumer can get it.

The stream is Outgoing Response

This is the script

<!-- This policy can be used to create or modify the standard HTTP request and response messages -->
<AssignMessage async="false" continueOnError="false" enabled="true" xmlns='http://www.sap.com/apimgmt'>
	<!-- Sets a new value to the existing parameter -->
	<Set>
		<Payload contentType="application/json">{"token":"{jwt-variable}"}</Payload>
	</Set>
	<IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
	<AssignTo createNew="false" type="response">response</AssignTo>
</AssignMessage>

 

The proxy should be like this now

Note:

if you use the echo iflow URL from CPI, another Basic Auth Policy needs to be added to the end of preflow.

Now, save the proxy and deploy it.

Test Get Token

Before conduct test, you need to publish a product based on the proxy created and subscribe an application to it , in order to get an application key / secret.

choose tab Product, then create

 

fill in a name and choose tab API

choose the API proxy just now deployed

Then publish it.

 

Now, go to developer portal. If you are in the cloud foundry, go to Business Hub Enterprise

choose the product published just now

subsribe an application to it

Fill in a name and the recall URL can be a dummy one

then the application key and secret will be created

record the application key

 

Go to resource tab of the API proxy

 

and you will get the token in the response after Execute button has been clicked.

by this means, we get a token generated from an application key of an applicaiton subsriber dynamiclly.

You can gain the token in your own app or POSTMAN as well. We will use POSTMAN to test the service later.

 

Validate JWT Token

To valid the token, create an another API proxy

 

Add an Assign Messge Policy to assign the private key

since the key is API Secret, we set the HTTP header X-Api-Secret to the variable private.key

Note: the VerifyJWT policy can only get key from private area.

<!-- This policy can be used to create or modify the standard HTTP request and response messages -->
<AssignMessage async="false" continueOnError="false" enabled="true" xmlns='http://www.sap.com/apimgmt'>
	<!-- Sets a new value to the existing parameter -->
    <AssignVariable>
        <Name>private.key</Name>
        <Ref>request.header.X-Api-Secret</Ref>
    </AssignVariable>
	<IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
	<AssignTo createNew="false" type="request"/>
</AssignMessage>

then add the VerifyJWT Policy

This is the script.

Note: Please fill the value based on the GenerateJWT Policy configured.

<!-- Verify JWT TOken -->
<VerifyJWT async="false" continueOnError="false" enabled="true" xmlns="http://www.sap.com/apimgmt">
    <Algorithm>HS256</Algorithm>
    <SecretKey>
		<Value ref="private.key"/>
    </SecretKey>
    <Subject>Sandbox JWT Token</Subject>
    <Issuer>urn://Man in Black</Issuer>
    <Audience>dummy</Audience>
    <AdditionalClaims>
		<Claim name="additional-claim-name" type="string">additional-claim-value-goes-here</Claim>
    </AdditionalClaims>
</VerifyJWT>

 

Add an Assign Message Policy to provide a response

<!-- This policy can be used to create or modify the standard HTTP request and response messages -->
<AssignMessage async="false" continueOnError="false" enabled="true" xmlns='http://www.sap.com/apimgmt'>
	<!-- Sets a new value to the existing parameter -->
	<Set>
		<Payload contentType="application/json" variablePrefix="@" variableSuffix="#">{"name":"JWT Sandbox", "message":"successful"}</Payload>
	</Set>
	<IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
	<AssignTo createNew="false" type="response">response</AssignTo>
</AssignMessage>

 

Unit Test

Positive Test

Get the token by using the application key

 

Fill the JWT Token to the bearer token field of the request in POSTMAN

 

Fill up the API Secret to the HTTP header with property name as below and you will get the mock response.

Negative Test Cases

Use invalid applicaiton key to fetch a JWT token

 

Expired JWT Token

 

Invalid JWT Token

 

Even with a correct JWT token, if the application secret is invalid

 

Conclusion

By using HR256, we can generate a JWT token based on the application secret, then we can get rid of private/public key pair and we do not need to use a static key.

 

GITHUB

Feel free to download the policy templates for both generate and validate JWT from here.

 

Suffix

UML Script Used for the flow chart

@startuml
title HS256 JWT Token Tutoria Process
autonumber
actor consumer
participant "Generate JWT Token" as proxy1 <<API Proxy1>> 
participant "Validate JWT Token" as proxy2 <<API Proxy2>> 


box "API Management" #LightGreen
    participant proxy1
    participant proxy2
endbox
participant "Echo URL or httpbin.org" as dummy

activate consumer
note over consumer: Get **application key**\nand **application secret**\nfrom developer portal

consumer -> proxy1: Retrieve JWT Token request\nwith **application key**
activate proxy1
note over proxy1: Validate the application key
note over proxy1: Get application secret\nfrom framework
note over proxy1: Generate JWT token by\nusing **applicaiton secrete**
proxy1 <-> dummy: Dummy call to the dummy service
proxy1 -> consumer: JWT token generated
deactivate proxy1
consumer -> proxy2: Request with JWT Token and **application secret**
activate proxy2
note over proxy2: Validate JWT Token by\nusing **application secret** provided\nby consumer
proxy2 -> consumer: dummy response
deactivate proxy2
deactivate consumer
@enduml

 

Assigned Tags

      4 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Bhushan L Chanda
      Bhushan L Chanda

      Great post!! Can you also create one using RS256 algorithm? Does the consumer sends the public key along with the request?

      Author's profile photo stephen xue
      stephen xue
      Blog Post Author

      Hi Bhushan

      for RS256, it needs a separated service to generate key. I haven't used this so far. 🙁

      However for RS256, I believe the standard document has provided a tutorial:

      https://help.sap.com/viewer/66d066d903c2473f81ec33acfe2ccdb4/Cloud/en-US/c28be0eab9ba4f95abb56a0ff19085a3.html

      Author's profile photo Bhushan L Chanda
      Bhushan L Chanda

      Thanks Stephen. In my case, what I did was, store the clients Public key in the Key Value map as encrypted. Let the client sign the JWT token and send it to us. Then used VerifyJWT policy to verify the token using the Public key stored in Key Value map. I used Key as APIKey(client_id) and value as Public Key. Not sure if there is a way to read Public Key from the Keystore directly. But this is the best I could think of.

      Author's profile photo stephen xue
      stephen xue
      Blog Post Author

      thanks for your proposal. i was thinking using KVM. however it would be a terdious work to maintain it. the key/secret might be renewed. 🙂