Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 171 additions & 0 deletions docs/chapter-13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,175 @@ The second one forces the login if needed:
Here ``@action.uses(auth.user)`` tells py4web that this action requires
a logged in user and should redirect to login if no user is logged in.

Custom actions after Auth events
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

After every Auth event, like: password_reset, login, register, verify_email, etc, it is possible to trigger an action.
For exmaple, to redirect a user to specific page after sign up and successfully email verification, we can do the following:

in ``common.py``
.. code:: python
#function triggered after a sign up with email verification sign up.
def after_register_callback(_, user_row):
redirect(URL('pending_registration'))

#function triggered after a successfull email verification.
def after_verify_email_callback(_, user_row):
redirect(URL('success_verification'))


In ``Auth `` section before auth.definetables() or auth.fix_actions(), add:

.. code:: python
# custom action after email verification
auth.on_accept['verify_email'] = after_verify_email_callback
auth.on_accept['register'] = after_register_callback


Example:

.. code:: python
# #######################################################
# Instantiate the object and actions that handle auth
# #######################################################
auth = Auth(session, db, define_tables=False)
auth.use_username = False
auth.param.registration_requires_confirmation = settings.VERIFY_EMAIL #False
auth.param.registration_requires_approval = settings.REQUIRES_APPROVAL #False
auth.param.login_after_registration = settings.LOGIN_AFTER_REGISTRATION #False
auth.param.allowed_actions = settings.ALLOWED_ACTIONS
auth.param.login_expiration_time = 3600
auth.param.password_complexity = {"entropy": 50}
auth.param.block_previous_password_num = 3
auth.param.default_login_enabled = settings.DEFAULT_LOGIN_ENABLED #True

auth.on_accept['verify_email'] = after_verify_email_callback
auth.on_accept['register'] = after_register_callback

auth.define_tables()
auth.fix_actions()



Authentication with CAPTCHA
~~~~~~~~~~~~~~~~~~~~~~~~~~~

CAPTCHAs are essential security measures that prevent automated bot abuse on public forms.
To implement Google reCAPTCHA or hCAPTCHA in your authentication form, follow these steps:

Enabling Google reCAPTCHA
^^^^^^^^^^^^^^^^^^^^^^^^^

In ``settings.py`` add your keys:

.. code:: python

RECAPTCHA_API_SECRET_V3 = "your_recaptcha_secret_key_v3"
RECAPTCHA_API_KEY_V3 = "your_recaptcha_site_key_v3"

RECAPTCHA_API_KEY_V2 = "your_recaptcha_site_key_v2"
RECAPTCHA_API_SECRET_V2 = "your_recaptcha_secret_key_v2"


In ``common.py`` add:

.. code:: python

#import the functionality
from . import settings
from py4web.utils.recaptcha import ReCaptcha

# To use recaptcha v3
recaptcha = ReCaptcha(settings.RECAPTCHA_API_KEY_V3, settings.RECAPTCHA_API_SECRET_V3, "v3")
or
# To use recaptcha v2
recaptcha = ReCaptcha(settings.RECAPTCHA_API_KEY_V2, settings.RECAPTCHA_API_SECRET_V2, "v2")


# in the section that auth is defined
# Example:
auth = Auth(session, db, define_tables=False)

# Add this line at the end of auth declaration to enable recaptcha on login, register and request_reset_password forms.
# or enable it on the action that you want by especifying the action name

#Example:

auth.extra_form_fields = {"login": [recaptcha.field], "register": [recaptcha.field], "request_reset_password": [recaptcha.field], }


#In section where auth is enabled, add the recaptcha fixture
# Example:

# #######################################################
# Enable authentication line
# #######################################################
auth.enable(uses=(session, T, db, recaptcha.fixture),env=dict(T=T))

Finally in ``auth.html`` add:

.. code:: python
[[try:]]
[[=form]]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is the form in a try...except block? Can we avoid it?

Copy link
Contributor Author

@ch-rigu ch-rigu Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello Massimo, The problem is without the try except block, after a completing the captcha, the app return a 500 error, it says "form is not defined". Using a try except block, all work without issues.

I don't know how to make it work without the try except.

Error trace:

127.0.0.1 - "POST /myapp/auth/request_reset_password HTTP/1.1" - 200 5435 > Thread-6 > 22:44:05.638
P1_eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.haJwZACjZXhwzml2x2SncGFzc2tlecUFG5_8HmgHAxZH-fUzVMsLBfEmL79RjKxRxnkRpu8oGwBxF4N--ohMNhTQV_omtyqDE1MDdhNTkxqHNoYXJkX2lkzgRHJZE.7EgVLBSDBINTlbjfY78vAyrf4YbJh7yBXAlwjt5Zpdw
ES_d0a63080d61949c9bda4c945be8bce45
{"success":true,"challenge_ts":"2026-01-26T01:44:12.000000Z","hostname":"127.0.0.1","credit":false}
[2026-01-25T22:44:13.874230]: Traceback (most recent call last):
  File "/Users/lab/Documents/develop/py4web/py4web/core.py", line 1025, in wrapper
    raise http_exception
  File "/Users/lab/Documents/develop/py4web/py4web/core.py", line 1022, in wrapper
    context["output"] = func(*args, **kwargs)
                        ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/lab/Documents/develop/py4web/py4web/utils/auth.py", line 1169, in _
    form=form_factory(), path=path, user=auth.get_user(), **env
         ^^^^^^^^^^^^^^
  File "/Users/lab/Documents/develop/py4web/py4web/utils/auth.py", line 1944, in request_reset_password
    self._postprocessing("request_reset_password", form, None)
  File "/Users/lab/Documents/develop/py4web/py4web/utils/auth.py", line 2158, in _postprocessing
    self.auth.on_accept[action](form, user)
  File "/Users/lab/Documents/develop/py4web/apps/myapp/common.py", line 152, in after_request_reset_password_callback
    redirect(URL('request_reset_password_message'))
  File "/Users/lab/Documents/develop/py4web/py4web/core.py", line 962, in redirect
    raise HTTP(303)
py4web.core.HTTP: 303

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/lab/Documents/develop/py4web/py4web/core.py", line 1061, in wrapper
    ret = func(*func_args, **func_kwargs)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/lab/Documents/develop/py4web/py4web/core.py", line 1046, in wrapper
    raise exception
  File "/Users/lab/Documents/develop/py4web/py4web/core.py", line 1039, in wrapper
    call_f(fixture.on_success, context)
  File "/Users/lab/Documents/develop/py4web/py4web/core.py", line 1004, in call_f
    return f(context)
           ^^^^^^^^^^
  File "/Users/lab/Documents/develop/py4web/py4web/core.py", line 626, in on_success
    context["output"] = render(
                        ^^^^^^^
  File "/Users/lab/Documents/develop/py4web/py4web/core.py", line 588, in render
    return engine.render(filename, context=context)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/lab/venv12/lib/python3.12/site-packages/renoir/apis.py", line 172, in render
    return self._render(source, file_path, context)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/lab/venv12/lib/python3.12/site-packages/renoir/apis.py", line 166, in _render
    make_traceback(exc_info)
  File "/Users/lab/venv12/lib/python3.12/site-packages/renoir/debug.py", line 114, in make_traceback
    reraise(exc_type, exc_value, tb)
  File "/Users/lab/venv12/lib/python3.12/site-packages/renoir/_internal.py", line 15, in reraise
    raise value.with_traceback(tb)
  File "/Users/lab/Documents/develop/py4web/apps/myapp/templates/auth.html", line 75, in template
    [[=form]]
NameError: name 'form' is not defined

Traceback (most recent call last):
  File "/Users/lab/Documents/develop/py4web/py4web/core.py", line 1025, in wrapper
    raise http_exception
  File "/Users/lab/Documents/develop/py4web/py4web/core.py", line 1022, in wrapper
    context["output"] = func(*args, **kwargs)
                        ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/lab/Documents/develop/py4web/py4web/utils/auth.py", line 1169, in _
    form=form_factory(), path=path, user=auth.get_user(), **env
         ^^^^^^^^^^^^^^
  File "/Users/lab/Documents/develop/py4web/py4web/utils/auth.py", line 1944, in request_reset_password
    self._postprocessing("request_reset_password", form, None)
  File "/Users/lab/Documents/develop/py4web/py4web/utils/auth.py", line 2158, in _postprocessing
    self.auth.on_accept[action](form, user)
  File "/Users/lab/Documents/develop/py4web/apps/myapp/common.py", line 152, in after_request_reset_password_callback
    redirect(URL('request_reset_password_message'))
  File "/Users/lab/Documents/develop/py4web/py4web/core.py", line 962, in redirect
    raise HTTP(303)
py4web.core.HTTP: 303

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/lab/Documents/develop/py4web/py4web/core.py", line 1061, in wrapper
    ret = func(*func_args, **func_kwargs)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/lab/Documents/develop/py4web/py4web/core.py", line 1046, in wrapper
    raise exception
  File "/Users/lab/Documents/develop/py4web/py4web/core.py", line 1039, in wrapper
    call_f(fixture.on_success, context)
  File "/Users/lab/Documents/develop/py4web/py4web/core.py", line 1004, in call_f
    return f(context)
           ^^^^^^^^^^
  File "/Users/lab/Documents/develop/py4web/py4web/core.py", line 626, in on_success
    context["output"] = render(
                        ^^^^^^^
  File "/Users/lab/Documents/develop/py4web/py4web/core.py", line 588, in render
    return engine.render(filename, context=context)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/lab/venv12/lib/python3.12/site-packages/renoir/apis.py", line 172, in render
    return self._render(source, file_path, context)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/lab/venv12/lib/python3.12/site-packages/renoir/apis.py", line 166, in _render
    make_traceback(exc_info)
  File "/Users/lab/venv12/lib/python3.12/site-packages/renoir/debug.py", line 114, in make_traceback
    reraise(exc_type, exc_value, tb)
  File "/Users/lab/venv12/lib/python3.12/site-packages/renoir/_internal.py", line 15, in reraise
    raise value.with_traceback(tb)
  File "/Users/lab/Documents/develop/py4web/apps/myapp/templates/auth.html", line 75, in template
    [[=form]]
  NameError: name 'form' is not defined
 > Thread-6 > 22:44:13.875

[[except:]]
[[pass]]
[[=recaptcha]]


After completing these steps, the reCAPTCHA field will be added to the login, register, and request_reset_password forms.

Enabling hCAPTCHA
^^^^^^^^^^^^^^^^^

in ``settings.py`` add your HCAPTCHA_SITE_KEY and HCAPTCHA_SECRET_KEY:

.. code:: python
HCAPTCHA_SITE_KEY = "your_hcaptcha_site_key"
HCAPTCHA_SECRET_KEY = "your_hcaptcha_secret_key"


In ``common.py`` add:

.. code:: python
#import the functionality
from . import settings
from py4web.utils.hcaptcha import Hcaptcha

hcaptcha = Hcaptcha(settings.HCAPTCHA_SITE_KEY, settings.HCAPTCHA_SECRET_KEY)


# in the section that auth is defined
# Example:
auth = Auth(session, db, define_tables=False)

# Add this line at the end of auth declaration to enable hcaptcha on login, register and request_reset_password forms.
# or enable it on the action that you want by especifying the action name

#Example:
auth.extra_form_fields = {"login": [hcaptcha.field], "register": [hcaptcha.field], "request_reset_password": [hcaptcha.field], }

#In section where auth is enabled, add the hcaptcha fixture
# Example:

# #######################################################
# Enable authentication
# #######################################################
auth.enable(uses=(session, T, db, hcaptcha.fixture),env=dict(T=T))


Finally in ``auth.html`` add:

.. code:: python
[[try:]]
[[=form]]
[[except:]]
[[pass]]
[[=hcaptcha]]

After completing these steps, the hCAPTCHA field will be added to the login, register, and request_reset_password forms.



Two Factor Authentication
~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down Expand Up @@ -431,6 +600,8 @@ You will also have to register your OAuth2 redirect URI in your created applicat
As Discord users have no concept of first/last name, the user in the auth table will contain the
Discord username as the first name and discriminator as the last name.



Auth API Plugins
~~~~~~~~~~~~~~~~

Expand Down