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
- Thumbnail: fluentforms.com