13 - JWT Authentication at API-Gateway web

Till now we had two end pointsi in API-Gateway-web

  1. Authentication - to validate the user credentials

  2. Registration - Allows any user to register in the application

Today we shall be developing a third end point i.e execute end point which will handle the logic after the customer does a login like getting applicant details,updating applicant details,adding skill of applicants.

Add JWT authentication

Lets first add a method to validate the JWT token which is received when a user inputs username and password.

In the folder API-Gateway/commonlib we added a file auth_bearer_handler.py which will do the duty of autheticating a code. We shall be create a class JWTBearer which shall inhert HTTPBearer class in name space fastapi.security so the properties and methods of HTTPBearer are available in JWTBearer class

We shall add two methods verify_jwt and decode_jwt. The verify_jwt method will in turn call the decode_jwt method. Code is given below

    def verify_jwt(self, jwtoken: str) -> bool:
        is_token_valid: bool = False

        try:
            payload = self.decode_jwt(jwtoken)
        except:
            payload = None
        if payload:
            is_token_valid = True
        return is_token_valid

    @staticmethod
    def decode_jwt(token: str) -> dict:
        try:
            decoded_token = jwt.decode(token.split(' ')[1], config.JWT_SECRET,
                                       algorithms=[config.JWT_ALGORITHM])
            return decoded_token if decoded_token["expires"] >= time.time() else None
        except Exception as  Ex:
            return {}

Add an execute route

Lets add an execute route

We shall add execute.py in routers folder which will have a route execute and a method containing the implementation. Also we would need a model of data accepted and sent as response in execute route. So lets define this under models/execute.py

from pydantic import BaseModel, Json
from typing import Optional


class ExecutePostModel(BaseModel):

    service: str
    path: str
    method: str
    query: Optional[str]
    data: Optional[dict] = {}


class ExecutePostResponseModel(BaseModel):

    data: Optional[Json]
    message: Optional[str]
    status_code: int

The Post model contains

  1. service we are hitting like api-backend,api-core

  2. path - the path of end point

  3. method - specifies if it is GET,POST OR DELETE

  4. query - for GET calls we can specify params like userid or customerid

  5. data - data to be sent for POST methods

The response model contains

  1. data - the data returned by the api end point

  2. message - any error message if the call errors out

  3. status_code - values 200,400,405 based on the execution of call

Following will be implementation of execute route

Below will be main highlights

@router.post("/execute", response_model=execute.ExecutePostResponseModel, dependencies=[Depends(JWTBearer())],
             tags=[router_tag])

dependencies=[Depends(JWTBearer())] handles the dependency injection

if gateway_post.method == 'GET':
        request_response = requests.get(url=url,
                                        headers=headers,
                                        verify=False
                                        )
    elif gateway_post.method == 'POST':
        request_response = requests.post(url=url,
                                         headers=headers,
                                         data=json.dumps(gateway_post.data),
                                         verify=False
                                         )
    elif gateway_post.method == 'DELETE':
        request_response = requests.delete(url=url,
                                           headers=headers,
                                           data=json.dumps(gateway_post.data),
                                           verify=False
                                           )

above lines use the requests library to call the backend service based on GET,POST and DELETE methods

The below code will construct the response model and return the json object

    if request_response.status_code >= 400:
        #logger.debug(message=f"API-Gateway-Web Execute", trace=f"Request='{str(request_response.request.__dict__)}' Response='{str(request_response.__dict__)}")
        print("400 code")

    if request_response.headers.get('content-type') == 'application/json':
        try:
            if request_response.text:
                response_model.data = json.dumps(request_response.json())
        except:
            raise RuntimeError(f"There was an issue trying to decode the response in API-Gateway Execute. Request-Response='{str(request_response.text)}' Request-URL='{request_response.url}'")

    response_model.status_code = request_response.status_code
    response.status_code = request_response.status_code

    return response_model.__dict__

Its time to Test !!!

Since we have now implemented this its time to test

We shall do the following steps

  1. call the auth end point to get the token

  2. call the execute end point with token and data

Step1 :

Calling auth end point. I made a call in POSTMAN and following is the response

Step2:

Calling the execute with the token we recieved in Step1 and payload. We shall try to get applicant details for id 1. So it will be a GET call with query parameters

place where token is added. the type of authorization will be Bearer

Our end point works perfectly and gives us access to many routes.