Wednesday, March 23, 2016

Flask-restful API Authorization

Leave a Comment

I use flask-restful to create my APIs. I have used flask-jwt for enabling authentication based on JWT. Now I need to do authorization.

I have tried putting my authorization decorator.

test.py (/test api)

from flask_restful import Resource from flask_jwt import jwt_required  from authorization_helper import authorized_api_user_type   class Test(Resource):      decorators = [jwt_required(), authorized_api_user_type()]      def get(self):         return 'GET OK'      def post(self):         return 'POST OK' 

Basically to handle the basic authorization, I need to access current_identity and check it's type. Then based on it's type I am gonna decided whether the user is authorized to access the api / resources.

But current_identity appears to be empty in that decorator. So to acquire it indirectly, I had to see the code of jwt_handler and do the thing done there.

authorization_helper.py

from functools import wraps from flask_jwt import _jwt, JWTError import jwt from models import Teacher, Student  def authorized_api_user_type(realm=None, user_type='teacher'):     def wrapper(fn):         @wraps(fn)         def decorator(*args, **kwargs):             token = _jwt.request_callback()              if token is None:                 raise JWTError('Authorization Required', 'Request does not contain an access token',                                headers={'WWW-Authenticate': 'JWT realm="%s"' % realm})              try:                 payload = _jwt.jwt_decode_callback(token)             except jwt.InvalidTokenError as e:                 raise JWTError('Invalid token', str(e))              identity = _jwt.identity_callback(payload)             if user_type == 'student' and isinstance(identity, Student):                 return fn(*args, **kwargs)             elif user_type == 'teacher' and isinstance(identity, Teacher):                 return fn(*args, **kwargs)             # NOTE - By default JWTError throws 401. We needed 404. Hence status_code=404             raise JWTError('Unauthorized',                            'You are unauthorized to request the api or access the resource',                            status_code=404)         return decorator     return wrapper 

Why can't I just access current_identity in my authorized_api_user_type decorator? What is the RIGHT way of doing authorization in flask-restful?

2 Answers

Answers 1

My current solution looks like:

@app.before_request def detect_something():     header = request.headers.get('Authorization')     if header:         _, token = header.split()         request.identity = identity(jwt.decode(token,                                                app.config['SECRET_KEY'])) 

After it we can access identity in decorator via request.identity. And I removed current_identity everywhere from code. It's still messy way.

Answers 2

Here is the combination of quickstarts of both Flask-JWT and Flask-Restful.

from flask import Flask from flask_restful import Resource, Api  app = Flask(__name__) api = Api(app)  from flask_jwt import JWT, jwt_required, current_identity from werkzeug.security import safe_str_cmp  class User(object):     def __init__(self, id, username, password):         self.id = id         self.username = username         self.password = password      def __str__(self):         return "User(id='%s')" % self.id  users = [     User(1, 'user1', 'abcxyz'),     User(2, 'user2', 'abcxyz'), ]  username_table = {u.username: u for u in users} userid_table = {u.id: u for u in users}  def authenticate(username, password):     user = username_table.get(username, None)     if user and safe_str_cmp(user.password.encode('utf-8'), password.encode('utf-8')):         return user  def identity(payload):     user_id = payload['identity']     return userid_table.get(user_id, None)  app.config['SECRET_KEY'] = 'super-secret'  jwt = JWT(app, authenticate, identity)   class HelloWorld(Resource):     decorators = [jwt_required()]     def get(self):         return {'hello': current_identity.username}  api.add_resource(HelloWorld, '/')  if __name__ == '__main__':     app.run(debug=True) 

POST

{     "username": "user1",     "password": "abcxyz" } 

To localhost:5000/auth and get the access_token in response.

Then GET localhost:5000/ with header

Authorization: JWT `the access_token value above` 

You would get

{   "hello": "user1" } 

For more reference:

https://github.com/rchampa/timetable/blob/master/restful/users.py

https://github.com/mattupstate/flask-jwt/issues/37

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment