falcon-authentication

Build version Documentation status build coverage license

A falcon middleware + authentication backends that adds authentication layer to you app/api service.

Installation

Install the extension with pip, or easy_install.

$ pip install -U falcon-authentication

If you wish to use the optional backends, specify those dependencies, too.

$ pip install -U falcon-authentication[backend-hawk,backend-jwt]

Usage

This package exposes a falcon middleware which takes an authentication backend as an input and use it to authenticate requests. You can specify some routes and methods which are exempted from authentication. Once the middleware authenticates the request using the specified authentication backend, it add the authenticated user to the request context

import falcon
from falcon_authentication import FalconAuthMiddleware, BasicAuthBackend

user_loader = lambda req, resp, resource, username, password: { 'username': username }
auth_backend = BasicAuthBackend(user_loader)
auth_middleware = FalconAuthMiddleware(
    auth_backend,
    exempt_routes=['/exempt'],
    exempt_methods=['HEAD'],
    context_key='auth')
api = falcon.API(middleware=[auth_middleware])

class ApiResource:

    def on_post(self, req, resp):
        # req.context['auth'] is of the form:
        #
        #   {
        #       'backend': <backend instance that performed the authentication>,
        #       'user': <user object retrieved from user_loader()>,
        #       '<backend specific item>': <some extra data from the backend>,
        #       ...
        #   }
        user = req.context['auth']['user']
        resp.body = "User Found: {}".format(user['username'])

If you wish to place the authentication results under a name other than 'auth' in the req.context, provide the context_key argument to the middleware constructor.

Override Authentication for a specific resource

Its possible to customize the exempt routes, exempt methods and authentication backend on a per resource basis as well

import falcon
from falcon_authentication import FalconAuthMiddleware, BasicAuthBackend, TokenAuthBackend

# a loader function to fetch user from username, password
user_loader = lambda req, resp, resource, username, password: { 'username': username }

# basic auth backend
basic_auth = BasicAuthBackend(user_loader)

# Auth Middleware that uses basic_auth for authentication
auth_middleware = FalconAuthMiddleware(basic_auth)
api = falcon.API(middleware=[auth_middleware])


class ApiResource:

    auth = {
        'backend': TokenAuthBackend(user_loader=lambda req, resp, resource, token: { 'id': 5 }),
        'exempt_methods': ['GET']
    }

    # token auth backend

    def on_post(self, req, resp):
        resp.body = "This resource uses token authentication"

    def on_get(self, req, resp):
        resp.body = "This resource doesn't need authentication"


api.add_route("/api", ApiResource())

Disable Authentication for a specific resource

class ApiResource:
    auth = {
        'auth_disabled': True
    }

Accessing Authenticated User (and other artifacts)

Once the middleware authenticates the request using the specified authentication backend, it adds the authenticated user to the request context.

class ApiResource:

    def on_post(self, req, resp):
        # req.context['auth'] is of the form:
        #
        #   {
        #       'backend': <backend instance that performed the authentication>,
        #       'user': <user object retrieved from user_loader()>,
        #       '<backend specific item>': <some extra data from the backend>,
        #       ...
        #   }
        user = req.context['auth']['user']
        resp.body = "User Found: {}".format(user['username'])

Be notified of success or failure to authenticate

The middleware accepts on_success and on_failure callbacks to be invoked upon the completion of the authentication process for a request. They both receive the standard falcon request, response and resource objects. The on_success callback will also receive the results of the authentication (augmented with the AuthBackend that authenticated the user). The on_failure callback will instead receive a BackendAuthenticationFailure exception to indicate the reason for the failure. These exceptions will always have a backend reference to indicate which backend failed the authentication (or a MultiAuthBackend if all nested authentications failed).

Authentication Backends

  • Basic Authentication

Implements HTTP Basic Authentication wherein the HTTP Authorization header contains the user credentials(username and password) encoded using base64 and a prefix (typically Basic)

  • Token Authentication

Implements a Simple Token Based Authentication Scheme where HTTP Authorization header contains a prefix (typically Token) followed by an API Token

  • JWT Authentication (Python 2.7, 3.4+)

Token based authentication using the JSON Web Token standard If you wish to use this backend, be sure to add the optional dependency to your requirements (See Python “extras”):

falcon-authentication[backend-jwt]
  • Hawk Authentication (Python 2.6+, 3.4+)

Token based authentication using the Hawk “Holder-Of-Key Authentication Scheme” If you wish to use this backend, be sure to add the optional dependency to your requirements (See Python “extras”):

falcon-authentication[backend-hawk]

This backend will also provide the mohawk.Receiver object in the req.context['auth'] result under the ‘receiver’ key.

  • Dummy Authentication

Backend which does not perform any authentication checks

  • Multi Backend Authentication

An AuthBackend which is comprised of multiple backends and requires any of them to authenticate the request successfully.

This backend will iterate over all provided backends until one of the following occurs:

  • A backend returns a successful authentication result, containing at least the user object
  • A backend raises a non-BackendNotApplicable BackendAuthenticationFailure exception and early_exit is true.
  • The end of the list is reached

The BackendNotApplicable exception should be raised by a backend when it determines that it is not the appropriate backend to handle the request. (eg. The BasicAuthBackend doesn’t know how to parse a Hawk authorization header). In this way, a list of backends can short-circuit when an appropriate backend is found, rather than traversing the whole list. Any other exceptions will result in authentication stopping, the optional on_failure() callback being invoked, and the exception propagating out of the middleware to be handled by the falcon framework. Any WWW-Authenticate challenges provided by the backends will be collected and sent back to the client.

Custom Backends

It is expected that users will want to write their own backends to work with this middleware. Here are the guidelines to follow when writing your backend:

  • Inherit from AuthBackend.
  • Take care to call the base class AuthBackend.__init__(user_loader) from your __init__() method.
  • Call AuthBackend.load_user() to invoke the provided user_loader callback and retrieve the user object.
  • Return a dictionary from authenticate() which includes at least the ‘user’ key holding the user object returned from user_loader(). Other backend-specific items can be included as well.
  • Raise a BackendNotApplicable exception if the backend determines that it is not equipped to handle the request and should defer to a more appropriate backend.
  • Prefer raising a BackendAuthenticationFailure in all other cases to potentially take advantage of the MultiAuthBackend early_exit short-circuiting.

Tests

This library comes with a good set of tests which are included in tests/. To run install pytest and simply invoke py.test or python setup.py test to exercise the tests. You can check the test coverage by running py.test --cov falcon_authentication. Note: The test suite makes use of pytest functionality that was deprecated in pytest==4.0.0, so be sure to run tests in an environment that uses a prior version.

API

class falcon_authentication.FalconAuthMiddleware(backend, exempt_routes=None, exempt_methods=None, context_key='auth', on_success=None, on_failure=None)[source]

Creates a falcon auth middleware that uses given authentication backend, and some optional configuration to authenticate requests. After initializing the authentication backend globally you can override the backend as well as other configuration for a particular resource by setting the auth attribute on it to an instance of this class.

The authentication backend must return an authenticated user which is then set as request.context['auth']['user'] to be used further down by resources othewise an falcon.HTTPUnauthorized exception is raised.

Args:
backend(falcon_authentication.backends.AuthBackend, required): Specifies
the auth backend to be used to authenticate requests
exempt_routes(list, optional): A list of paths to be excluded while performing
authentication. Default is None
exempt_methods(list, optional): A list of paths to be excluded while performing
authentication. Default is ['OPTIONS']
context_key(str, optional): The key to be used when adding the successful
authentication results to the req.context dictionary. Default is 'auth'.
on_success(function, optional): A callback function that is called with the

results of the authenticate() call when authentication succeeds.

def on_success(req, resp, resource, results):
    ...
on_failure(function, optional): A callback function that is called with the

results of the authenticate() call when authentication fails. This will only be called if the backend was deemed appropriate for the request (ie. a falcon.HTTPUnauthorized exception was raised and not a BackendNotApplicable exception).

def on_failure(req, resp, resource, exception):
    ...
class falcon_authentication.BasicAuthBackend(user_loader, auth_header_prefix='Basic')[source]

Implements HTTP Basic Authentication Clients should authenticate by passing the base64 encoded credentials username:password in the Authorization HTTP header, prepended with the string specified in the setting auth_header_prefix. For example:

Authorization: BASIC ZGZkZmY6ZGZkZ2RkZg==
Args:
user_loader(function, required): A callback function that is called with the user
credentials (username and password) extracted from the Authorization header. Returns an authenticated user if user exists matching the credentials or return None to indicate if no user found or credentials mismatch.
auth_header_prefix(string, optional): A prefix that is used with the
bases64 encoded credentials in the Authorization header. Default is basic
authenticate(req, resp, resource)[source]

Extract basic auth token from request authorization header, deocode the token, verifies the username/password and return either a user object if successful else raise an BackendAuthenticationFailure exception

get_auth_token(user_payload)[source]

Extracts username, password from the user_payload and encode the credentials username:password in base64 form

class falcon_authentication.TokenAuthBackend(user_loader, auth_header_prefix='Token')[source]

Implements Simple Token Based Authentication. Clients should authenticate by passing the token key in the “Authorization” HTTP header, prepended with the string “Token “. For example:

Authorization: Token 401f7ac837da42b97f613d789819ff93537bee6a
Args:
user_loader(function, required): A callback function that is called
with the token extracted from the Authorization header. Returns an authenticated user if user exists matching the credentials or return None to indicate if no user found or credentials mismatch.
auth_header_prefix(string, optional): A prefix that is used with the
token in the Authorization header. Default is basic
authenticate(req, resp, resource)[source]

Extract basic auth token from request authorization header, deocode the token, verifies the username/password and return either a user object if successful else raise an BackendAuthenticationFailure exception

get_auth_token(user_payload)[source]

Extracts token from the user_payload

class falcon_authentication.JWTAuthBackend(user_loader, secret_key, algorithm='HS256', auth_header_prefix='jwt', leeway=0, expiration_delta=86400, audience=None, issuer=None, verify_claims=None, required_claims=None)[source]

Token based authentication using the JSON Web Token standard Clients should authenticate by passing the token key in the Authorization HTTP header, prepended with the string specified in the setting auth_header_prefix. For example:

Authorization: JWT eyJhbGciOiAiSFMyNTYiLCAidHlwIj
Args:
user_loader(function, required): A callback function that is called with the
decoded jwt payload extracted from the Authorization header. Returns an authenticated user if user exists matching the credentials or return None to indicate if no user found or credentials mismatch.
secrey_key(string, required): A secure key that was used to encode and
create the jwt token from a dictionary payload
algorithm(string, optional): Specifies the algorithm that was used
to for cryptographic signing. Default is HS256 which stands for HMAC using SHA-256 hash algorithm. Other supported algorithms can be found here
auth_header_prefix(string, optional): A prefix that is used with the
bases64 encoded credentials in the Authorization header. Default is jwt
leeway(int, optional): Specifies the timedelta in seconds that is allowed
as leeway while validating expiration time / nbf(not before) claim /iat (issued at) claim which is in past but not very far. For example, if you have a JWT payload with an expiration time set to 30 seconds after creation but you know that sometimes you will process it after 30 seconds, you can set a leeway of 10 seconds in order to have some margin. Default is 0 seconds
expiration_delta(int, optional): Specifies the timedelta in seconds that
will be added to current time to set the expiration for the token. Default is 1 day(24 * 60 * 60 seconds)
audience(string, optional): Specifies the string that will be specified
as value of aud field in the jwt payload. It will also be checked agains the aud field while decoding.
issuer(string, optional): Specifies the string that will be specified
as value of iss field in the jwt payload. It will also be checked agains the iss field while decoding.
authenticate(req, resp, resource)[source]

Extract auth token from request authorization header, decode jwt token, verify configured claims and return either a user object if successful else raise an BackendAuthenticationFailure exception.

get_auth_token(user_payload)[source]

Create a JWT authentication token from user_payload

Args:
user_payload(dict, required): A dict containing required information
to create authentication token
class falcon_authentication.HawkAuthBackend(user_loader, credentials_loader, receiver_kwargs=None)[source]

Holder-Of-Key Authentication Scheme defined by Hawk Clients should authenticate by passing a Hawk-formatted header as the Authorization HTTP header. For example:

Authorization: Hawk id=”dh37fgj492je”, ts=”1353832234”, nonce=”j4h3g2”, ext=”some-app-ext-data”, mac=”6R4rV5iE+NPoym+WwjeHzjAGXUtLNIxmo1vpMofpLAE=”
Args:
user_loader(function, required): A callback function that is called with the id
value extracted from the Hawk header. Returns an authenticated user if the user matching the credentials exists or returns None to indicate if no user was found.
receiver_kwargs(dict, optional): A dictionary of arguments to be passed through
to the Receiver. One must provide the credentials_map function for the purposes of looking up a user’s credentials from their user id (the same value passed to user_loader()). See the docs for further details.
authenticate(req, resp, resource)[source]

Authenticate the request and return the authenticated user. Must raise an a BackendAuthenticationFailure exception if authentication fails. It is preferred that it raise an BackendNotApplicable exception if it’s determined that the provided credentials cannot be handled by this backend.

credentials_map(req, resp, resource, user_id)[source]

Look up the user from the application and allow the application to extract/generate Hawk credentials from the user object. It then drops the user object into the credentials map as a way of memoizing this object for fast retrieval once authentication succeeds (we want to avoid another round trip to the backing datastore to get the user again).

parse_auth_token_from_request(auth_header)[source]

Parses and returns the Hawk Authorization header if it is present and well-formed. Raises BackendNotApplicable exception with proper error message

class falcon_authentication.NoneAuthBackend(user_loader)[source]

Dummy authentication backend.

This backend does not perform any authentication check. It can be used with the MultiAuthBackend in order to provide a fallback for an unauthenticated user.

Args:
user_loader(function, required): A callback function that is called
without any arguments and returns an unauthenticated user.
authenticate(req, resp, resource)[source]

Authenticate the request and return the authenticated user. Must raise an a BackendAuthenticationFailure exception if authentication fails. It is preferred that it raise an BackendNotApplicable exception if it’s determined that the provided credentials cannot be handled by this backend.

class falcon_authentication.MultiAuthBackend(*backends, **kwargs)[source]

A backend which takes two or more AuthBackend as inputs and successfully authenticates if any of them succeeds else raises a BackendNotApplicable exception.

Args:
backends(list[AuthBackend], required): A list of AuthBackend to be used in
order to authenticate the user.
early_exit(bool, optional): If early_exit is True, the iteration through the list of
backends will stop upon the first non-BackendNotApplicable BackendAuthenticationFailure exception it encounters. Otherwise, it will treate all falcon.HTTPUnauthorized exceptions the same: just move on to the next backend in the list. Default is False.
authenticate(req, resp, resource)[source]

Authenticate the request and return the authenticated user. Must raise an a BackendAuthenticationFailure exception if authentication fails. It is preferred that it raise an BackendNotApplicable exception if it’s determined that the provided credentials cannot be handled by this backend.

get_auth_token(user_payload)[source]

Returns a authentication token created using the provided user details

Args:
user_payload(dict, required): A dict containing required information
to create authentication token