flask-simple-captcha
CURRENT VERSION: v5.5.4
v5.0.0+ added an encryption mechanism to the stored text in the jwts. Previous versions are insecure!
v5.5.4 removed the upper limit on werkzeug/pillow dependency versions
flask-simple-captcha
is a CAPTCHA generator class for generating and validating CAPTCHAs. It allows for easy integration into Flask applications.
See the encryption / decryption breakdown below for more information on the verification mechanism.
Features
- Generates CAPTCHAs with customizable length and characters
- Easy integration with Flask applications
- Built-in image rendering and line drawing for added complexity
- Base64 image encoding for easy embedding into HTML
- Uses JWTs and Werkzeug password hashing for secure CAPTCHA verification
- Successfully submitted CAPTCHAs are stored in-memory to prevent resubmission
- Backwards compatible with 1.0 versions of this package
- Avoids visually similar characters by default
- Supports custom character set provided by user
- Casing of submitted captcha is ignored by default
- Minor random font variation in regards to size/family/etc
- PNG/JPEG image format support
- Customizable text|noise/background colors
Prerequisites
- Python 3.7 or higher
- Werkzeug >=0.16.0, <3
- Pillow >4, <10
Installation
Import this package directly into your Flask project and make sure to install all dependencies.
How to Use
Configuration
DEFAULT_CONFIG = {
'SECRET_CAPTCHA_KEY': 'LONGKEY',
'EXPIRE_SECONDS': 60 * 10,
'CAPTCHA_IMG_FORMAT': 'JPEG',
'CAPTCHA_LENGTH': 6,
'CAPTCHA_DIGITS': False,
'EXCLUDE_VISUALLY_SIMILAR': True,
'BACKGROUND_COLOR': (0, 0, 0),
'TEXT_COLOR': (255, 255, 255),
}
Initialization
Add this code snippet at the top of your application:
from flask_simple_captcha import CAPTCHA
YOUR_CONFIG = {
'SECRET_CAPTCHA_KEY': 'LONG_KEY',
'CAPTCHA_LENGTH': 6,
'CAPTCHA_DIGITS': False,
'EXPIRE_SECONDS': 600,
}
SIMPLE_CAPTCHA = CAPTCHA(config=YOUR_CONFIG)
app = SIMPLE_CAPTCHA.init_app(app)
Protecting a Route
To add CAPTCHA protection to a route, you can use the following code:
@app.route('/example', methods=['GET','POST'])
def example():
if request.method == 'GET':
new_captcha_dict = SIMPLE_CAPTCHA.create()
return render_template('your_template.html', captcha=new_captcha_dict)
if request.method == 'POST':
c_hash = request.form.get('captcha-hash')
c_text = request.form.get('captcha-text')
if SIMPLE_CAPTCHA.verify(c_text, c_hash):
return 'success'
else:
return 'failed captcha'
In your HTML template, you need to wrap the CAPTCHA inputs within a form element. The package will only generate the CAPTCHA inputs but not the surrounding form or the submit button.
<form action="/example" method="post">
{{ captcha_html(captcha)|safe }}
<input type="submit" value="Submit" />
</form>
Example Captcha Images
Here is an example of what the generated CAPTCHA images look like, this is a screen shot from the /images
route of the debug server.
link to image url if the above does not load
Encryption and Decryption Breakdown
Uses a combination of JWTs and Werkzeug's password hashing to encrypt and decrypt CAPTCHA text.
Encryption
- Salting the Text: The CAPTCHA text is salted by appending the secret key at the beginning.
salted_text = secret_key + text
- Hashing: Werkzeug's
generate_password_hash
function is then used to hash the salted CAPTCHA text.
hashed_text = generate_password_hash(salted_text)
- Creating JWT Token: A JWT token is generated using the hashed CAPTCHA text and an optional expiration time.
payload = {
'hashed_text': hashed_text,
'exp': datetime.utcnow() + timedelta(seconds=expire_seconds),
}
return jwt.encode(payload, secret_key, algorithm='HS256')
Decryption
- Decode JWT Token: The JWT token is decoded using the secret key. If the token is invalid or expired, the decryption process will fail.
decoded = jwt.decode(token, secret_key, algorithms=['HS256'])
- Extract Hashed Text: The hashed CAPTCHA text is extracted from the decoded JWT payload.
hashed_text = decoded['hashed_text']
- Verifying the Hash: Werkzeug's
check_password_hash
function is used to verify that the hashed CAPTCHA text matches the original salted CAPTCHA text.
salted_original_text = secret_key + original_text
if check_password_hash(hashed_text, salted_original_text):
return original_text
Development
Setting Up Your Development Environment Without VS Code
-
Create a Virtual Environment:
-
Activate the Virtual Environment:
- Activate the virtual environment to isolate the project dependencies:
-
Install Dependencies:
Install the required dependencies for development:
pip install -r requirements_dev.txt
Install the local flask-simple-captcha package:
pip install .
Running Tests
ENSURE YOU HAVE A VENV NAMED venv
IN THE PROJECT DIRECTORY AND THAT IT IS ACTIVATED AND BOTH THE DEPENDENCIES AND THE LOCAL FLASK-SIMPLE-CAPTCHA PACKAGE ARE INSTALLED IN THE VENV
As of the time of me writing this README (2023-11-15), pytest reports 97% test coverage of the logic in the flask_simple_captcha
package. Should be kept as close to 100% as possible.
Run Tests Without VS Code
Running Tests With VS Code
Simply hit command + shift + p and type "Select And Start Debugging" and select Python: Run tests
. You will want to make sure your venv is installed and activated.
Example Test Output
... previous output omitted for brevity ...
tests.py::TestCaptchaUtils::test_jwtencrypt PASSED
tests.py::TestCaptchaUtils::test_no_hashed_text PASSED
---------- coverage: platform darwin, python 3.8.18-final-0 ----------
Name Stmts Miss Cover Missing
--------------------------------------------------------------------------
flask_simple_captcha/__init__.py 3 0 100%
flask_simple_captcha/captcha_generation.py 78 0 100%
flask_simple_captcha/config.py 10 0 100%
flask_simple_captcha/img.py 56 0 100%
flask_simple_captcha/text.py 25 0 100%
flask_simple_captcha/utils.py 51 0 100%
--------------------------------------------------------------------------
TOTAL 223 0 100%
==================================== 41 passed in 5.53s
Debug Server
Start the debug server without VS Code
- Set Environment Variables:
- Before running the debug Flask server, set the required environment variables:
- On macOS/Linux:
export FLASK_APP=debug_flask_server
export FLASK_DEBUG=1
- On Windows (using Command Prompt):
set FLASK_APP=debug_flask_server
set FLASK_DEBUG=1
- On Windows (using PowerShell):
$env:FLASK_APP="debug_flask_server"
$env:FLASK_DEBUG="1"
- Start the debug Flask server:
- Run the following command to start the debug Flask server:
flask run --no-debugger
- This will start the debug Flask server with the automatic reloader. See the navigation section below on how to access the debug server.
Start the debug server with VS Code
- Hit command + shift + p and type "Select And Start Debugging" and select
Python: Flask
- This will start the debug Flask server with debugging features enabled, including the interactive debugger and automatic reloader.
Accessing the Debug Server
Navigate to localhost:5000
in your browser to view the debug server. You can also navigate to localhost:5000/images
to view 50 CAPTCHA images at once.
Code Examples
For usage and integration examples, including how to use flask-simple-captcha
with WTForms and Flask-Security, check out the Code Examples document.
Contributing
Feel free to open a PR. The project has undergone a recent overhaul to improve the code quality.
If you make changes in the logic, please follow the steps laid out in this document for testing and debugging. Make sure the coverage % stays >= 100% and that you verify manually at least once that things look okay by submitting a real CAPTCHA in the debug server.
The pyproject.toml
has the required configuration for black
and isort
. There is also a vscode settings file equivalent to the pyproject.toml
file in .vscode/settings.json
.
License
MIT
Contact: ccarterdev@gmail.com