How to use HCaptcha with nicegui

Posted by : on

Category : python


Introduction & Prerequisites

This is a short description on how to integrate hCaptcha into your nicegui application. We’re using the official authentication example from the nicegui GitHub repository. Just copy the python code from here

As the file already mentions, this is really a basic authentication example showing how to use the AuthMiddleware. Please use a database and hashed passwords for productive systems and make sure the database is properly secured.

There’s not much to do to integrate hCaptcha into the login. The most modifications will take place in the login page and the try_login() method.

Setup an hCaptcha account

Creating an hCaptcha account is not a rocket science. Just register there or link your existing GitHub account to hCaptcha. After the successful registration at the service, you’ll be presented your sitekey and you’re asked to generate a secret.

To not expose these in our code we’ll store these in a .env file that is loaded when we start out nicegui app.

HCAPTCHA_SITEKEY=<your sitekey>
HCAPTCHA_SECRETKEY=<your secret key>

In case you encounter any issues during following the hCaptcha integration into nicegui, checkout the hCaptcha Docs as they’re quite helpful. Also, to avoid having to solve a captcha everytime we reload the application, you can use their Test Key Set during development.

Site Key: 20000000-ffff-ffff-ffff-000000000002
Secret Key: 0x0000000000000000000000000000000000000000
Response Token: 20000000-aaaa-bbbb-cccc-000000000002

The response token is the answer you’ll get from hCaptcha. For the Test Key Set the response is static and will not change, but no worries, it’ll change if you’re using your own keys.

Modifying the existing code

Jump right into the official example authentication app provided by nicegui. We will not change the authentication mechanism, but add the hCaptcha as a second layer to the authentication.

Adding some imports

First of all, we need some new imports. Make sure to add the following imports to your app. In case some module is missing, install it with pip.

from dotenv import load_dotenv
import requests
import json
import os

Add the call to load_dotenv() just below your imports.

Now we’re ready to implement the hCaptcha magic to our login. We need to load some javascript library from hcaptcha to enable the app to load and show the hCaptcha. Also, as nicegui does not implement forms for the login, we need a way to fetch the response from hCaptcha which is normally send in the form parameters of an HTML form.

Loading and preparing some Javascript

Add the following code right below def login(...):

ui.add_head_html("""
    <script src="https://js.hcaptcha.com/1/api.js" async defer></script>
    <script>get_hcaptcha_response = () => {return document.getElementsByTagName("iframe")[0].attributes.getNamedItem("data-hcaptcha-response").nodeValue;}</script>
""")

Add the hCaptcha container

To add the hCaptcha iframe to our login card, we need to add a div element with class h-captcha between our password input and the login button. The div also contains the hCaptcha sitekey that we defined in the .env file. The variables defined in there will be loaded into our environment when we call load_dotenv() at the top of our app. Check this part of the hCaptcha docs to see what properties we can add to the element. In this case, the mandatory data-sitekey has been added and also the data-theme to make the hCaptcha checkbox to appear in dark mode (as I run the app in dark mode).

ui.element("div").classes("h-captcha").props(f'data-sitekey="{os.getenv("HCAPTCHA_SITEKEY")}" data-theme="dark"')

Also, make sure to make the login() function async by changing def to async def.

Status check of hCaptcha challenge

When solving a hCaptcha challenge, the verification endpoint of hCaptcha returns some JSON with further information on whether the challenge was solved, and if not, it provides useful error information.

Within async def login we define a new method called check_captcha that returns True if the captcha was successfully solved or False if the attempt of solving it failed. We first get the response that was generated by the hCaptcha challenge endpoint, along with the remote IP. With this information and our secret and site key, we can generate a payload that we then send to the verification endpoint. The response comes in JSON and contains some information that is also documented in the hCaptcha docs. We only use the success and error-codes fields in that particular response to either return True and continue the login process or return False and show a notification with the error-codes.

async def check_captcha() -> bool:
    h_captcha_response = await ui.run_javascript("get_hcaptcha_response();")
    ip = client.environ['asgi.scope']['client'][0]
    payload = {
        "secret": os.getenv("HCAPTCHA_SECRETKEY"),
        "response": h_captcha_response,
        "remoteip": ip,
        "sitekey": os.getenv("HCAPTCHA_SITEKEY")
    }
    res = requests.post(url="https://api.hcaptcha.com/siteverify", data=payload)
    try:
        res = res.json()
        if res.get("success", False):
            return True
        else:
            ui.notify(f"hCaptcha Error: {','.join(res.get('error-codes', []))}")
    except Exception as e:
        print(repr(e))
        return False

Adding hCaptcha check to try_login()

Now we need to modify the try_login() function to not only check for the credentials, but also check if the captcha has been solved successfully. To do so, we add only two lines of code to it.

async def try_login() -> None:
    captcha_passed = await check_captcha()
    if captcha_passed:
        if passwords.get(username.value) == password.value:
                app.storage.user.update({"username": username.value, "authenticated": True})
                ui.open(app.storage.user.get("referrer_path", "/"))  # Go back to where the user wanted to go
        else:
            ui.notify("Wrong username or password", color="negative")
    else:
        ui.notify("Captcha not passed", color="negative")

Full Code

Here’s the fully adapted sourcecode of the official authentication example: github.com

Image Sources


About Luna
Luna

Hi, my name is Luna. I do stuff sNaKe-StYlE!

Email :

Website : https://simplylu.me

About Luna S

Hi, my name is Luna. I do stuff sNaKe-StYlE!

Categories
Useful Links