I am using flask restful for my API server, and would like to use flask_jwt in order to secure my endpoints.
This is my endpoint and how I add those to the API server
class Model(Resource):
def get(self):
return 1
api.add_resource(Model, '/path')
I would like to add a simple #jet_required decorator for my API endpoint. How can I get something that is similar to this
#app.route('/protected')
#jwt_required()
def protected():
return '%s' % current_identity
But using flask restful interface?
When I try to use the following, and accessing the endpoint I get this error
class Model(Resource):
#jwt_required
def get(self):
return 1
TypeError: .wrapper..decorator
at 0x7ff5fb262840> is not JSON serializable
127.0.0.1 - - [22/Jan/2020 10:10:28] "GET /resize HTTP/1.1" 500
Made it work using flask_jwt_extended, as in this example
Using the following code
app = Flask(__name__)
api = Api(app)
CORS(app)
app.config['JWT_SECRET_KEY'] = 'jwt-secret-string'
jwt = JWTManager(app)
#app.route('/protected')
#jwt_required()
def protected():
return '%s' % current_identity
That now returns a graceful message in cases of missing authentication header.
Related
I have a flask restful resource like this:
api.add_resource(TrainerById, '/api/trainer/<int:uuid>')
with the source code like this:
class TrainerById(Resource):
def get(self):
data = trainer_by_id_parser.parse_args()
trainer_uuid = data['uuid']
new_trainer = Trainer.find_by_uuid(trainer_uuid)
if not new_trainer:
return {'msg': f"Trainer with uuid {trainer_uuid} not found"}, 401
else:
return {'msg': to_json_trainer(new_trainer)}
I want to return the trainer profile of the trainer with the UUID from the path param, however the issue is that, it returns a 404 whenever I try to access the endpoint like such:
localhost:5000/api/trainer/profile/886313e1-3b8a-5372-9b90-0c9aee199e5d #gives 404
You mixed resourceful routing with argument parsing.
Resourceful Routing are endpoints of an app.
Listed below are the examples of different routes:
localhost:5000/api/trainer/
localhost:5000/api/trainer/profile
localhost:5000/api/trainer/profile/6385d786-ff51-455e-a23f-0699c2c9c26e
localhost:5000/api/trainer/profile/4385d786-ef51-455e-a23f-0c99c2c9c26d
Note that last two can be grouped by using resourceful routing.
RequestParser is Flask-RESTPlus built-in support for request data validation. Those can be querystring or POST form encoded data etc.
With incomplete code you gave, the functionality you want can be implemented like so:
from flask import Flask
from flask_restplus import Resource, Api
app = Flask(__name__)
api = Api(app)
# List of trainers, just basic example instead of DB.
trainers = [
'6385d786-ff51-455e-a23f-0699c2c9c26e',
'7c6d64ae-8334-485f-b402-1bf08aee2608',
'c2a427d5-5294-4fad-bf10-c61018ba49e1'
]
class TrainerById(Resource):
def get(self, trainer_uuid):
# In here, trainer_uuid becomes <class 'uuid.UUID'>, so you can
# convert it to string.
if str(trainer_uuid) in trainers:
return {'msg': f"Trainer with UUID {trainer_uuid} exists"}, 200
else:
return {'msg': f"Trainer with uuid {trainer_uuid} not found"}, 404
# This means after profile/, next expected keyword is UUID with name in route
# as trainer_uuid.
api.add_resource(TrainerById, '/api/trainer/profile/<uuid:trainer_uuid>')
if __name__ == '__main__':
app.run(debug=True)
I'm trying to wrap my Flask server in a class, so as to better fit in the structure of the rest of my application.
I have the below code:
class HTTPServer(object):
def __init__(self):
self.app = Flask(__name__)
self.app.add_url_rule('/', 'index', self.hello_world, methods=['POST'])
self.app.run(port=5050, use_reloader=False)
def hello_world(self, data):
print "Hello, World: {}".format(data)
However, if I send a POST request to localhost:5050/index I get a 404 error.
The Flask log shows the following:
127.0.0.1 - - [30/Aug/2019 11:17:52] "POST /index HTTP/1.1" 404 -
The same happens if I change ['POST'] to ['GET'] in methods and send a GET request.
However, if I remove the methods parameter from add_url_rule() entirely, I can send GET requests and they are handled appropriately.
I didn't understand the endpoint parameter of add_url_rule. It isn't the endpoint as seen by the client, but rather an internal name for the endpoint. The correct method call is:
self.app.add_url_rule('/index', 'hello_world', self.hello_world, methods=['POST'])
I've created an API using flask, where the authentication is all working fine using flask_jwt_extended.
However if I add a resource that has a jwt_required decorator I get this error.
File "/Library/Python/2.7/site-packages/flask_jwt/__init__.py", line 176, in decorator
_jwt_required(realm or current_app.config['JWT_DEFAULT_REALM'])
KeyError: 'JWT_DEFAULT_REALM'
Example resource:
class Endpoint(Resource):
#jwt_required()
def get(self):
return {"State": "Success"}
Initialising the app:
app = Flask(__name__)
api = Api(app)
Adding the resource:
api.add_resource(resource_class, "/myEndpoint")
The only way I can get it to work is to define the Endpoint class in the same file as the API.
I think I need someway to pass the Realm into the endpoint class and have use the optional parameter on jwt_required to set the Realm.
Discovered the issue, in the resource I was importing the jwt_required:
from flask_jwt_extended import jwt_required
However I needed to import jwt_required from the class that where JWT was initalized.
I think you forgot to initialize JWT instance. You can do it in 2 ways. First:
from flask import Flask
from flask_jwt import jwt_required, JWT
from flask_restful import Resource, Api
class Endpoint(Resource):
#jwt_required()
def get(self):
return {"State": "Success"}
app = Flask(__name__)
app.config['SECRET_KEY'] = 'super-secret'
def authenticate(username, password):
# you should find user in db here
# you can see example in docs
user = None
if user:
# do something
return user
def identity(payload):
# custom processing. the same as authenticate. see example in docs
user_id = payload['identity']
return None
# here what you need
jwt = JWT(app, authenticate, identity)
api = Api(app)
api.add_resource(Endpoint, '/myEndpoint')
if __name__ == '__main__':
app.run(debug=True)
app.run(host='0.0.0.0')
The second way is update our configuration of application. Just change:
jwt = JWT(app, authenticate, identity)
To:
app.config.update(
JWT=JWT(app, authenticate, identity)
)
Let's open our route. You will see:
{
"description": "Request does not contain an access token",
"error": "Authorization Required",
"status_code": 401
}
Hope it helps.
I'm currently working on creating a Cookie from an endpoint. As my backend and frontend only interacts via RESTful endpoints, is there anyway I can create a cookie when the frontend calls my backend's endpoint?
flask.make_response.set_cookie() doesn't seem to work for me. Also, I can't use app.route('/') to set my cookie either.
You can do this with Set-Cookie header returning with a response.
from flask import Flask
from flask_restful import Resource, Api
app = Flask(__name__)
api = Api(app)
class HelloWorld(Resource):
def get(self):
return {'task': 'Hello world'}, 200, {'Set-Cookie': 'name=Nicholas'}
api.add_resource(HelloWorld, '/')
if __name__ == '__main__':
app.run(debug=True)
Setting the header in the response tuple is one of the standard approaches. However, keep in mind that the Set-Cookie header can be specified multiple times, which means that a python Dictionary won't be the most effective way to set the cookies in the response.
According to the flask docs the header object can also be initialized with a list of tuples, which might be more convenient in some cases.
Example:
from flask import Flask
from flask_restful import Api, Resource
app = Flask(__name__, static_url_path='')
api = Api(app)
class CookieHeaders(Resource):
def get(self):
# Will only set one cookie "age = 23"
return { 'message' : 'Made with dict'}, 200, { 'Set-Cookie':'name=john', 'Set-Cookie':'age=23' }
def post(self):
# Will set both cookies "name = john" and "age = 23"
headers = [ ('Set-Cookie', 'name=john'), ('Set-Cookie', 'age=23') ]
return { 'message' : ' Made with a list of tuples'}, 200, headers
api.add_resource(CookieHeaders, '/')
if __name__ == '__main__':
app.run(debug=True)
The GET call will only set 1 cookie (due to the lack of multi-key support in python dictionaries), but the POST call will set both.
Flask has a #after_this_request callback decorator. (see: http://flask.pocoo.org/docs/1.0/api/#flask.after_this_request)
so you can set your cookies in it
from flask import after_this_request
from flask_restful import Resource
class FooResource(Resource):
def get(self):
#after_this_request
def set_is_bar_cookie(response):
response.set_cookie('is_bar', 'no', max_age=64800, httponly=True)
return response
return {'data': 'foooo'}
or even
from flask import after_this_request, request
from flask_restful import Resource, abort
class FooResource(Resource):
def get(self):
self._check_is_bar()
return {'data': 'foooo'}
def _check_is_bar(self)
if request.cookies.get('is_bar') == 'yes':
abort(403)
#after_this_request
def set_is_bar_cookie(response):
response.set_cookie('is_bar', 'no', max_age=64800, httponly=True)
return response
I inherited a project that uses flask. This flask application has several APIs, each API has a GET function that returns a json object.
I was asked to implement an additional API that requests information from the other APIs. So my question is how do I make a GET request directly to a flask application? Is it something like....
from flask import request
#app.route('/root_dir/api_number_1/info', methods=['GET'])
def request_info_from_api_number_1():
return request.get_json()
#app.route('/root_dir/api_number_2/info', methods=['GET'])
def request_info_from_api_number_2():
return request.get_json()
When I do this the functions return None. I suppose I could always make an http request to the flask url and specify the address as localhost but it seems strange to have to make an http request when I can directly access the flask app object.
Could you just use the existing api functions?
from flask import Flask
app = Flask(__name__)
#app.route("/foo")
def foo():
return "foo"
#app.route("/foobar")
def foobar():
return foo() + "bar"
if __name__ == "__main__":
app.run(debug=True)