krakend jwt authentication not validating any secure API

1.3k Views Asked by At

I'm using Krakend for jwt authentication for my nodejs server.

In nodejs server,I'm generation jwt token using jsonwebtoken library.

Here without adding any token in request header for secret API's,it's return success.No error is coming.Below is my krakend.json file

{
  "$schema": "https://www.krakend.io/schema/v3.json",
  "version": 3,
  "name": "RestApiGateway",
  "timeout": "300000ms",
  "cache_ttl": "4000s",
  "port": 8080,
  "output_encoding": "json",
  "extra_config": {
    "router": {
      "return_error_msg": true
    }
  },
  "endpoints": [
    {
      "endpoint": "/login_with_signer",
      "method": "POST",
      "extra_config": {
        "security/cors": {
          "allow_origins": [
            "*"
          ],
          "allow_methods": [
            "GET",
            "HEAD",
            "POST"
          ],
          "expose_headers": [
            "Content-Length",
            "Content-Type"
          ],
          "allow_headers": [
            "Origin",
            "Authorization",
            "Content-Type"
          ],
          "max_age": "12h",
          "allow_credentials": false,
          "debug": false
        },
        "auth/signer": {
          "alg": "HS256",
          "kid": "sim2",
          "keys_to_sign": [
            "access_token",
            "refresh_token"
          ],
          "cache": false,
          "jwk_url": "http://localhost:4500/symmetric.json",
          "disable_jwk_security": true,
          "operation_debug": true
        }
      },
      "backend": [
        {
          "url_pattern": "/api/test",
          "method": "POST",
          "host": [
            "http://localhost:4500"
          ]
        }
      ]
    },
    {
      "endpoint": "/login",
      "method": "POST",
      "extra_config": {
        "security/cors": {
          "allow_origins": [
            "*"
          ],
          "allow_methods": [
            "GET",
            "HEAD",
            "POST"
          ],
          "expose_headers": [
            "Content-Length",
            "Content-Type"
          ],
          "allow_headers": [
            "Origin",
            "Authorization",
            "Content-Type"
          ],
          "max_age": "12h",
          "allow_credentials": false,
          "debug": false
        }
      },
      "backend": [
        {
          "url_pattern": "/api/login",
          "method": "POST",
          "host": [
            "http://localhost:4500"
          ]
        }
      ]
    }
  ]
}

my symmetric.json file:

{
    
      "keys": [
        {
            "kid": "sim2",
            "kty": "oct",
            "k": "and0X3NlY3JldGU",
            "alg": "HS256"
        }
      ]
  
  }

Now when I'm calling login_with_signer this api without any authorization header,it didn't throw any type of error.How to solve this?

1

There are 1 best solutions below

0
On BEST ANSWER

Simply put, you have a configuration problem.

According to the documentation, for jwt handling in krakend, you have two different parts :

  1. Generating tokens, Krakend will sign the json object for you and construct a proper JWT. The auth/signer in the extra_config will handle this part for you. It does not verify, but create your jwt

  2. JWT Validation, in this part, you will effectively verify your jwt token. For the routes you want to check the jwt token, you have to specify auth/validator in the extra_config.

So from your krakend configuration file, it is normal login_with_signer endpoint does not check your token. This endpoint has the auth/signer configuration, which signs your token and does not verify it.


For creating the jwt token, you can use multiple approaches, but for the sake of a simple example, I choosed to let krakend handle that for me. A really simple nodejs / express ap

const express=require('express')
const app=express();
app.listen('9000')
app.post("/login",(req,res)=>{
    res.json({
    "access_token": {
        "aud": "http://api.example.com",
        "iss": "https://krakend.io",
        "sub": "1234567890qwertyuio",
        "jti": "mnb23vcsrt756yuiomnbvcx98ertyuiop",
        "roles": ["user", "admin"],
        "exp": 1735689600
    },
    "refresh_token": {
        "aud": "http://api.example.com",
        "iss": "https://krakend.io",
        "sub": "1234567890qwertyuio",
        "jti": "mnb23vcsrt756yuiomn12876bvcx98ertyuiop",
        "exp": 1735689600
    },
    "exp": 1735689600
})
})
app.get('/protected/:id',(req,res)=>{
    console.log(req.params.id)
    res.json({id:req.params.id})
})

So we have two routes in the nodejs app. A login endpoint to handle authentication, I simply return a json object, that I took from krakend documentation. For a real use case, you have to create it dynamically of course, from your users database and other business rules.

And a simple protected route.

Krakend will sign/create the jwt from the login route and check the token when we will try to access the protected route. If you let Krankend handle the jwt signing, you don't need jsonwebtoken package in your nodejs API.

My krakend config file

{
    "$schema": "https://www.krakend.io/schema/v3.json",
    "version": 3,
    "name": "RestApiGateway",
    "timeout": "300000ms",
    "cache_ttl": "4000s",
    "port": 8080,
    "output_encoding": "json",
    "extra_config": {
        "router": {
            "return_error_msg": true
        }
    },
    "endpoints": [
        {
            "endpoint": "/login",
            "method": "POST",
            "extra_config": {
                "auth/signer": {
                    "alg": "HS256",
                    "kid": "sim2",
                    "keys_to_sign": [
                        "access_token",
                        "refresh_token"
                    ],
                    "cache": false,
                    "jwk_local_path": "symmetric.json",
                    "disable_jwk_security": true,
                    "operation_debug": true
                }
            },
            "backend": [
                {
                    "url_pattern": "/login",
                    "method": "POST",
                    "host": [
                        "http://localhost:9000"
                    ]
                }
            ]
        },
        {
            "endpoint": "/protected/{id}",
            "method": "GET",
            "extra_config": {
                "auth/validator": {
                    "alg": "HS256",
                    "jwk_local_path": "symmetric.json",
                    "roles_key": "roles",
                    "roles": [
                        "user",
                        "admin"
                    ],
                    "operation_debug": true,
                    "disable_jwk_security": true,
                    "cache":false
                }
            },
            "backend": [
                {
                    "url_pattern": "/protected/{id}",
                    "method": "GET",
                    "host": [
                        "http://localhost:9000"
                    ]
                }
            ]
        }
    ]
}

I used HS256 symmetric encryption, and jwk_local_path for simplicity, to read the symmetric.json that contains the secret to sign the jwt.

But you can perfectly replace jwk_local_path with jwk_url to get your json file that contains your secret, from any server providing it.

The symmetric.json file

{
    "keys": [
        {
            "kty": "oct",
            "k": "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow",
            "kid": "sim2",
            "alg": "HS256"
        }
    ]
}

One important thing to note, is the use of the kid and alg in auth/signer and auth/validator, they must match correctly with the informations provided in the symmetric.json file. Krakend will use it to handle correctly the secret value in "k"

With my example, the login endpoint will returns, the access token and a refresh token

{
    "access_token": "eyJhbGciOiJIUzI1NiIsImtpZCI6InNpbTIifQ.eyJhdWQiOiJodHRwOi8vYXBpLmV4YW1wbGUuY29tIiwiZXhwIjoxNzM1Njg5NjAwLCJpc3MiOiJodHRwczovL2tyYWtlbmQuaW8iLCJqdGkiOiJtbmIyM3Zjc3J0NzU2eXVpb21uYnZjeDk4ZXJ0eXVpb3AiLCJyb2xlcyI6WyJ1c2VyIiwiYWRtaW4iXSwic3ViIjoiMTIzNDU2Nzg5MHF3ZXJ0eXVpbyJ9.g-JemdLjruDrZg8nMHdGyvm-eclQXaKeLoJrs2My6PU",
    "exp": 1735689600,
    "refresh_token": "eyJhbGciOiJIUzI1NiIsImtpZCI6InNpbTIifQ.eyJhdWQiOiJodHRwOi8vYXBpLmV4YW1wbGUuY29tIiwiZXhwIjoxNzM1Njg5NjAwLCJpc3MiOiJodHRwczovL2tyYWtlbmQuaW8iLCJqdGkiOiJtbmIyM3Zjc3J0NzU2eXVpb21uMTI4NzZidmN4OThlcnR5dWlvcCIsInN1YiI6IjEyMzQ1Njc4OTBxd2VydHl1aW8ifQ.4v36tuYHe4E9gCVO-_asuXfzSzoJdoR0NJfVQdVKidw"
}

After that for your protected route, you can pass the access_token in a Authorization Header : Bearer <access_token_value>

And krakend should handle everything for you. If you don't provide any token, krakend, should returns a 401 http code, or if you provide a valid token, but with roles different from admin or user (for my example), you should receive a 403 http code.

Hope it helps !