keosariel

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.