Technical Articles
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
Add Policies
- 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
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
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
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 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
Great post!! Can you also create one using RS256 algorithm? Does the consumer sends the public key along with the request?
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
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.
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. 🙂