Imagine you have developed a REST API with JWT authentication for a mobile app, and then decided to create a personal area of product’s website or an admin panel. Chances are, you’ll want to re-use some parts of the same API from JavaScript on these pages. The common approach for user’s authentication in the web would be to use Flask-Login, which stores current user id in Flask session by default. Session data is then stored in signed cookies:
But you obviously can’t authorize with the API using it, because that’s not what Flask-JWT expects. Instead, it looks for a JWT in Authorization
header of the request. So, you’ll have to implement a separate authorization logic in JavaScript code, which usually will call /auth
endpoint with username and password from the login form to obtain a login token, then will persist it between calls and renew as it expires. That’s less than convenient and may potentially introduce security flaws to your application.
Luckily, there’s an easy way to override default JWT obtaining behavior and generate it right from user object stored in session data by Flask-Login! Here’s how to do it:
from flask import current_app, request, _request_ctx_stack from flask_jwt import JWT, jwt_required, JWTError, _jwt @jwt.request_handler def request_handler(): auth_header_value = request.headers.get('Authorization', None) auth_header_prefix = current_app.config['JWT_AUTH_HEADER_PREFIX'] if not auth_header_value: # check if flask_login is configured if isinstance(current_app.login_manager, LoginManager): # load user current_app.login_manager._load_user() # if successful, this will set user variable at request context if hasattr(_request_ctx_stack.top, 'user'): # generate token access_token = _jwt.jwt_encode_callback(_request_ctx_stack.top.user) return access_token parts = auth_header_value.split() if parts[0].lower() != auth_header_prefix.lower(): raise JWTError('Invalid JWT header', 'Unsupported authorization type') elif len(parts) == 1: raise JWTError('Invalid JWT header', 'Token missing') elif len(parts) > 2: raise JWTError('Invalid JWT header', 'Token contains spaces') return parts[1]
Most of the code is the same as Flask-JWT’s default request handler, we only had to handle the case when authorization header is empty and generate new JWT for user object obtained from Flask-Login. Now, given we have Flask-Login configured for an app, we can call JWT protected API endpoints from JavaScript transparently.
Sample endpoint:
class ApiMethod(Resource): @jwt_required() def get(self): # some sample data return {'data': [{'id': 1, 'value': 'string'}, {'id': 2, 'value': 'string'}], 'result': 'OK'}
JavaScript code (with jQuery):
$(function () { $.get('/api_method').done(function (data) { console.log('API call success: ' + JSON.stringify(data)); }).fail(function (e) { console.log('API call error: ' + e.status); }) });
Note: same approach will work even if API and Web are separate Flask apps using the same database. You’ll just need to add session cookie decoding code from Flask-Login directly into custom request handler to obtain user id and fetch user object.