Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flask-Limiter does not work with Flask-Restful API-based application

I am trying to build a RESTful API app with flask_restful, flask_jwt_extended for user authorization and flask_limiter to limit the quota of a user at 6/minute. My toy/testing code follows (no actual authorization schemes have been implemented yet):

from flask import Flask, make_response
from flask_restful import Api, Resource
from flask_limiter import Limiter
from werkzeug.exceptions import HTTPException
from flask_jwt_extended import jwt_required, create_access_token, JWTManager, get_jwt_identity

# custom HTTP exception
class OutOfQuota(HTTPException):
    code = 402
    name = 'Out Of Quota'

app = Flask(__name__)
limiter = Limiter(app, key_func=get_jwt_identity)

api = Api(prefix='')

class Token(Resource):
    def get(self, user):
        return make_response({'token': create_access_token(identity=user)})

class Test(Resource):
    @jwt_required
    @limiter.limit('6/minute')
    def get(self):
        return make_response({'message': f'OK {get_jwt_identity()}'})

api.add_resource(Token, '/token/<string:user>')
api.add_resource(Test, '/test')

api.init_app(app)

# custom error handler (change "Payment Required" to "Out Of Quota" HTTP status
@app.errorhandler(429)
def ratelimit_handler(e):
    return OutOfQuota(f'quota limit exceeded: {e.description}')

jwt = JWTManager(app)

app.config['JWT_SECRET_KEY'] = 'nothing-fancy-for-now'

if __name__ == '__main__':
    app.run(host='localhost', port=8080)

The endpoint /token generates a JWT token for the user, with their username stored inside as the JWT identity. When the /test endpoint is accessed with this token, I want to check how many times this specific user (i.e. this specific identity) has accessed this endpoint, hence the usage of get_jwt_identity as the key_func of the Limiter.

The problem is that I do not have any limit when I access the /test endpoint when I run the above code; I can access it as many times as I want, as fast as I want. Am I missing something here?

like image 448
Zehanort Avatar asked Oct 16 '22 05:10

Zehanort


1 Answers

I had the similar issue and found a solution that seems to work. Flask-Restful does things a little differently. It creates pluggable views. There's a section in Flask-Limiter docs about this particular case - https://flask-limiter.readthedocs.io/en/stable/#using-flask-pluggable-views

class Test(Resource):

  decorators = [limiter.limit("5/minute")]

  def get(self):
     ...

Instead of providing the decorator via @limiter.limit, we need to provide it this way.

I also had an issue about where to get the reference of limiter from. In some base directory, you can implement this and import in your class.

from flask import Flask
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

_app = Flask(__name__)
limiter = Limiter(
    _app,
    key_func=get_remote_address
)
like image 193
Froyo Avatar answered Oct 18 '22 23:10

Froyo