January 18, 2015

November 25, 2019

Ferris 2 (Google App Engine), JSON Web Token and CORS

When I started working on my new project I decided to use Google App Engine and Ferris 2. Since App Engine had to be used only for backend, the next decision was AngularJS and JSON Web Tokens. At the moment I switched to Ferris 3 (it is a great tool!) but I will write here only about how to solve the problem with CORS.

Let's start with Ferris 2 authorization chain. This method is responsible for verification of a user token. It is very simple, it gets Authorization header, gets secret token, tries to decode token, checks expiration time, returns user in controller.user if token is correct, otherwise it returns False. You can read more here.

from datetime import datetime
import jwt
from ferris import settings
def require_user(controller):
token = controller.request.headers.get('Authorization')
if not token:
return False
try:
payload = jwt.decode(
token, settings.get('app_config').get('token_secret')
)
except jwt.DecodeError:
return False
if datetime.fromtimestamp(payload['exp']) < datetime.now():
return False
controller.user = payload['user']
return True

Of course, I had a problem with CORS when javascript client wanted to make a request to the backend. I will put improved decorator firstly posted here. Decorator from Google groups doesn't work, i don't remember why but below is a correct version (it worked for me ;-)).

import functools
def allow_options(function):
@functools.wraps(function)
def inner(self, *args, **kwargs):
if self.request.method == 'OPTIONS':
self.response.headers.add_header(
'Access-Control-Allow-Origin',
'http://localhost:9000'
)
self.response.headers.add_header(
'Access-Control-Allow-Methods',
'POST, GET, OPTIONS'
)
self.response.headers.add_header(
'Access-Control-Max-Age',
str(1000)
)

Of course you need to replace http://localhost:9000 with your frontend address and you can change values in Access-Control-Allow-Methods and Access-Control-Max-Age if you need this. I just tried it only on localhost (because I moved to Ferris 3 and endpoints) but it should work correctly. It just catches a requests with OPTIONS method, adds required headers and return response so the next request should get correct answer instead of error about domain not allowed by Access-Control-Allow-Origin.

Short example how to use it:

from ferris.core.controller import add_authorizations
from ferris import route
from app.utils.auth import (
allow_options,
require_user
)
@allow_options
@route
@add_authorizations(require_user):
def profile(self, username):
user = self.controller.user
© 2020 Przemysław Kołodziejczyk