An effective, simple, and async security library for the Sanic framework.
Project description
Sanic Security
An effective, simple, and async security library for Sanic.
Table of Contents
About The Project
Sanic Security is an authentication, authorization, and verification library designed for use with Sanic. This library contains a variety of features including:
- Login, registration, and authentication
- Two-step verification
- Two-factor authentication
- Captcha
- Wildcard and role based authorization
This repository has been starred by Sanic's core maintainer:
Please visit security.sunsetdeveloper.com for documentation.
Getting Started
In order to get started, please install pip.
Prerequisites
- pip
sudo apt-get install python3-pip
Installation
- Install the Sanic Security pip package.
pip3 install sanic-security
Configuration
Sanic Security configuration is merely an object that can be modified either using dot-notation or like a dictionary.
For example:
from sanic_security.configuration import config
config.SECRET = "This is a big secret. Shhhhh"
config["CAPTCHA_FONT"] = "./resources/captcha.ttf"
You can also use the update() method like on regular dictionaries.
Any environment variables defined with the SANIC_SECURITY_ prefix will be applied to the Config. For example, setting SANIC_SECURITY_SECRET will be loaded by the application automatically and fed into the SECRET config variable.
You can load environment variables with a different prefix via calling the config.load_environment_variables("NEW_PREFIX_")
method.
- Default configuration values:
Key | Value | Description |
---|---|---|
SECRET | This is a big secret. Shhhhh | The secret used by the hashing algorithm for generating and signing JWTs. This should be a string unique to your application. Keep it safe. |
CACHE | ./security-cache | The path used for caching. |
SESSION_SAMESITE | strict | The SameSite attribute of session cookies. |
SESSION_SECURE | False | The Secure attribute of session cookies. |
SESSION_HTTPONLY | True | The HttpOnly attribute of session cookies. HIGHLY recommended that you do not turn this off, unless you know what you are doing. |
SESSION_DOMAIN | None | The Domain attribute of session cookies. |
SESSION_EXPIRES_ON_CLIENT | False | When true, session cookies are removed from the clients browser when the session expires. |
SESSION_ENCODING_ALGORITHM | HS256 | The algorithm used to encode sessions to a JWT. |
SESSION_PREFIX | token | Prefix attached to the beginning of session cookies. |
CAPTCHA_SESSION_EXPIRATION | 60 | The amount of seconds till captcha session expiration on creation. Setting to 0 will disable expiration. |
CAPTCHA_FONT | captcha.ttf | The file path to the font being used for captcha generation. |
TWO_STEP_SESSION_EXPIRATION | 200 | The amount of seconds till two step session expiration on creation. Setting to 0 will disable expiration. |
AUTHENTICATION_SESSION_EXPIRATION | 2692000 | The amount of seconds till authentication session expiration on creation. Setting to 0 will disable expiration. |
ALLOW_LOGIN_WITH_USERNAME | False | Allows login via username and email. |
DATABASE_URL | sqlite://:memory: | Database URL for connecting to the database Sanic Security will use. |
Usage
Sanic Security implementation is easy.
The tables in the below examples represent example request form-data
.
Authentication
- Registration
Phone can be null or empty.
Key | Value |
---|---|
username | example |
example@example.com | |
phone | 19811354186 |
password | testpass |
captcha | Aj8HgD |
@app.post("api/auth/register")
@requires_captcha()
async def on_register(request, captcha_session):
account = await register(request)
two_step_session = await request_two_step_verification(request, account)
await email_code(two_step_session.code) #Custom method for emailing verification code.
response = json("Registration successful!", two_step_session.account.json())
two_step_session.encode(response)
return response
- Verify Account
Key | Value |
---|---|
code | G8ha9nVae |
@app.post("api/auth/verify")
async def on_verify(request):
two_step_session = await verify_account(request)
return json("You have verified your account and may login!", two_step_session.account.json())
- Login
Key | Value |
---|---|
example@example.com | |
password | examplepass |
You can use a username as well as an email for login if ALLOW_LOGIN_WITH_USERNAME
is true in the config.
@app.post("api/auth/login")
async def on_login(request):
authentication_session = await login(request)
response = json("Login successful!", authentication_session.account.json())
authentication_session.encode(response)
return response
- Login (With two-factor authentication)
Key | Value |
---|---|
example@example.com | |
password | example |
You can use a username as well as an email for login if ALLOW_LOGIN_WITH_USERNAME
is true in the config.
@app.post("api/auth/login")
async def on_two_factor_login(request):
authentication_session = await login(request, two_factor=True)
two_step_session = await request_two_step_verification(request, authentication_session.account)
await email_code(two_step_session.code) #Custom method for emailing verification code.
response = json("Login successful! A second factor is now required to be authenticated.", authentication_session.account.json())
authentication_session.encode(response)
two_step_session.encode(response)
return response
- Second Factor
Key | Value |
---|---|
code | G8ha9nVae |
@app.post("api/auth/login/second-factor")
@requires_two_step_verification()
async def on_login_second_factor(request, two_step_session):
authentication_session = await on_second_factor(request)
response = json("Second factor attempt successful! You may now be authenticated!",
authentication_session.account.json())
return response
- Logout
@app.post("api/auth/logout")
@requires_authentication()
async def on_logout(request, authentication_session):
await logout(authentication_session)
response = json("Logout successful!", authentication_session.account.json())
return response
- Requires Authentication
@app.post("api/auth")
@requires_authentication()
async def on_authenticated(request, authentication_session):
return json(f"Hello {authentication_session.account.username}! You have been authenticated.",
authentication_session.account.json())
Captcha
You must download a .ttf font for captcha challenges and define the file's path in the configuration.
Captcha challenge example:
- Request Captcha
@app.post("api/captcha/request")
async def on_request_captcha(request):
captcha_session = await request_captcha(request)
response = await captcha_session.get_image()
captcha_session.encode(response)
return response
- Requires Captcha
Key | Value |
---|---|
captcha | Aj8HgD |
@app.post("api/captcha")
@requires_captcha()
async def on_captcha_attempt(request, captcha_session):
return json("Captcha attempt successful!", captcha_session.json())
Two-step Verification
- Request Two-step Verification
Key | Value |
---|---|
example@example.com | |
captcha | Aj8HgD |
@app.post("api/verification/request")
@requires_captcha()
async def on_request_verification(request, captcha_session):
two_step_session = await request_two_step_verification(request)
await email_code(two_step_session.code) #Custom method for emailing verification code.
response = json("Verification request successful!", two_step_session.account.json())
two_step_session.encode(response)
return response
- Resend Two-step Verification Code
@app.post("api/verification/resend")
async def on_resend_verification(request):
two_step_session = await TwoStepSession.decode(request)
await email_code(two_step_session.code) #Custom method for emailing verification code.
return json("Verification code resend successful!", two_step_session.account.json())
- Requires Two-step Verification
Key | Value |
---|---|
code | G8ha9nVa |
@app.post("api/verification")
@requires_two_step_verification()
async def on_verification(request, two_step_session):
response = json("Two-step verification attempt successful!", two_step_session.account.json())
return response
Authorization
Sanic Security comes with two protocols for authorization: role based and wildcard based permissions.
Role-based permissions is a policy-neutral access-control mechanism defined around roles and privileges.
Wildcard permissions support the concept of multiple levels or parts. For example, you could grant a user the permission
printer:query
, printer:query,delete
, and/or printer:*
.
- Require Permissions
@app.post("api/auth/perms")
@require_permissions("admin:update", "employee:add")
async def on_require_perms(request, authentication_session):
return text("Account permitted.")
- Require Roles
@app.post("api/auth/roles")
@require_roles("Admin", "Moderator")
async def on_require_roles(request, authentication_session):
return text("Account permitted.")
Testing
- Install httpx:
pip3 install httpx
-
Make sure the test Sanic instance (
test/server.py
) is running on your machine. -
Run the unit test client (
test/unit.py
) and wait for results.
Tortoise
Sanic Security uses Tortoise ORM for database operations.
Tortoise ORM is an easy-to-use asyncio ORM (Object Relational Mapper).
- Initialise your models and database like so:
async def init():
await Tortoise.init(
db_url=config.DATABASE_URL,
modules={'models': ['sanic_security.models', 'app.models']}
)
await Tortoise.generate_schemas()
or
register_tortoise(
app,
db_url=config.DATABASE_URL,
modules={"models": ["sanic_security.models", "app.models"]},
generate_schemas=True
)
- Define your models like so:
from tortoise.models import Model
from tortoise import fields
class Tournament(Model):
id = fields.IntField(pk=True)
name = fields.TextField()
- Use it like so:
# Create instance by save
tournament = Tournament(name='New Tournament')
await tournament.save()
# Or by .create()
await Tournament.create(name='Another Tournament')
# Now search for a record
tour = await Tournament.filter(name__contains='Another').first()
print(tour.name)
Support for SQLAlchemy coming soon.
Roadmap
Keep up with Sanic Security's Trello board for a list of proposed features, known issues, and in progress development.
Contributing
Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are greatly appreciated.
- Fork the Project
- Create your Feature Branch (
git checkout -b feature/AmazingFeature
) - Commit your Changes (
git commit -m 'Add some AmazingFeature'
) - Push to the Branch (
git push origin feature/AmazingFeature
) - Open a Pull Request
License
Distributed under the GNU General Public License v3.0. See LICENSE
for more information.
Versioning
0.0.0
-
MAJOR version when you make incompatible API changes,
-
MINOR version when you add functionality in a backwards compatible manner, and
-
PATCH version when you make backwards compatible bug fixes.
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
File details
Details for the file sanic-security-1.3.1.tar.gz
.
File metadata
- Download URL: sanic-security-1.3.1.tar.gz
- Upload date:
- Size: 35.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.4.2 importlib_metadata/4.8.1 pkginfo/1.7.1 requests/2.26.0 requests-toolbelt/0.9.1 tqdm/4.62.3 CPython/3.9.5
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | a14cc5115842b383ebb5258f4e588b296447741b728ff83ee5aefc88a10dc55d |
|
MD5 | 99a6b5f4d9d89dcc6ef4684c5083e79a |
|
BLAKE2b-256 | e94d2b0a57b8a36ffff9a891c15f38516af3b2fb7f570e092822ccf696b1e1db |