Reputation: 156
I am developing a Flask API using Flask Restful. I wonder if there is a clean way for authorizing users that would not force me to code duplication. I use Flask-JWT-Extended for authentication in my API.
I have got some endpoints that I want to be accessible only by user with admin
role OR the user, that is related to a given resource.
So, let's say I'd like to enable user to obtain information about their account and I'd like to prevent other users from accessing this information. For now, I am solving it this way:
from flask import request, Response
from flask_restful import Resource, reqparse
from flask_jwt_extended import (create_access_token, create_refresh_token, jwt_required, get_jwt_identity)
from app.mod_auth.models import User
[...]
class UserApi(Resource):
@jwt_required()
def get(self, name):
current_user = User.find_by_login(get_jwt_identity())
if current_user.login==name or current_user.role==1:
user = User.query.filter_by(login=name).first_or_404(description="User not found")
return user.json()
else:
return {'message': 'You are not authorized to access this data.'}, 403
[...]
So first I check, if there's correct and valid JWT token in the request, and then, basing on the token I check if the user related with the token is the same, as the user, whose data is being returned. Other way for accessing data is user with role 1
, which I treat as an administrative role.
This is the part of my User's model:
[...]
class User(Base):
login = db.Column(db.String(128), nullable=False)
email = db.Column(db.String(128), unique=True, nullable=False)
password = db.Column(db.String(192), nullable=False)
active = db.Column(db.Boolean(), default=True, nullable=False)
role = db.Column(db.SmallInteger, default=0, nullable=False)
[...]
Of course, soon I'll have a few endpoints with data specific for user.
I have found an example of custom operator in Flask-JWT-Extended, that provides an authorization for admin users: flask-jwt-extended admin authz - but on the other hand, it does not support user-specific authorization. I have no idea, how to improve that snippet in order to verify, if the user requesting for an resource is a specific user with rights to the resource.
How can I define a custom operator, that will provide correct access to the user-specific data?
Maybe I should include some kind of owner
data in each DB model, that should support authorization, and verify that in the requests, as in the example above?
Thanks in advance
Upvotes: 0
Views: 1496
Reputation: 2022
You can create a decorator that checks the identity of the user:
def validate_user(role_authorized:list() = [1]):
def decorator(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
name = request.path.rsplit('/', 1)[-1]
current_user = User.find_by_login(get_jwt_identity())
if (current_user.login == name) or (current_user.role in role_authorized):
kwargs["logged_user"] = current_user # If you need to use the user object in the future you can use this by passing it through the kwargs params
return fn(*args, **kwargs)
else:
return {'message': 'You are not authorized to access this data.'}, 403
return wrapper
return decorator
class Test(Resource):
@jwt_required()
#list of the roles authorized for this endpoint = [1]
@validate_user([1])
def post(self, name, **kwargs):
#logged_user = kwargs["logged_user"] # Logged in User object
user = User.query.filter_by(login=name).first_or_404(description="User not found")
return user.json()
Upvotes: 2