question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Support for SPA/RESTful APIs (with proper JWT Authentication)

See original GitHub issue

Hi.

A few weeks back I moved over to this project from Laravel, since I wanted to start writing my project backends in Python. I have also previously used Flask and FastAPI but did not like the way those libraries structure web applications. This project however does it in a very clean way and is one of the reasons why I like it and want to use it.

However, I have spent countless hours now trying to figure out how to get proper JWT authentication for my Single Page Application, where I will not use views. It is not starting to get tiring trying to get the framework to work the way I want it to and it’s putting my development to a halt.

What I am trying to achieve, is simply getting a working and stable web application with the following routes:

ROUTES = [

    # Server Side Rendered routes - WILL BE HIDDEN CLIENT SIDE

    RouteGroup(
        prefix="/ssr",
        routes=[
            GET('/test',           'HomeController@show').name('ssr.test'),
            POST('/login',         'JWTAuthController@login').name('ssr.login'),
            POST('/register',      'JWTAuthController@register').name('ssr.register'),
        ]
    ),
    RouteGroup(
        prefix="/ssr",
        middleware=['jwt'], # Requires a valid Authorization Bearer Token (JWT)
        routes=[
            POST('/logout',        'JWTAuthController@logout').name('ssr.logout'),
            POST('/refresh',       'JWTAuthController@refresh').name('ssr.refresh'),
            GET('/profile',        'JWTAuthController@me').name('ssr.profile'),
            GET('/getkey',         'APIKeyController@show').name('ssr.getkey'),
            GET('/search/data',    'DataController@show').name('ssr.data.search'),
        ]
    ),

   
    # Public facing API - should only be accessible with an API KEY generated from users profile, and NOT JWT.
    RouteGroup( 
        prefix="/v1",
        middleware=['apikey'], # Requires a valid X-API-KEY header.
        routes=[
            GET('/',             'HomeController@show').name('api.usage'),
            GET('/search/data',  'DataController@show').name('api.data.search'),
        ]
    ),

It would be amazing if something like this could be crafted by doing craft auth:jwt for JWT authentication and craft auth:apikey for API Key authentication.

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:2
  • Comments:12 (7 by maintainers)

github_iconTop GitHub Comments

2reactions
nicolaiprecommented, Oct 29, 2020

Thank you for the fix in PR#360.

I just spent the last two hours debugging and writing a massive reply here, but all of a sudden I managed to get it working with your help! 👍 Very happy with that.

I solved the problem by rewriting the Middleware and creating a JWTAuthProvider class that extends MasoniteFramework/api’s JWTAuthentication.

The problem was that something was being done under the hood since error messages were being returned perfectly fine with Resources, but not with my JWTAuthMiddleware. It seemed that BaseAuthentication.run_authentication never got executed even though the Middelware extended JWTAuthentication and I had to handle exceptions similar to this.

JWTAuthMiddleware.py

"""JWT Authentication Middleware."""

from masonite.request import Request
from masonite.response import Response
from masonite.api.authentication import JWTAuthentication, PermissionScopes
from ...providers.JWTAuthProvider import JWTAuthProvider

class JWTAuthMiddleware(JWTAuthProvider):
    """Middleware To Check If a Request Contains a Valid JWT Token."""

    def __init__(self, request: Request, response: Response):
        """Inject Any Dependencies From The Service Container.

        Arguments:
            request {masonite.request.Request} -- The Masonite request class.
            response {masonite.response.Response} -- The Masonite response class.
        """
        self.request = request
        self.response = response

    def before(self):
        """Run This Middleware Before The Route Executes."""
        self.check_jwt(self.request)

    def after(self):
        """Run This Middleware After The Route Executes."""
        pass

JWTAuthProvider.py


import pendulum
from masonite.request import Request
from masonite.api.exceptions import NoApiTokenFound, ExpiredToken, InvalidToken
from masonite.api.authentication import JWTAuthentication, PermissionScopes

from masonite.api.exceptions import (ApiNotAuthenticated, ExpiredToken, InvalidToken,
                          NoApiTokenFound, PermissionScopeDenied,
                          RateLimitReached)

class JWTAuthProvider(JWTAuthentication):

    def check_jwt(self, request: Request):
        """ Valdate the JWT token specified in Request """
        try:
            self.authenticate(self.request)
        except ApiNotAuthenticated:
            return self.response.json({'error': 'token not authenticated'}, status=500) # TODO: Change status codes...
        except ExpiredToken:
            return self.response.json({'error': 'token has expired'}, status=500)
        except InvalidToken:
            return self.response.json({'error': 'token is invalid'}, status=500)
        except NoApiTokenFound:
            return self.response.json({'error': 'no API token found'}, status=500)
        except PermissionScopeDenied:
            return self.response.json({'error': 'token has invalid scope permissions'}, status=500)
        except RateLimitReached:
            return self.response.json({'error': 'rate limit reached'}, status=500)
        except Exception as e:
            #raise e
            return self.response.json({'error': str(e)}, status=500)

I did not find many tutorials or guides online where Masonite was used for RESTful APIs, so I have created a repo here for anyone who might find this useful.

As a feature request in 3.0: Perhaps a command such as craft auth:jwt that implements functionality like this could be added for those wanting to use Masonite for RESTful APIs.

1reaction
josephmancusocommented, Oct 27, 2020

Ok in gonna take a look at this myself and see what the issue is. It should be working as is

Read more comments on GitHub >

github_iconTop Results From Across the Web

How to secure a REST API using JWT authentication
In this article, we'll cover one very powerful yet simple way to secure a REST API using JSON Web Tokens (JWT), reviewing some...
Read more >
How To Implement API Authentication with JSON Web Tokens ...
In your terminal window, create a directory for the project: mkdir jwt-and-passport-auth. Copy. And ...
Read more >
API keys vs JWT authorization: Which is best? | Algolia Blog
Sometimes, API keys are sufficient; more often, JWT (JSON Web Authorization) offers more protection, ease-of-use, and flexibility.
Read more >
Controlling access to HTTP APIs with JWT authorizers
Currently, only RSA-based algorithms are supported. Validate claims. API Gateway evaluates the following token claims: kid – The token must have a header ......
Read more >
Securing a REST API with JWT, concepts - YouTube
In this episode, we'll show you how to build a secure REST API in Cloud Run using JSON Web Tokens ( JWT )...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found