Is there an accepted way to use API keys for authentication in Flask? [closed]

For authentication keys, create a random value and store that value in a database. random() provides insufficient entropy for things like this, so use os.urandom().

The link you posted to has a very good example of how to handle things with a decorator function. In the decorator function, check the appkey value is set in the request, verify it is valid in the database, and then return the function. If the appkey is invalid, raise AuthenticationError("Invalid appkey") and you're done.

The example you linked to is a bit confusing. I like the demonstration from How to make a chain of function decorators? better.

def checkAppKey(fn):
    def inner(*args, **kwargs): #appkey should be in kwargs
        try:
            AppKey.get(appkey)
        except KeyError:
            raise AuthenticationError("Invalid appkey")
            #Whatever other errors can raise up such as db inaccessible
        #We were able to access that API key, so pass onward.
        #If you know nothing else will use the appkey after this, you can unset it.
        return fn(*args, **kwargs)
    return inner

Here is an function that uses hashlib which has worked fairly well for me:

def generate_hash_key():
    """
    @return: A hashkey for use to authenticate agains the API.
    """
    return base64.b64encode(hashlib.sha256(str(random.getrandbits(256))).digest(),
                            random.choice(['rA', 'aZ', 'gQ', 'hH', 'hG', 'aR', 'DD'])).rstrip('==')

A possible solution to implement this in the app could be applying a decorator on each route that you want to protect.

Example:

def get_apiauth_object_by_key(key):
    """
    Query the datastorage for an API key.
    @param ip: ip address
    @return: apiauth sqlachemy object.
    """
    return model.APIAuth.query.filter_by(key=key).first()

def match_api_keys(key, ip):
    """
   Match API keys and discard ip
   @param key: API key from request
   @param ip: remote host IP to match the key.
   @return: boolean
   """
   if key is None or ip is None:
      return False
   api_key = get_apiauth_object_by_key(key)
   if api_key is None:
      return False
   elif api_key.ip == "0.0.0.0":   # 0.0.0.0 means all IPs.
      return True
   elif api_key.key == key and api_key.ip == ip:
      return True
   return False

def require_app_key(f):
   """
   @param f: flask function
   @return: decorator, return the wrapped function or abort json object.
   """

   @wraps(f)
   def decorated(*args, **kwargs):
      if match_api_keys(request.args.get('key'), request.remote_addr):
         return f(*args, **kwargs)
      else:
         with log_to_file:
            log.warning("Unauthorized address trying to use API: " + request.remote_addr)
         abort(401)
      return decorated

And then you can use the decorator as such:

@require_app_key
def delete_cake(version, cake_id):
   """
   Controller for API Function that gets a cake by ID
   @param cake_id: cake id
   @return: Response and HTTP code
   """

This example uses SQLAlchemy to store keys in database (You could use SQLite).

You can see the implementation here: https://github.com/haukurk/flask-restapi-recipe.


The "typical" way to generate an API key is to create a UUID (usually by creating an md5 hash of some subset of user information + somewhat random information (like current time)).

All API keys, however, should be UUIDs. The hexadecimal hash created by md5 meets this requirement, but there are certainly other methods.

Once you've created a key for the user, store it in the database as part of the user information and check that their key (stored in a cookie, usually) matches what you have. The actual mechanics of this are (somewhat) described in the page you linked to.