vimalloc / flask-jwt-simple

A barebones Flask extension for creating and protecting endpoints with JWT
MIT License
69 stars 12 forks source link

more options to jwt_data_loader #3

Closed ghost closed 6 years ago

ghost commented 6 years ago

Hi, I was hoping to add roles to the token based on what was coming out of my database. I've looked at your example where you use the jwt_data_loader to hardwire a single admin user based on just the identity...

I would need to have more users that may be admins or more roles... and the User object I take from the database contains the list of Roles each user has.

I can't work this way with just the identity and jwt_data_loader.. (I think?)..

Is there some other way to do this? I can't pass the User object to the create...

Can I make a jwt and then add to it?

Thanks

ghost commented 6 years ago

My solution to this is a bit clumsy.. cos it means a second trip to the database... but it works..

@jwt.jwt_data_loader
def add_claims_to_access_token(identity):

    thisUser = session.query(User).filter_by(user=identity).first()

    now = datetime.utcnow()
    return {
        'exp': now + current_app.config['JWT_EXPIRES'],
        'iat': now,
        'nbf': now,
        'sub': identity,
        'roles': thisUser.roles
    }

So, now at least when the client (say and Angular app) sends a request, the server doesn't have to go to the database to find out if the user has the required role...

vimalloc commented 6 years ago

There should not a limitation on what you can pass in as the create_jwt identity. You could pass in the full sqlalchemy user object to the ‘create_jwt’ function, and that would be the object you received in the jwt_data_loader callback, thus preventing the need to hit the database twice.

Hope that helps!

ghost commented 6 years ago

ooh.. ok.. I'm a newbie at python .. am trying this but passing the sqlalchemy user object to create_jwt is giving me errors.. Think its not json serializable so have to figure out how to do that ...

But thanks ! PJ

ps: apologies for python newbieness...

ghost commented 6 years ago

I have it workin like this now..

    data = {}
    data['user'] = theUser.user
    data['roles'] = theUser.roles
    json_data = json.dumps(data)

    ret = {'jwt': create_jwt(identity=json_data)}
    return jsonify(ret), 200

and my protected route can now differentiate ...

app.route('/protected', methods=['GET'])
@jwt_required
def protected():
    print '------ protected route accessed ----'

    theUser = json.loads(get_jwt()['sub'])
    if (theUser['roles'] == 'admin'):
        print 'this is an admin'

    return jsonify({'hello_from': get_jwt_identity()}), 200

Probably a bit newbie and clumsy.. but it'll do for today..

vimalloc commented 6 years ago

Another way you could do it would be like this:

@jwt.jwt_data_loader
def add_claims_to_access_token(user):  # user is the sqlalchemy user object
    now = datetime.utcnow()
    return {
        'exp': now + current_app.config['JWT_EXPIRES'],
        'iat': now,
        'nbf': now,
        'sub': user.user,
        'roles': user.roles
    }

@app.route('/login', methods=['POST'])
def login():
    username = request.json.get('username', None)
    password = request.get('password', None)
    user = session.query(User).filter_by(user=identity).first()

    # you would need to implement this with however your system is setup
    if not validate_password(user, password):
        return jsonify({"msg": "Bad username or password"}), 401

    ret = {'jwt': create_jwt(identity=user)}
    return jsonify(ret), 200
ghost commented 6 years ago

Oh yeah.. thats a lot neater isn't it? Thanks very much for this!

vimalloc commented 6 years ago

No problem :+1: