Skip to Content
Technical Articles
Author's profile photo Piotr Tesny

Hardening access to Kyma APIs with a self-made JWT token.

Mission statement.

Nihil novi sub sole, the WWW is not a very safe place. Thus this is paramount to protect the public API endpoints from unauthorised access.

In Hardening access to Kyma APIs with JWT tokens I demonstrated how to protect public  Kyma APIs with JWT tokens, using SAP BTP XSUAA as OIDC provider.

But sometimes, if on-premise or edge application needed to call into a protected Kyma API, a self-generated JWT might be a simpler approach….

Let’s see how this can be done…

Putting it all together

The client application (the caller) will need to fetch a digitally signed JWT token and pass it in the Authorization header of the API call (the callee).

Subsequently, the digitally signed JWT token will be validated by the callee using the issuer and the jwks_uri URLs as defined in the API Rule  access strategy.

Step1. Generate a digitally signed JWT token
Step2. Create an API Rule.
Step3 and Step4 Test (bootstrap) the API.

Step1. Generate a digitally signed JWT token

The following code snippet demonstrates how to generate a digitally signed JWT token using the standard jsonwebtoken library, namely:

URdaHVX6B270SUDiP6TDPApn9E+IaISzPRpk\nXT6c0QNVYg37DBU/qhSN\n-----END CERTIFICATE-----\n'; 

const key = '-----BEGIN PRIVATE KEY-----\nMIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDFz/eQv30tj5oC\nLjT1Im7OtVAVo6mB/wQbEpbOh3LSI
LSxBTGdiZznqgLKnImxU1WDSA2xlKJy7J\nAwx8lLYgANSJ7qkKPgPR/t5ZHrx/plY=\n-----END PRIVATE KEY-----\n';
const jwt = require('jsonwebtoken');
const { randomUUID } = require('crypto'); // Added in: node v14.17.0

function generateAccessToken(issuer, jwks_uri) {

  return jwt.sign( {iss: issuer, jti: randomUUID() }, key, { algorithm: 'RS256', keyid: 'QtV2kV6VfnvJpRHCHFVXjDPFKydXWVHjuz4XIE0xxxx', header: {jku: jwks_uri}, expiresIn: '3600sec' } );


Step2. Create an API Rule protected with a self-made JWT token.

Let’s create a new API Rule with a JWT access strategy for our  backend Kyma function…

  • The issuer and jwks_uri urls must match the iss and jku attributes of the self-generated JWT token as depicted in the below table
Issuer URL
  • iss: http://poster-app.default.svc.cluster.local
  • jku: http://poster-app.default.svc.cluster.local/jwks_uri
  • In order to achieve a better resilience both URLs point to local kyma cluster resources

  • When trying to call this API Rule directly we are getting the Unauthorized (401) error. This is expected and by design.
{"error":{"code":401,"status":"Unauthorized","request":"<request>","message":"The request could not be authorized"}}

Step3. Bootstrap the API

Let’s have a look at the following code snippet.

That’s the “bootstrapping” code needed to call an API endpoint secured with a digitally signed JWT token.

const axios = require('axios')
const jwt = require('jsonwebtoken');

//The url is the JWT-protected API Rule enpoint
//The logonToken is the self-generated bearer JWT token

  try {
      logonToken = generateAccessToken(
      let decoded = await jwt.decode(logonToken, { complete: true });
      console.log('decoded logonToken:', decoded);
      let configGet = {
        method: 'get',
        url: url,
        headers: { 
          "Authorization": 'Bearer ' + logonToken,
      const response = await axios(configGet);
  catch(error) {
      return error;

Step4. Test the API

Calling the API Rule from the CURL command:

curl -ik https://<hostname>.<domain>/here_location?location=Massy -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsImprdSI6Imh0dHBzOi8vb2VtLWF6dXJlLmF1dGhlbnRpY2F0aW9uLmFwMjEuaGFuYS5vbmRlbWFuZrsKGSwfr6KoREMSrJ7wxp80NgoFFqD8CS2lK4OSoXmIQqWJsRZzCHfa-rKA'

HTTP/2 200 

  "items": [
      "title": "Massy, Île-de-France, France",
      "id": "here:cm:namedplace:20004823",
      "resultType": "locality",
      "localityType": "city",
      "address": {
        "label": "Massy, Île-de-France, France",
        "countryCode": "FRA",
        "countryName": "France",
        "stateCode": "IDF",
        "state": "Île-de-France",
        "county": "Essonne",
        "city": "Massy",
        "postalCode": "91300"

Last but not least…

  • The bootstrapping nodejs code snippet above, with axios as the http client:, is for illustration purposes only.
  • The bootstrapping sequence is fairly simple but may need to be adapted to the caller’s native environment itself: GO, ABAP, JAVA, C++, etc..
  • The JWT generation should be to be done at the edge. As it can be implemented outside of the on premise system that is calling out the Kyma API, there is not much need to adapt it to the caller’s native environment.


It is not that uncommon to self-generate a digitally signed JWT. Different identity platforms literally explain how to do it. There are other libraries that can help you do it as well, for instance…

I hope the approach I demonstrated in this blog post will help you consider SKR – SAP managed k8s cluster a a service as a perfect platform to host and manage extensions to a variety of business applications. This way the only piece of code one will need to adapt to a native environment is the bootstrap sequence. Just imagine your ABAP or COBOL systems extended forever!

Worth mentioning is that SKR does not charge on per API call basis – it is all included in the price of the cluster….or free with SAP BTP free tier…

And once on Kyma side you can get access to ever growing list of SAP BTP services….to be continued…

Breaking news! Partners!

SAP BTP, Kyma runtime (SKR) is now as well part of the Pay-as-you-Go offering for test, demo, and development.




JWKS keys

The JWKS array must contains the public x509 certificate from your certificate keypair as to be able to decode the JWT signed digitally with the private key of the keypair.JWKS must be available on Kyma side via JWKS_URI URL. For the sake of enhanced security and resilience I have made the JWKS available only locally on the Kyma cluster where the callee api is hosted.

  "keys": [
      "kid": "QtV2kV6VfnvJpRHCHFVXjDPFKydXWVHjuz4XIE0xxx",
      "kty": "RSA",
      "alg": "RS256",
      "use": "sig",
      "n": "xc_3kL99LY-aAi409SJuzrVQFaOpgf8EGxKWzody0iPIf39NH8CzCf7kN52B4sKrBJU7ygNIdAebZu_cP2dRQcf7646q2yr0BBVUP8x92FRL
      "e": "AQAB",
      "x5c": [
      "x5t": "DQMOdr9ITZxhd6NYI7l8y6LIdTw",
      "x5t#S256": "NINub7bHeCqT1mEJiQcUdbJtJgSmTeQhw48DGAJ1r1w"

Assigned Tags

      Be the first to leave a comment
      You must be Logged on to comment or reply to a post.