Extremely simple, "Good Enough" captcha implemention for flask forms. No server side session library required.
Project description
flask-simple-captcha
CURRENT VERSION: v5.3.0
v5.0.0+ added an encryption mechanism to the stored text in the jwts. Previous versions are insecure!
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
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': 'LONG_KEY', # Used for JWT encoding/
'CAPTCHA_LENGTH': 6 # CAPTCHA text length
'CAPTCHA_DIGITS': False # Include digits in the character pool?
'EXPIRE_SECONDS': 600 # CAPTCHA expiration time in seconds
# 'EXPIRE_MINUTES': 10 # Also supported for backwards compatibility
# 'EXCLUDE_VISUALLY_SIMILAR': True # Optional exclude visually similar characters like 0OIl
# 'ONLY_UPPERCASE': True # Optional only use uppercase characters
# 'CHARACTER_POOL': 'AaBbCc123' # Optional specify character pool
}
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)
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.
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()
render_template('example.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.
<!-- your_template.html -->
<form action="/example" method="post">
{{ captcha_html(captcha)|safe }}
<input type="submit" value="Submit" />
</form>
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:
-
Navigate to the project directory where you've cloned the repository and create a virtual environment named
venv
within the project directory:python -m venv venv/
-
-
Activate the Virtual Environment:
- Activate the virtual environment to isolate the project dependencies:
- On macOS/Linux:
source venv/bin/activate
- On Windows (using Command Prompt):
.\venv\Scripts\activate
- On Windows (using PowerShell):
.\venv\Scripts\Activate.ps1
- On macOS/Linux:
- 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
Run Tests Without VS Code
- Run the tests using the following command (make sure your venv is activated and you are in the project directory)
python -m pytest tests.py -s -vv --cov --cov-report term-missing
- The command runs pytest with flags for verbose output, standard output capture, coverage report, and displaying missing lines in the coverage.
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
===== test session starts =====
flask-simple-captcha/venv/bin/python
cachedir: .pytest_cache
rootdir: /Users/mym2/flask-simple-captcha
plugins: cov-4.1.0
collected 19 items
tests.py::TestCAPTCHA::test_arg_order PASSED
tests.py::TestCAPTCHA::test_backwards_defaults PASSED
tests.py::TestCAPTCHA::test_captcha_html PASSED
tests.py::TestCAPTCHA::test_convert_b64img PASSED
tests.py::TestCAPTCHA::test_create PASSED
tests.py::TestCAPTCHA::test_get_background PASSED
tests.py::TestCAPTCHA::test_init_app PASSED
tests.py::TestCAPTCHA::test_jwt_expiration PASSED
tests.py::TestCAPTCHA::test_repr PASSED
tests.py::TestCAPTCHA::test_reversed_args PASSED
tests.py::TestCAPTCHA::test_verify_duplicate PASSED
tests.py::TestCAPTCHA::test_verify_invalid PASSED
tests.py::TestCAPTCHA::test_verify_valid PASSED
tests.py::TestCaptchaUtils::test_exclude_similar_chars PASSED
tests.py::TestCaptchaUtils::test_gen_captcha_text PASSED
tests.py::TestCaptchaUtils::test_hashed_text PASSED
tests.py::TestCaptchaUtils::test_jwtdecrypt_invalid_token PASSED
tests.py::TestCaptchaUtils::test_jwtdecrypt_valid_token PASSED
tests.py::TestCaptchaUtils::test_jwtencrypt PASSED
---- coverage: platform darwin, python 3.11.5-final-0 ----
Name Stmts Miss Cover Missing
--------------------------------------------------------------------------
__init__.py 0 0 100%
flask_simple_captcha/__init__.py 3 0 100%
flask_simple_captcha/captcha_generation.py 103 10 90% 38-46, 49, 57-58, 64, 193
flask_simple_captcha/config.py 8 1 88% 23
flask_simple_captcha/utils.py 59 3 95% 81, 110, 112
tests.py 135 1 99% 198
--------------------------------------------------------------------------
TOTAL 308 15 95%
============== 19 passed in 5.24s ==============
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 debugging features enabled, including the interactive debugger and automatic reloader. See the navigation section below on how to access the debug server.
- Run the following command to start the debug Flask 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.
Contributing
Feel free to open a PR. The project has undergone a recent overhaul to improve the code quality.
License
MIT
Contact: ccarterdev@gmail.com
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Hashes for flask-simple-captcha-5.3.0.tar.gz
Algorithm | Hash digest | |
---|---|---|
SHA256 | 351d477caed1450a30a14232936b10a9f04536c601dc640418b979b54fc7dcf7 |
|
MD5 | 6a9fd87caa3acc0ddd6db5c05fac5572 |
|
BLAKE2b-256 | d0a0f581f7f52fd05238e7c989b6e49f6da61ac46fe56915cb545650bbc7ee19 |