A simple login manager for your Flask API
Here’s a simple implementation of the login_required
function
for your flask api.
Firstly, we’d a means of authentication and I’d be using
Authorization: Bearer Token
as my means of authorization and
JWT
(JSON web token) to encrypt certain data or in this case create access tokens.
# -*- coding: utf-8 -*-
# login_manager.py
from flask import current_app
from datetime import datetime, timedelta
from functools import wraps
import jwt
class APILoginManager(object):
def __init__(self, app=None):
self.login_message = "You need to login"
if app is not None:
self.init_app(app)
def init_app(self, app):
"""
Configures an application. This registers an `after_request` call, and
attaches this `LoginManager` to it as `app.login_manager`.
:param app: The :class:`flask.Flask` object to configure.
:type app: :class:`flask.Flask`
"""
app.login_manager = self
def user_loader(self, callback):
'''
This sets the callback for reloading a user. The
function you set should take a user ID and return a
user object, or ``None`` if the user does not exist.
:param callback: The callback for retrieving a user object.
:type callback: callable
'''
self._user_callback = callback
return callback
def encode_token(self, user_id, minutes):
""" Generates a user access token
:param minutes: The token's age
:type minutes: integer
:return: user access token
"""
# set up a payload with an expiration time
payload = {
'exp': datetime.utcnow() + timedelta(minutes=minutes),
'iat': datetime.utcnow(),
'user_id': user_id
}
# create the byte string token using the payload and the SECRET key
jwt_string = jwt.encode(
payload,
current_app.config.get('SECRET_KEY'),
algorithm='HS256'
)
if type(jwt_string) == bytes:
jwt_string = jwt_string.decode()
return jwt_string
def decode_token(self, token):
"""Decodes the token from the Authorization header.
:param token: a valid access token
:type token: String
:return: (token_is_valid, user_id)
"""
try:
# try to decode the token using our SECRET variable
payload = jwt.decode(token, current_app.config.get('SECRET_KEY'), algorithms=['HS256'])
return True, payload['user_id']
except jwt.ExpiredSignatureError:
# the token is expired, return an error string
return False, "Expired token. Please login to get a new token"
except jwt.InvalidTokenError:
# the token is invalid, return an error string
return False, "Invalid token. Please register or login"
return False, "Invalid token. Please register or login"
def get_token_for(self, user, minutes=60):
try:
user_id = str(user.id)
except AttributeError:
raise NotImplemented("No `id` attribute")
return self.encode_token(user_id, minutes)
The decode_token
and encode_token
property functions are simply used to decode end encode
payloads respectively using JWT (JSON Web Tokens). From the code you’d understand that we can set a age limit (when the token would expire) for an access token.
However, we’d need a decorator function to utilize the class above. Here would call
this function login_required
.
# utils.py
from flask import (
jsonify,
request,
current_app
)
from functools import wraps
def login_required(func):
@wraps(func)
def decorator(*args, **kwargs):
auth_header = request.headers.get('Authorization')
error_message = current_app.login_manager.login_message
if auth_header:
try:
# Expected format (Bearer TOKEN)
token = auth_header.split(" ")[1]
except IndexError:
return jsonify(
dict(
status=401,
message=error_message
)
), 401
valid, user_id = current_app.login_manager.decode_token(token)
if valid:
user = current_app.login_manager._user_callback(user_id)
return func(*args, **kwargs, current_user=user)
else:
# Note: if not valid, user_id holds an error message
error_message = user_id
return jsonify(
dict(
status=401,
message=error_message
)
), 401
return decorator
Here is an example to test our little login manager.
Full source code I hope this was helpful tho.