Skip to content

Commit

Permalink
custom password verification callback
Browse files Browse the repository at this point in the history
  • Loading branch information
miguelgrinberg committed Nov 26, 2013
1 parent d4cf53d commit 33d60f2
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 89 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ Basic authentication example
if __name__ == '__main__':
app.run()
Note: See the [documentation](http://pythonhosted.org/Flask-HTTPAuth) for more complex examples that involve password hashing and custom verification callbacks.

Digest authentication example
-----------------------------

Expand Down
43 changes: 31 additions & 12 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ If the hashing algorithm requires the username to be known then the callback can
get_salt(username)
return hash(password, salt)

For the most degree of flexibility the `get_password` and `hash_password` callbacks can be replaced with `verify_password`::

@auth.verify_password
def verify_pw(username, password):
return call_custom_verify_function(username, password)

Digest authentication example
-----------------------------

Expand Down Expand Up @@ -99,38 +105,51 @@ API Documentation

.. method:: get_password(password_callback)

*Required*. This callback function will be called by the framework to obtain the password for a given user. Example::
This callback function will be called by the framework to obtain the password for a given user. Example::
@auth.get_password
def get_password(username):
return db.get_user_password(username)

.. method:: hash_password(hash_password_callback)

*Optional*. If defined, this callback function will be called by the framework to apply a custom hashing algorithm to the password provided by the client. If this callback isn't provided the password will be checked unchanged. The callback can take one or two arguments. The one argument version receives the password to hash, while the two argument version receives the username and the password in that order. Example single argument callback::
If defined, this callback function will be called by the framework to apply a custom hashing algorithm to the password provided by the client. If this callback isn't provided the password will be checked unchanged. The callback can take one or two arguments. The one argument version receives the password to hash, while the two argument version receives the username and the password in that order. Example single argument callback::

@auth.hash_password
def hash_password(password):
return md5(password).hexdigest()

Example two argument callback::
Example two argument callback::

@auth.hash_password
def hash_pw(username, password):
get_salt(username)
return hash(password, salt)
@auth.hash_password
def hash_pw(username, password):
get_salt(username)
return hash(password, salt)

.. method:: verify_password(verify_password_callback)

If defined, this callback function will be called by the framework to verify that the username and password combination provided by the client are valid. The callback function takes two arguments, the username and the password and must return ``True`` or ``False``. Example usage::

@auth.verify_password
def verify_password(username, password):
user = User.query.filter_by(username).first()
if not user:
return False
return passlib.hash.sha256_crypt.verify(password, user.password_hash)

Note that when a `verify_password` callback is provided the `get_password` and `hash_password` callbacks are not used.

.. method:: error_handler(error_callback)

*Optional*. If defined, this callback function will be called by the framework when it is necessary to send an authentication error back to the client. The return value from this function can be the body of the response as a string or it can also be a response object created with `make_response`. If this callback isn't provided a default error response is generated. Example::
If defined, this callback function will be called by the framework when it is necessary to send an authentication error back to the client. The return value from this function can be the body of the response as a string or it can also be a response object created with `make_response`. If this callback isn't provided a default error response is generated. Example::
@auth.error_handler
def auth_error():
return "<h1>Access Denied</h1>"

.. method:: login_required(view_function_callback)

*Required*. This callback function will be called when authentication is succesful. This will typically be a Flask view function. Example::
This callback function will be called when authentication is succesful. This will typically be a Flask view function. Example::

@app.route('/private')
@auth.login_required
Expand All @@ -152,15 +171,15 @@ Example two argument callback::

.. method:: get_password(password_callback)

*Required*. See basic authentication for documentation and examples.
See basic authentication for documentation and examples.

.. method:: error_handler(error_callback)

*Optional*. See basic authentication for documentation and examples.
See basic authentication for documentation and examples.

.. method:: login_required(view_function_callback)

*Required*. See basic authentication for documentation and examples.
See basic authentication for documentation and examples.

.. method:: username()

Expand Down
14 changes: 10 additions & 4 deletions flask_httpauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ def decorated(*args, **kwargs):
if not auth:
return self.auth_error_callback()
password = self.get_password_callback(auth.username)
if not password:
return self.auth_error_callback()
#if not password:
# return self.auth_error_callback()
if not self.authenticate(auth, password):
return self.auth_error_callback()
return f(*args, **kwargs)
Expand All @@ -61,21 +61,27 @@ class HTTPBasicAuth(HTTPAuth):
def __init__(self):
super(HTTPBasicAuth, self).__init__()
self.hash_password(None)
self.verify_password(None)

def hash_password(self, f):
self.hash_password_callback = f

def verify_password(self, f):
self.verify_password_callback = f

def authenticate_header(self):
return 'Basic realm="' + self.realm + '"'

def authenticate(self, auth, password):
def authenticate(self, auth, stored_password):
client_password = auth.password
if self.verify_password_callback:
return self.verify_password_callback(auth.username, client_password)
if self.hash_password_callback:
try:
client_password = self.hash_password_callback(client_password)
except TypeError:
client_password = self.hash_password_callback(auth.username, client_password)
return client_password == password
return client_password == stored_password

class HTTPDigestAuth(HTTPAuth):
def __init__(self):
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

setup(
name='Flask-HTTPAuth',
version='2.1.0',
version='2.2.0',
url='http://github.com/miguelgrinberg/flask-httpauth/',
license='MIT',
author='Miguel Grinberg',
Expand Down
Loading

0 comments on commit 33d60f2

Please sign in to comment.