Bento check: Securing your Flask routes with JWT decorators

by Sharon Lin

get this check now in Bento: pip3 install bento-cli && bento init

For web developers, setting up authentication for applications can be a nuisance, especially when you have to ensure that your method for transmitting information is secure and efficient. Luckily, there are already packages that handle this for you. Flask, for instance, has several packages that use a JSON Web Token (JWT) to handle verifying users.

JWTs are an open standard (RFC 7519) that allow developers to securely transmit information through a JSON object. Since they use digital signatures, you know that the information contained can be verified and trusted. There’s a couple of methods and encryptions that can be used for implementing JWTs, i.e., using a secret with the HMAC algorithm or private/public key pairs with RSA or ECDSA.

At its core, JWTs allow you to send an encrypted token in place of user information whenever you’re making a request. They’re small enough to send through a URL, POST parameter, or within an HTTP header and they already have all of the required information for user authentication.

Typically, when you’re setting up authentication in Flask you might have a process that looks a bit like this:

JWT Graphic

(Source: Medium)

Users signing up for the app will first go to the registration route where their data is added to a database. Once that’s done, they are redirected to an auth route (in this case the login route) where a JWT is created with a secret. When they try to access protected routes, the JWTs are checked to authenticate the user and authorize them access those routes.

Using Flask JWT packages, developers can specify which routes required authentication. In flask_jwt_extended, for instance, you can use the @jwt_required decorator to check for the required token. You can read more about logout procedures on the flask_jwt_extended docs.

What if you forget to include the decorators on certain routes? The routes won’t check for the Authorization header in the request object, exposing your API endpoints.

The r2c-flask-missing-jwt-token check alerts when you’ve imported a Flask JWT package but don’t have protected routes. Since the packages also allow custom token verification, the check doesn’t count files where methods like JWTManager() or JWT() are called to set up authentication response handlers.

For instance, the check will fire on the following examples since protected routes are missing decorators:

## Missing decorators
import flask_jwt

@app.route('/protected')
def protected():
    return

## Missing decorators in classes
import flask_jwt_extended

class Example(Resource):
    def get():
        return

The check considers these cases acceptable.

## @jwt_required decorator
import flask_jwt

@app.route('/protected')
@jwt_required
def protected():
   return

## Implementing JWT
import flask_jwt

jwt = JWT(app, authenticate, identity)

@app.route('/protected')
def protected():
   return

## Implementing JWTManager
import flask_jwt

jwt = JWTManager()

@app.route('/protected')
def protected():
   return

We used our program analysis platform to verify the check and add support for several more calls that were commonly used for setting up response handling:

  • get_jwt_identity
  • get_current_user
  • create_refresh_token
  • create_access_token
  • set_access_cookies
  • get_jwt_claims
  • verify_jwt_in_request
  • current_identity
  • decode_token

These calls indicate that the check is running on a file likely not to contain protected routes, or that contains routes protected with a custom authentication response handler. By detecting these cases, we can decrease our false positive rate. This in turn ensures that when the check does fire, it will be helpful for developers receiving the alert.

Testing our static analysis on actual projects is important for ensuring that our checks address real issues that developers face, and we make sure to calibrate all of our checks over lots of projects before releasing them. Noisy checks tend to be annoying and counter-productive, so we want to ensure that our checks only fire when they've detected something actionable for the developer.

To test the check, we ran it across 2200 Flask apps on GitHub and found 199 instances across 109 repositories where authentication decorators were missing from protected routes.

Histogram of r2c-flask-missing-jwt-token

If you want to try out this check for yourself, you can access it now on Bento:

$ pip3 install bento-cli && bento init

References

Made with ❤️ by r2c.dev,
a software security startup