web-auth-sdk
Authorization SDK for building protected Web APIs. It allows your endpoints to perform custom authorization and authentication before the request reaches the view function. Then it passes the consumer object to the view function for further processing.
To access protected APIs, clients should authenticate by passing authorizations. For example, a JWT key can be used as follows:
curl 'http://api.example.com/resources' -H 'Authorization: Bearer eyJ1c2VyX2lkIjoxLCJwZXJtaXNzaW9uX2JpdG'
curl 'http://api.example.com/resources?access_token=eyJ1c2VyX2lkIjoxLCJwZXJtaXNzaW9uX2JpdG'
TIP: When utilizing FastAPI, click the lock symbol on Swagger UI to include your JWT. Run
make startupfor a quick preview.
Requirements
- Python 3.8+
- FastAPI 0.109.0+ (recommended)
- Django 4.0+ (optional)
- Flask 2.0+ (optional)
Installation
pip install web-auth-sdk
or
git clone https://github.com/yangaound/web-auth-sdk
cd web-auth-sdk && poetry install
Permission Representation
-
Permission list, located in the
usr/etc/permissions.jsonfile:permissions = [ {'bitmask_idx': 0, 'codename': 'add_order', 'name': 'Can add order', 'service': 'order'}, {'bitmask_idx': 1, 'codename': 'change_order', 'name': 'Can change order', 'service': 'order'}, {'bitmask_idx': 2, 'codename': 'delete_order', 'name': 'Can delete order', 'service': 'order'}, {'bitmask_idx': 3, 'codename': 'view_order', 'name': 'Can view order', 'service': 'order'}, {'bitmask_idx': 4, 'codename': 'add_tickettype', 'name': 'Can add ticket type', 'service': 'order'}, {'bitmask_idx': 5, 'codename': 'change_tickettype', 'name': 'Can change ticket type', 'service': 'order'}, {'bitmask_idx': 6, 'codename': 'view_tickettype', 'name': 'Can view ticket type', 'service': 'order'}, {'bitmask_idx': 7, 'codename': 'delete_tickettype', 'name': 'Can delete ticket type', 'service': 'order'}, ]
-
How to grant permissions?
Permissions are encoded using a bitmask of length n that is a multiple of 24. Each permission is represented by a
1at the correspondingbitmask_idx-th position in the bitmask, indicating that the permission is granted. -
Base64-encoded bitmask
Bitmask Base64-encoded 111111111111111111111111111111110111111101111111 /////39/ -
Decoded/Encoded JWT
Decoded JWT:
{ "user_id": 1, "permission_bitmask": "/////39/", "iat": 1678798980, "exp": 1678800187 }
Encoded JWT:
eyJ1c2VyX2lkIjoxLCJwZXJtaXNzaW9uX2JpdG1hc2siOiIvLy8vLzM5LyIsImlhdCI6MTY3ODc5ODk4MCwiZXhwIjoxNjc4ODAwMTg3fQ
Development
-
FastAPI
import web_auth @fastapi.get('/tickets') @web_auth.permissions('view_ticket') # Iterable[str] are acceptable async def list_tickets() -> list: return []
-
Django
import web_auth from web_auth.django import DjangoBridge web_auth.configure(bridge_class=DjangoBridge) @web_auth.permissions('view_ticket') def list_tickets(request): pass urlpatterns = [django.urls.path('list-tickets', list_tickets)]
-
Flask
import web_auth from web_auth.flask import FlaskBridge web_auth.configure(bridge_class=FlaskBridge) @flask.route('/tickets', methods=['GET']) @web_auth.permissions('view_ticket') def list_tickets() -> list: return []
-
Use instanced context
import web_auth context = web_auth.make_context(bridge_class='web_auth.fastapi.FastapiBridge') @fastapi.get('/tickets') @context.permissions('view_ticket') async def list_tickets() -> list: return []
-
Retrieve the consumer
import fastapi import web_auth @fastapi.get('/profile') @web_auth.permissions(['view_directory']) def get_profile(consumer: web_auth.Consumer) -> dict: return { 'user': consumer.user.dict(), 'directories': 'get_directories(consumer.user.user_id)', }
-
Implement Fine-Grained Permission Control
import fastapi import web_auth @fastapi.post('/some-action') def some_action(request: fastapi.Request): # Create a context with fastapi bridge class context = web_auth.make_context(bridge_class=web_auth.Config.DEFAULT_BRIDGE_CLASS) # Authorize access with specific permissions (e.g., 'view_directory') # If this request lacks permission, it will raise `web_auth.AuthException` _: web_auth.Consumer = context.bridge.access_control( request=request, permissions={'view_directory'}, aggregation_type=web_auth.PermissionAggregationTypeEnum.ALL, ) # Do some action
-
Customization
- Permission Storage
from typing import Optional import fastapi import requests from web_auth import make_context, Storage, PermissionModel, Context class RESTStorage(Storage): def __init__(self, ttl: int, url: str, context: Optional[Context] = None): self.url = url super().__init__(ttl=ttl, context=context) def _load_permissions(self) -> list[PermissionModel]: return [PermissionModel(**r) for r in requests.get(self.url).json()] my_context = make_context( storage_class=RESTStorage, storage_params={'ttl': 60, 'url': 'http://api.example.com/permissions?format=json'}, ) @fastapi.get('/tickets') @my_context(['view_ticket', 'change_ticket']) def get_tickets() -> list[object]: pass
- Authentication and Authenticated Consumer/User
import pydantic import fastapi from web_auth import make_context, Consumer from web_auth.fastapi import FastapiBridge class AuthenticatedUser(pydantic.BaseModel): account: str class MyFastapiBridge(FastapiBridge): # Inject your consumer here if it's not inherited from the `web_auth.Consumer` consumer_class = Consumer def authenticate(self, request: fastapi.Request) -> Consumer: # Your authenticate logic here return Consumer( permission_bitmask='11101101111', user=AuthenticatedUser(account='52354342/Jack'), ) my_context = make_context(bridge_class=MyFastapiBridge) @fastapi.get('/me') @my_context([]) def get_profile(consumer: Consumer) -> AuthenticatedUser: return consumer.user
- Authorization
import fastapi from web_auth import make_context, BitmaskAuthorization, Consumer, PermissionAggregationTypeEnum from web_auth.fastapi import FastapiBridge class MyAuthorization(BitmaskAuthorization): def authorize( self, consumer: Consumer, permissions: set[str], aggregation_type: PermissionAggregationTypeEnum, ): permission_models = self.context.storage.get_permissions() # Checks whether the `consumer` has the `permissions` in `permission_models` class MyFastapiBridge(FastapiBridge): # Inject your Authorization implementation here, it's default to BitmaskAuthorization. authorization_class = MyAuthorization # Configurate your customization my_context = make_context(bridge_class=MyFastapiBridge) @fastapi.get('/tickets') @my_context(['view_ticket', 'change_ticket']) def get_tickets() -> list[object]: pass
Metadata
Release files for web-auth-sdk 1.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| web_auth_sdk-1.2.0.tar.gz | 14.0 kB | Without provenance | Details for web_auth_sdk-1.2.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| web_auth_sdk-1.2.0-py3-none-any.whl | Python 3 | none | any | Details for web_auth_sdk-1.2.0-py3-none-any.whl |
Total release size: 31.5 kB
Release files / web_auth_sdk-1.2.0.tar.gz
| Download URL | web_auth_sdk-1.2.0.tar.gz |
|---|---|
| Size | 14.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9d5cbcf25b3beb37492b283593ec99cd94c314fbeeb9bfbb3930638d43e2bb77
|
|
BLAKE2b-256 checksum How to use checksums |
bf8e118251dc48529cf7702c3e6ec18017194789a698daf06f08d14e0da98ab6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/1.8.3 CPython/3.9.20 Linux/6.8.0-1014-azure
|
Release files / web_auth_sdk-1.2.0-py3-none-any.whl
| Download URL | web_auth_sdk-1.2.0-py3-none-any.whl |
|---|---|
| Size | 17.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
46dd32dea5cbf5f595ebd9f060fc05d7f2fe97fb847050fb86430a191add1e97
|
|
BLAKE2b-256 checksum How to use checksums |
dc023d4c066c1fb139833e2d17957dac0e94d9135a90783dd2ff4fe3a21648e3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/1.8.3 CPython/3.9.20 Linux/6.8.0-1014-azure
|
Security for web-auth-sdk 1.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.