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
- 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