mik0w
mik0w

Reputation: 156

Flask RESTful API - authorization for specific user or admin user

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

Answers (1)

Matteo Pasini
Matteo Pasini

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

Related Questions