Authentication and authorization

Graceful offers very simple and extendable authentication and authorization mechanism. The main design principles for authentication and authorization in graceful are:

  • Authentication (identifying users) and authorization (restricting access to the endpoint) are separate processes and because of that they should be declared separately.
  • Available authentication schemes are gloabl and always the same for whole application.
  • Different resources usually require different permissions so authorization is always defined on per-resource or per-method level.
  • Authentication and authorization layers communicate only through request context (the req.context attribute).

Thanks to these principles we are able to keep auth implementation very simple and also allow both mechanisms to be completely optional:

  • You can replace the built-in authorization tools with your own custom middleware classes and hooks. You can also implement authorization layer inside of the resource modification methods (list/create/retrieve/etc.).
  • If your use case is very simple and successful authentication (user identification) allows for implicit access grant you can use only the authentication_required decorator.
  • If you want to move whole authentication layer outside of your application code (e.g. using specialized reverse proxy) you can easily do that. The only thing you need to do is to create some middleware that will properly modify your request context dictionary to include proper user object.

Authentication - identifying the users

In order to define authentication for your application you need to instantiate one or more of the built in authentication middleware classes and configure falcon application to use them. For example:

api = application = falcon.API(middleware=[
    authentication.XForwardedFor(),
    authentication.Anonymous(),
])

If request made the by the user meets all the requirements that are specific to any authentication flow, the generated/retrieved user object will be included in request context under req.context['user'] key. If this context variable exists it is a clear sign that request was succesfully authenticated.

If you use multiple different middleware classes only the first middleware that succeeded to identify the user will be resolved. This allows for having fallback authentication mechanism like anonymous users or users identified by remote address.

User objects and working with user storages

Most of authentication middleware classes provided in graceful require user_storage initializations argument. This is the object that abstracts access to the authentication database. It should implement at least the get_user() method:

from graceful.authentication import BaseUserStorage

class CustomUserStorage(BaseUserStorage):
    def get_user(
        self, identified_with, identifier,
        req, resp, resource, uri_kwargs
    ):
        ...

Accepted get_user() method arguments are:

  • identified_with (object): instance of the authentication middleware that provided the identifier value. It allows to distinguish different types of user credentials.
  • identifier (object): object that identifies the user. It is specific for every authentication middleware implementation. For some middlewares it can be a raw string value (e.g. token or API key).
  • req (falcon.Request): the request object.
  • resp (falcon.Response): the response object. resource (object): the resource object.
  • uri_kwargs (dict): keyword arguments from the URI template.

If user entry exists in the storage (user can be identified) the method should return user object. This object usually is just a simple Python dictionary. This object will be later included in the request context as req.context['user'] variable. If user cannot be found in the storage it means that his identifier is either fake or invalid. In such case this method should always return None.

Note

Note that at this stage you should not verify any user permissions. If you can identify user but it is unpriviledged client you should still return the user object. Actual permission checking is a responsibility of the authorization layer. You should inlcude all user metadata that will be later required in the authorization process.

Graceful inlcudes a few useful concrete user storage implementations:

  • KeyValueUserStorage: simple implementation of user storage using any key-value database client as a storage backend.
  • DummyUserStorage: a dummy user storage that will always return the configured default user. It is useful only for testing purposes.
  • IPRangeWhitelistStorage: user storage with IP range whitelist intended to be used exclusively with the XForwardedFor authentication middleware.

Implictit authentication without user storages

Some built-in authentication implementations for graceful do not require any user storage to be defined in order to work. These authentication schemes are provided in form of following middlewares:

If XForwardedFor is used without any storage it will sucessfully identify every request. The resulting request object will be syntetic user dictionary in following form:

{
    'identified_with': <authenticator>,
    'identifier': <user-address>
}

Where <authenticator> is the authentication middleware instance (here defaults to XForwardedFor) and the indentity will be client’s address. Client address is either value of X-Forwarded-For header or remote address taken directly from WSGI enviroment dictionary (only if middleware is configured with remote_address_fallback=True).

In case of Anonymous the resulting user context variable will be always the same as the value of middleware’s user initialization argument.

Both XForwardedFor (without user storage) and Anonymous are intended to be used only as authentication fallbacks for applications that expect req.context['user'] variable to be always available. This can be useful for applications that identify every user to track and throttle API usage on endpoints that do not require any authorization.

Custom authentication middleware

The easiest way to implement custom authentication middleware is by subclassing the BaseAuthenticationMiddleware. The only method you need to implement is identify(). It has access to following arguments: identify(self, req, resp, resource, uri_kwargs):

  • req (falcon.Request): falcon request object. You can read headers and get arguments from it.
  • resp (falcon.Response): falcon response object. Usually not accessed during authentication.
  • resource (object): resource object that request is routed to. May be useful if you want to provide dynamic realms.
  • uri_kwags (dict): dictionary of keyword arguments from URI template.

Aditionally you can control further the behaviour of authentication middleware using following class attributes:

  • only_with_storage: if it is set to True, it will be impossible to initialize the middleware without user_storage argument.
  • challenge: returns the challenge string that will be inlcuded in WWW-Authenticate header on unauthorized request responses. This has effect only in resources protected with authentication_required.

Authorization - restricting access to the endpoint

The recommended way to implement authorization in graceful is through falcon hooks that can be applied to whole resources and HTTP method handlers:

import falcon

from graceful.resources.generic import ListAPI

falcon.before(my_authorization_hook)
class MyListResource(ListAPI):
    ...

    @falcon.before(my_other_authorization_hook)
    def on_post(self, *args, **kwargs)
        return super().on_post()

Authorization hooks depend solely on user context stored under req.context['user']. The usual authorization hook implementation does two things:

  • Check if the 'user' variable is available in req.context dictionary. If it isn’t then raise the falcon.HTTPForbidden exception.
  • Verify user object content (e.g. check his group) and raise the falcon.HTTPForbidden exception if does not meet specific requirements.

Example of customizable authorization hook implementation that requires specific user group to be assigned could be as follows:

import falcon

def group_required(user_group):

    @falcon.before
    def authorization_hook(req, resp, resource, uri_kwargs)
        try:
            user = req.context['user']

        except KeyError:
            raise falcon.HTTPForbidden(
                "Forbidden",
                "Could not identify the user!"
            )

        if user_group not in user.get('groups', set()):
            raise falcon.HTTPForbidden(
                "Forbidden",
                "'{}' group required!".format(user_group)
            )

Depending on your application design and complexity you will need different authorization handling. The way how you grant/deny access also depends highly on the structure of your user objects and the preferred user storage. This is why graceful provides only one basic authorization utility - the authentication_required decorator.

The authentication_required decorator ensures that request successfully passed authentication. If none of the authentication middlewares succeeded to identify the user it will raise falcon.HTTPUnauthorized exception and include list of available authentication challenges in the WWW-Authenticate response header. If you use this decorator you don’t need to check for req.context['user'] existence in your custom authorization hooks (still, it is a good practice to do so).

Example usage is:

from graceful import authorization
from graceful.resources.generic import ListAPI

from myapp.auth import group_required

@authentication_required
@group_required("admin")
class MyListResource(ListAPI):
    ...

    @falcon.before(my_other_authorization_hook)
    def on_post(self, *args, **kwargs)
        return super().on_post()

Heterogenous authentication

Graceful does not allow you to specify unique per-resource or per-method authentication schemes. This allows for easier implementation but may not cover every use case possible.

If you need to restrict some authentication methods to specific resources (e.g. some custom auth only for internal use) the best way is to handle this through separate application deployments.

Practical example – authentication with redis backend

Let’s assume we want to build simple REST API application supporting two authentication schemes:

  • Token access authentication with Authorization: Token HTTP header
  • Basic access authentication with Authorization: Basic HTTP header as specified by RFC 7617.

As a user database we will use KeyValueUserStorage storage class which is compatible with any key-value database client that provides two simple methods:

  • set(key, value): set key value in the storage. Both key and value should be strings.
  • get(key): get key value from the storage. Both key and return value should be string.

First step is to create a key-value store client user storage intance that will be used by both authentication middlewares. With redis and KeyValueUserStorage this is very simple:

from redis import StrictRedis as Redis
from graceful.authentication import KeyValueUserStorage

auth_storage = KeyValueUserStorage(Redis())

This storage can be used by many different authentication middlewares at the same time. It will properly prefix every Redis key with middleware name to make sure different types of user entries do not collide with each other.

The only problem is that default implementation of KeyValueUserStorage.hash_identifier(identified_with, identifier) method expects that identifier argument is a single string argument. The Basic authentication middleware generates identifiers in form of (username, password) two-tuples. Fortunately you don’t need to use subclassing in order to override this method behavior. The hash_identifier method is a single-dispatch generic function so you can easily create custom handlers for specific authentication middleware types.

We definitely don’t want to store user passwords in plain text. Let’s register simple hash_identifier handler for Basic access authentication that will properly prepare password hash using SHA1 algorithm:

from hashlib import sha1

from graceful.authentication import Basic


@auth_storage.hash_identifier.register(Basic)
def _(identified_with, identifier):
    return ":".join((
        identifier[0],
        hashlib.sha1(identifier[1].encode()).hexdigest()
    ))

Default hash_identifier leaves single-string identifiers untouched so it may be a good idea to hash token identifiers in similar fashion too:

@auth_storage.hash_identifier.register(Token)
def _(identified_with, identifier):
    return hashlib.sha1(identifier[1].encode()).hexdigest()

Note

Really secure password verification mechanism would require proper time-consuming hashing algorithm that would prevent application from brute-force and timing attacks. Anyway, for real end-user applications you would probably use a session cookie for authentication rather than basic access authentication. For such case simple SHA1 hashing may not be the best solution. Still, basic access authentication is a simple alternative to custom authentication headers and/or GET parameters when communicating in server-to-server fashion over the secure channel.

Our authentication setup is almost finished. The last things to do is to initialize authentication middlewares and setup a very basic authorization to API resources. Following is the code for a very small application that protects its resources with Token and Basic authentication middlewares:

import hashlib

from redis import StrictRedis as Redis
import falcon

from graceful.resources.generic import Resource
from graceful.authentication import KeyValueUserStorage, Token, Basic
from graceful.authorization import authentication_required

@authentication_required
class Me(Resource, with_context=True):
    def retrieve(self, params, meta, context):
        return context.get('user')


auth_storage = KeyValueUserStorage(Redis())


@auth_storage.hash_identifier.register(Basic)
def _(identified_with, identifier):
    return ":".join((
        identifier[0],
        hashlib.sha1(identifier[1].encode()).hexdigest()
    ))


@auth_storage.hash_identifier.register(Token)
def _(identified_with, identifier):
    return hashlib.sha1(identifier[1].encode()).hexdigest()

api = application = falcon.API(
    middleware=[
        Token(auth_storage),
        Basic(auth_storage),
    ]
 )

api.add_route('/me/', Me())

Now you can easily create new user entries using Pyhton console:

>>> from auth_app import auth_storage, Token, Basic
>>> auth_storage.register(Token(auth_storage), 'mytoken', {"user": "me with token"})
>>> auth_storage.register(Basic(auth_storage), ['myusername', 'mysecretpassword'], {"user": "me with password"})

... check if they are successfully saved in Redis:

$ redis-cli keys '*'
1) "users:Token:95cb0bfd2977c761298d9624e4b4d4c72a39974a"
2) "users:Basic:myusername:08cd923367890009657eab812753379bdb321eeb"

... and verify authentication using HTTP client (here with httpie):

$ http localhost:8000/me
HTTP/1.1 401 Unauthorized
Connection: close
Date: Thu, 23 Mar 2017 16:09:55 GMT
Server: gunicorn/19.6.0
content-length: 91
content-type: application/json
vary: Accept
www-authenticate: Token, Basic realm=api

{
    "description": "This resource requires authentication",
    "title": "Unauthorized"
}

$ http localhost:8000/me --auth myusername:mysecretpassword
HTTP/1.1 200 OK
Connection: close
Date: Thu, 23 Mar 2017 16:08:53 GMT
Server: gunicorn/19.6.0
content-length: 76
content-type: application/json

{
    "content": {
        "user": "me with password"
    },
    "meta": {
        "params": {
            "indent": 0
        }
    }
}

$ http localhost:8000/me 'Authorization:Token mytoken'
HTTP/1.1 200 OK
Connection: close
Date: Thu, 23 Mar 2017 16:09:39 GMT
Server: gunicorn/19.6.0
content-length: 73
content-type: application/json

{
    "content": {
        "user": "me with token"
    },
    "meta": {
        "params": {
            "indent": 0
        }
    }
}