Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Flask-Security Roles with Flask-JWT REST API

I am building a Flask-based REST API and using Flask-JWT to handle JWT auth. I also want to use the built in roles management with Flask-Security. However, Flask-Security's @roles_required() decorator assumes I am showing a Flask view when it fails.

Here is my token endpoint (which is working as I want):

$ http POST localhost:5000/auth/token username='test' password='test' HTTP/1.0 200 OK Content-Length: 192 Content-Type: application/json Date: Sun, 08 Nov 2015 17:45:46 GMT Server: Werkzeug/0.10.4 Python/3.5.0  {     "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE0NDcwMDQ3NDYsIm5iZiI6MTQ0NzAwNDc0NiwiZXhwIjoxNDQ3MDA1MDQ2LCJpZGVudGl0eSI6MX0.RFIeaLuvJNM9fDjFYFQ7sh_WaDVU-_aM7e46tVJzlBQ" } 

Here is a successful response to a resource that does not have any role requirement (using only @jwt_required) This is also working as I want:

$http GET localhost:5000/protected Authorization:'JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE0NDcwMDQ3NDYsIm5iZiI6MTQ0NzAwNDc0NiwiZXhwIjoxNDQ3MDA1MDQ2LCJpZGVudGl0eSI6MX0.RFIeaLuvJNM9fDjFYFQ7sh_WaDVU-_aM7e46tVJzlBQ' HTTP/1.0 200 OK Content-Length: 25 Content-Type: text/html; charset=utf-8 Date: Sun, 08 Nov 2015 17:46:24 GMT Server: Werkzeug/0.10.4 Python/3.5.0  <models.User[email=test]> 

When I do the same for a resource that has roles required (such as admin in this example), it seems to assume I have a page to display such as /login which I do not since it is a headless REST API:

$ http GET localhost:5000/admin Authorization:'JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE0NDcwMDQ3NDYsIm5iZiI6MTQ0NzAwNDc0NiwiZXhwIjoxNDQ3MDA1MDQ2LCJpZGVudGl0eSI6MX0.RFIeaLuvJNM9fDjFYFQ7sh_WaDVU-_aM7e46tVJzlBQ' HTTP/1.0 302 FOUND Content-Length: 209 Content-Type: text/html; charset=utf-8 Date: Sun, 08 Nov 2015 17:46:43 GMT Location: http://localhost:5000/ Server: Werkzeug/0.10.4 Python/3.5.0 Set-Cookie: session=eyJfZmxhc2hlcyI6W3siIHQiOlsiZXJyb3IiLCJZb3UgZG8gbm90IGhhdmUgcGVybWlzc2lvbiB0byB2aWV3IHRoaXMgcmVzb3VyY2UuIl19XX0.CSEcAw.pjwXLeSWUsORXR-OU5AfFvq6ESg; HttpOnly; Path=/  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> <title>Redirecting...</title> <h1>Redirecting...</h1> <p>You should be redirected automatically to target URL: <a href="/">/</a>.  If not click the link. 

I know Flask-Security uses Flask-Principal behind the scene for its roles management (@roles_required, etc.) and it ties into the RoleMixin and UserMixin for the datastore which is very nice. However, if there is no way to get Flask-Security to just allow the resource through without using my JWT header, then maybe the best bet is to build my own decorators which uses Flask-Principal to manage the roles.

Does anyone have any experience with this? The idea is that the entire front end can and will be built in whatever language we need and that means it may not be Flask's templates/views which is what Flask-Security appears to be doing.

Thank you for any insight anyone can provide!

like image 464
chrcoe Avatar asked Nov 08 '15 17:11

chrcoe


People also ask

How do you secure a Flask REST API with JSON Web Token?

To do that, change the endpoint to /user and then in the headers section, add a field as x-access-token and add the JWT token in the value and click on Send. You will get the list of users as JSON. So, this is how you can perform authentication with JWT in Flask.

How do I secure a Flask REST API?

Integrate the Security Library The code for a working OAuth secured Python Flask API is provided below: The OAuth filter is configured to run before API requests. The filter verifies the token signature and the expected issuer / audience claims. API routes can then access JWT claims in the request object.


1 Answers

Instead of a redirect, you would want to respond with an HTTP status code 403.

Your best bet is indeed to create your own decorator to manage the roles, and move away from using Flask-Security entirely.

The author of Flask-Security has mentioned that there are better ways to secure APIs, and it makes even more sense as the library is not maintained.

Flask-JWT or Flask-JWT-Extended are perfect candidates for this task. The former would require a bit more boilerplate to get things going. There is a stale PR suggesting an API to support roles, that you could use to create your own decorator if you decide to go with Flask-JWT.

The Flask-JWT-Extended docs suggest a simpler solution that might fit your case. You should follow the custom decorators section of the documentation for the full example, but here's the decorator in the nutshell:

from functools import wraps  from flask import jsonify from flask_jwt_extended import (     verify_jwt_in_request, get_jwt_claims )  def admin_required(fn):     @wraps(fn)     def wrapper(*args, **kwargs):         verify_jwt_in_request()         claims = get_jwt_claims()         if claims['roles'] != 'admin':             return jsonify(msg='Admins only!'), 403         else:             return fn(*args, **kwargs)     return wrapper 

This code looks for a roles claim in the JWT, and returns a 403 response if it is not admin.

like image 151
Nikolay Shebanov Avatar answered Sep 21 '22 13:09

Nikolay Shebanov