Say I want to protect a route called /protected
, I will do the following:
@app.route('/protected')
@jwt_required
def protected():
return "Protected", 200
Doing so will mean that only authenticated users can access /protected
. But what if I want to protect a route called /protected/123
such that only User123 is allowed to access the route?
To motivate this question, I am trying to implement an edit-profile
feature. When User123 accesses /users/edit/123
, the server will respond with existing user data. Of course, I want to make sure that the server will only respond when the request comes from User123.
CodePudding user response:
Flask-JWT-Extended does not offer any decorators that let you limit a view by userid.
You can check the user in the view and abort if the userid doesn't match the id in the route. E.g. if you are using the automatic user loading feature, check if the user id matches via the current_user
object:
from flask import abort
from flask_jwt_extended import current_user
@app.route('/users/<userid:int>/edit')
@jwt_required
def users_edit(userid):
if userid != current_user.id:
abort(403)
# ... handle view for matching user
Note: I changed the URL to put the userid right after /users/
, so you'd get consistent URLs with other user-related routes.
If you are not using automic user loading but instead rely on the JWT identity claim (the sub
claim usually), use get_jwt_identity()
and check that value:
# assuming that the sub claim is an integer value
if userid != get_jwt_identity()
You can always create your own decorator that does the check before calling the decorated function:
from functools import wraps
from flask_jwt_extended import current_user, jwt_protected
def userid_must_match(f):
"""Abort with a 403 Forbidden if the userid doesn't match the jwt token
This decorator adds the @protected decorator
Checks for a `userid` parameter to the function and aborts with
status code 403 if this doesn't match the user identified by the
token.
"""
@wraps(f)
@jwt_protected
def wrapper(*args, userid=None, **kwargs):
if userid is not None and userid != current_user.id:
abort(403)
return f(*args, **kwargs)
return wrapper
The decorator assumes no check needs to be made if there is no userid
parameter in the route.
Use like this:
@app.route('/users/<userid:int>/edit')
@userid_must_match
def users_edit():
# ... handle view for matching user
From a design point of view, I'd make it so that you can leave out the userid; that way you can visit /users/edit
and edit your own settings:
from flask import abort
from flask_jwt_extended import current_user
@app.route('/users/edit')
@app.route('/users/<userid:int>/edit')
@userid_must_match
def users_edit():
# ... handle view for matching user via current_user
You can then consider checking for an administrator or superuser account if you want to grant access to editing any user to such accounts.
I'd also look into other Flask plugins such as Flask-Principal to handle roles and permissions, or even Flask-Security if you need more advanced user management options.