Comprehensive Python authentication package bridging Web2 and Web3. Provides JWT authentication, OAuth integration, passwordless login, Web3 wallet authentication, TOTP/2FA, WebAuthn passkeys, and a KDF system that enables blockchain access without crypto knowledge.
- Features
- Requirements
- Installation
- Configuration Setup
- User journey of some functionalities
- Social Providers Login Mechanism (Google, LinkedIn, Facebook, etc.)
- Utility Classes
- Custom JWT Claims
- Logging in BlocAuth
- Rate Limiting
- Step-Up Authentication (TOTP Receipt)
- Contributors
- License
- Acknowledgments
- JWT Authentication (HS256, RS256, ES256 β symmetric and asymmetric)
- Token refresh functionality
- SignUp with email and password
- Login with email and password (Basic Auth)
- Login via OTP (Passwordless login)
- Web3 Wallet Authentication (Ethereum/MetaMask)
- π Passkey/WebAuthn Authentication - Face ID, Touch ID, Windows Hello, hardware keys
- Reset password
- Change password
- Change email
- Google, Facebook, LinkedIn login (OAuth2)
- π KDF (Key Derivation Function) System - Complete Web2βWeb3 bridge
- π Smart Contract Account Integration - ERC-4337 account abstraction
- π Dual Encryption - User password + platform key security
- π± Passwordless Authentication - Email-only blockchain wallet generation
- π Password Management Triggers - Automatic wallet re-encryption
- π― Custom JWT Claims - Extensible claims provider system for adding custom data to tokens
- π‘οΈ Step-Up Authentication - RFC 9470 receipt-based step-up auth for sensitive operations
The Step-Up Authentication module (blockauth.stepup) implements the industry-standard step-up authentication pattern (RFC 9470, PSD2/SCA, Auth0 Step-Up, Fireblocks TAP, AWS IAM MFA session tokens).
After a user completes an additional authentication factor (e.g., TOTP verification), the issuing service creates a short-lived signed receipt (HS256 JWT). The consuming service validates this receipt before allowing sensitive operations. This moves enforcement from the client (SDK) to the backend.
- Short-lived: 120-second TTL by default (configurable)
- Scoped:
audclaim prevents cross-service replay,scoperestricts to operation classes - Anti-IDOR:
submust match the authenticated user - Django-independent: Pure Python + PyJWT, usable in any service
- Generic: Not tied to TOTP specifically -- works with any step-up factor
from blockauth.stepup import ReceiptIssuer
issuer = ReceiptIssuer(
secret=RECEIPT_SHARED_SECRET, # min 32 chars
issuer="my-auth-service",
default_audience="my-wallet-service",
default_scope="mpc",
default_ttl_seconds=120,
)
# After user passes TOTP verification:
receipt_token = issuer.issue(subject=str(user.id))
# Return receipt_token in the API responsefrom blockauth.stepup import ReceiptValidator, ReceiptValidationError
validator = ReceiptValidator(
secret=RECEIPT_SHARED_SECRET, # min 32 chars
expected_audience="my-wallet-service",
expected_scope="mpc",
)
try:
claims = validator.validate(
token=receipt_from_header,
expected_subject=authenticated_user_id, # anti-IDOR check
)
# claims.subject, claims.scope, claims.jti, etc.
except ReceiptValidationError as e:
# e.reason β human-readable message
# e.code β machine-readable code (e.g., "receipt_expired", "receipt_subject_mismatch")
return 403, {"error": e.reason}{
"sub": "user-uuid",
"type": "stepup_receipt",
"aud": "my-wallet-service",
"scope": "mpc",
"iat": 1740000000,
"exp": 1740000120,
"jti": "random-hex-16-bytes",
"iss": "my-auth-service"
}The receipt is typically passed as an HTTP header (X-TOTP-Receipt). The consuming service applies middleware to protected endpoints:
- Header present: Must be valid or request is rejected (403)
- Header absent: Pass through (users who didn't do TOTP, e.g., passkey/EOA users)
- Enforce mode: Reject ALL requests without a valid receipt (opt-in, for strict environments)
| Check | Error Code |
|---|---|
| HS256 signature valid | receipt_signature_invalid |
Not expired (exp > now) |
receipt_expired |
type == "stepup_receipt" |
receipt_wrong_type |
aud matches expected |
receipt_audience_mismatch |
scope matches expected |
receipt_scope_mismatch |
sub matches authenticated user |
receipt_subject_mismatch |
Create an issuer. secret must be >= 32 characters.
issue(subject, *, audience=None, scope=None, ttl_seconds=None)->str(JWT)
Create a validator.
validate(token, *, expected_subject=None)->ReceiptClaims
subject,audience,scope,issued_at,expires_at,jti,issuer
reason(str) β human-readablecode(str) β machine-readable
The KDF System is a revolutionary feature that bridges Web2 and Web3 by enabling email/password users to have blockchain accounts without ever seeing or managing crypto keys. This makes blockchain accessible to billions of Web2 users.
Email + Password β KDF β Private Key β EOA β Smart Contract Account
β β β β β
Web2 Auth Key Derivation Hidden Internal User's Wallet
- π Deterministic Generation: Same email/password always generates same private key
- π Smart Contract Accounts: ERC-4337 accounts with programmable logic
- π Dual Encryption: Private keys encrypted with both user password and platform key
- π± Passwordless Support: Email-only authentication for blockchain wallets
- π Automatic Recovery: Platform can recover any user wallet when needed
- π‘οΈ Enterprise Security: PBKDF2/Argon2 with configurable iterations
Layer 1: User Credentials (Email + Password)
Layer 2: Key Derivation (PBKDF2/Argon2 with 100k+ iterations)
Layer 3: Private Key Generation (32-byte deterministic)
Layer 4: Dual Encryption (AES-256-GCM)
Layer 5: Secure Storage (Database + Platform key backup)
- E-commerce: Users can own NFTs without crypto knowledge
- Gaming: True ownership of in-game items on blockchain
- Social Media: Content creators can monetize with minimal fees
- DeFi: Access to decentralized finance with familiar authentication
- Governance: Participate in DAOs without MetaMask
BLOCK_AUTH_SETTINGS = {
"KDF_ENABLED": True,
"KDF_ALGORITHM": "pbkdf2_sha256", # or "argon2id"
"KDF_ITERATIONS": 100000, # Production: 100k+, Dev: 1k
"KDF_SECURITY_LEVEL": "HIGH", # LOW, MEDIUM, HIGH, CRITICAL
"KDF_MASTER_SALT": "your-32-char-minimum-salt",
"MASTER_ENCRYPTION_KEY": "0x" + "64-char-hex-key",
"PLATFORM_MASTER_SALT": "your-platform-salt-32-chars-minimum",
}- python = ^3.12
- django = 5.1.4
- pyjwt = 2.9.0
- requests = 2.32.3
- djangorestframework = 3.15.2
- setuptools = ^75.6.0
- drf-spectacular = 0.28.0
- drf-spectacular-sidecar = 2025.7.1
- cryptography = ^41.0.0 (for AES-256-GCM encryption)
- web3 = ^6.0.0 (for Ethereum integration)
- eth-account = ^0.9.0 (for wallet management)
- argon2-cffi = ^21.3.0 (for Argon2 KDF algorithm)
uv add "blockauth @ https://github.com/BloclabsHQ/auth-pack/releases/download/v0.3.0/blockauth-0.3.0-py3-none-any.whl"Or with pip:
pip install https://github.com/BloclabsHQ/auth-pack/releases/download/v0.3.0/blockauth-0.3.0-py3-none-any.whluv add "blockauth @ git+https://github.com/BloclabsHQ/auth-pack.git@dev"git clone https://github.com/BloclabsHQ/auth-pack.git
uv pip install -e ./auth-packAdd the package to your Django project's INSTALLED_APPS:
INSTALLED_APPS = [
...
'rest_framework',
'blockauth',
...
]Add the Blockauth authentication classe to your Django project's REST_FRAMEWORK settings.
By this way, the package's authentication classes will be used for the APIs.:
REST_FRAMEWORK = {
...
'DEFAULT_AUTHENTICATION_CLASSES': (
'blockauth.authentication.JWTAuthentication',
),
...
}Configs which can be added to the Django project's settings.py.
If you don't add these configs, the default values will be used which are shown here:
- AUTH_PROVIDERS has no default values, you need to add the values for the providers you want to use. If you do not add them then the social auth URLs related to the providers won't be available. See the following Video tutorials to create OAuth client id & client secret.
- DEFAULT_TRIGGER_CLASSES has default classes implemented within blockauth package. It's recommended to implement own class and add the class path in the settings. Details disccussed in the Utility Classes section.
- DEFAULT_NOTIFICATION_CLASS has default class implemented within blockauth package. It's recommended to implement own class and add the class path in the settings. Details disccussed in the Utility Classes section.
BLOCK_AUTH_SETTINGS = {
"BLOCK_AUTH_USER_MODEL": "{{app_name.model_class_name}}", # replace it with your custom user model class name for Blockauth users
"CLIENT_APP_URL": "http://localhost:3000", # this is the URL of the client app which will communicate with the backend API
"ACCESS_TOKEN_LIFETIME": timedelta(seconds=3600),
"REFRESH_TOKEN_LIFETIME": timedelta(days=1),
"ALGORITHM": "HS256", # Supports: HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512
"AUTH_HEADER_NAME": "HTTP_AUTHORIZATION",
"USER_ID_FIELD": "id", # Field name in the user model which will be used as user id in the JWT token
"JWT_SECRET_KEY": "your-jwt-secret-key-here", # For HS256: shared secret (optional, falls back to Django SECRET_KEY)
# "JWT_PRIVATE_KEY": "<PEM-encoded signing key>", # For RS256/ES256: PEM key (signing)
# "JWT_PUBLIC_KEY": "<PEM-encoded verification key>", # For RS256/ES256: PEM key (verification)
# NOTE: For RS256/ES256, never expose signing keys in logs, version control, or client-side code.
# Use environment variables or a secrets manager in production.
"OTP_VALIDITY": timedelta(minutes=3),
"OTP_LENGTH": 6,
"REQUEST_LIMIT": (3, 30), # (number of request, duration in second) rate limits based on per (identifier, subject, and IP address)
# Email verification settings
"EMAIL_VERIFICATION_REQUIRED": False, # Whether users must verify email before accessing non-auth endpoints
# Feature flags - Enable/disable specific authentication features
"FEATURES": {
# Core authentication features
"SIGNUP": True, # Enable user registration with email/password
"BASIC_LOGIN": True, # Enable email/password login authentication
"PASSWORDLESS_LOGIN": True, # Enable passwordless login with OTP
"WALLET_LOGIN": True, # Enable wallet-based authentication
"TOKEN_REFRESH": True, # Enable JWT token refresh functionality
# Password management
"PASSWORD_RESET": True, # Enable password reset functionality
"PASSWORD_CHANGE": True, # Enable password change for authenticated users
# Email management
"EMAIL_CHANGE": True, # Enable email change functionality
"EMAIL_VERIFICATION": True, # Enable email verification requirement
# Wallet features
"WALLET_EMAIL_ADD": True, # Enable adding email to wallet accounts
# Social authentication (controlled by provider configuration)
"SOCIAL_AUTH": True, # Master switch for social authentication
# Passkey/WebAuthn authentication
"PASSKEY_AUTH": True, # Enable passkey authentication (Face ID, Touch ID, Windows Hello)
},
"AUTH_PROVIDERS": {
"GOOGLE": {
"CLIENT_ID": os.getenv('GOOGLE_CLIENT_ID'),
"CLIENT_SECRET": os.getenv('GOOGLE_CLIENT_SECRET'),
"REDIRECT_URI": os.getenv('GOOGLE_REDIRECT_URI'),
},
"LINKEDIN": {
"CLIENT_ID": os.getenv('LINKEDIN_CLIENT_ID'),
"CLIENT_SECRET": os.getenv('LINKEDIN_CLIENT_SECRET'),
"REDIRECT_URI": os.getenv('LINKEDIN_REDIRECT_URI'),
},
"FACEBOOK": {
"CLIENT_ID": os.getenv('FACEBOOK_CLIENT_ID'),
"CLIENT_SECRET": os.getenv('FACEBOOK_CLIENT_SECRET'),
"REDIRECT_URI": os.getenv('FACEBOOK_REDIRECT_URI'),
}
},
# don't need to add DEFAULT_TRIGGER_CLASSES & DEFAULT_NOTIFICATION_CLASS object if you want to use default classes
"DEFAULT_TRIGGER_CLASSES": {
"POST_SIGNUP_TRIGGER": '{{path.to.your.Class}}', # replace it with your own class path
"PRE_SIGNUP_TRIGGER": '{{path.to.your.Class}}', # replace it with your own class path
"POST_LOGIN_TRIGGER": '{{path.to.your.Class}}', # replace it with your own class path
},
"DEFAULT_NOTIFICATION_CLASS": "{{path.to.your.Class}}", # replace it with your own class path
"BLOCK_AUTH_LOGGER_CLASS": '{{path.to.your.Class}}', # replace it with your own class path
}BlockAuth provides comprehensive Swagger/OpenAPI documentation with detailed descriptions, examples, and security information for all endpoints.
Add the following related things to the Django project's settings.py:
INSTALLED_APPS = [
...
'drf_spectacular',
'drf_spectacular_sidecar',
...
]
REST_FRAMEWORK = {
...
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
...
}
# read more about the settings here: https://drf-spectacular.readthedocs.io/en/latest/readme.html#installation
SPECTACULAR_SETTINGS = {
'TITLE': 'Your API Title',
'DESCRIPTION': 'Your API description here',
'VERSION': '1.0.0',
'SERVE_INCLUDE_SCHEMA': False,
'SWAGGER_UI_SETTINGS': {
'deepLinking': True,
},
}Add the following URL pattern to the Django project's urls.py:
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
urlpatterns += [
# Schema generation endpoint
path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
# Optional UI endpoints
path('api/swagger/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
path('api/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),
]After adding the above URL pattern, you can access the swagger documentation by going to the URL http://localhost:8000/api/swagger/ or
the redoc documentation by going to the URL http://localhost:8000/api/redoc/.
BlockAuth supports feature flags to enable/disable specific authentication features. This allows you to customize which endpoints are available in your application.
- SIGNUP: Enable user registration with email and password
- BASIC_LOGIN: Enable email/password login authentication
- PASSWORDLESS_LOGIN: Enable passwordless login with OTP
- WALLET_LOGIN: Enable wallet-based authentication
- TOKEN_REFRESH: Enable JWT token refresh functionality
- PASSWORD_RESET: Enable password reset functionality
- PASSWORD_CHANGE: Enable password change for authenticated users
- EMAIL_CHANGE: Enable email change functionality
- EMAIL_VERIFICATION: Enable email verification requirement
- WALLET_EMAIL_ADD: Enable adding email to wallet accounts
- SOCIAL_AUTH: Master switch for social authentication
- PASSKEY_AUTH: Enable passkey/WebAuthn authentication (Face ID, Touch ID, Windows Hello, hardware keys)
To disable email change functionality:
from blockauth.constants import Features
BLOCK_AUTH_SETTINGS = {
# ... other settings ...
"FEATURES": {
Features.SIGNUP: True,
Features.BASIC_LOGIN: True,
Features.PASSWORDLESS_LOGIN: True,
Features.WALLET_LOGIN: True,
Features.TOKEN_REFRESH: True,
Features.PASSWORD_RESET: True,
Features.PASSWORD_CHANGE: True,
Features.EMAIL_CHANGE: False, # Disable email change
Features.EMAIL_VERIFICATION: True,
Features.WALLET_EMAIL_ADD: True,
Features.SOCIAL_AUTH: True,
Features.PASSKEY_AUTH: True, # Passkey/WebAuthn authentication
},
}BlockAuth provides constants for better type safety and IDE support:
from blockauth.constants import Features, SocialProviders, URLNames
from blockauth.utils.feature_flags import is_feature_enabled
from blockauth.utils.config import is_social_auth_configured
# Check if a feature is enabled
if is_feature_enabled(Features.EMAIL_CHANGE):
# Show email change form
pass
# Use social provider constants
if is_social_auth_configured(SocialProviders.GOOGLE):
# Configure Google auth
pass
# Use URL name constants
from django.urls import reverse
signup_url = reverse(URLNames.SIGNUP) # Get signup URLYou can also use constants in your configuration for better maintainability:
from blockauth.constants import Features, ConfigKeys
BLOCK_AUTH_SETTINGS = {
ConfigKeys.FEATURES: {
Features.SIGNUP: True,
Features.EMAIL_CHANGE: False,
},
ConfigKeys.ACCESS_TOKEN_LIFETIME: timedelta(hours=1),
ConfigKeys.OTP_VALIDITY: timedelta(minutes=5),
}Some features have logical dependencies:
EMAIL_CHANGErequiresEMAIL_VERIFICATIONto be enabledPASSWORDLESS_LOGINrequiresSIGNUPto be enabled
The system will automatically validate these dependencies and warn about any configuration issues.
β
Type Safety - Use constants instead of string literals
β
IDE Support - Autocomplete and error detection
β
Maintainable - Centralized constants in one place
β
Consistent - Uniform naming across the codebase
β
Scalable - Easy to add new features and constants
β
Documented - Comprehensive documentation and examples
Inherit the blockauth.models.BlockUser model in your custom User model in django project. An example is shown below:
from django.db import models
from blockauth.models.user import BlockUser
class CustomUser(BlockUser):
first_name = models.CharField("First name", max_length=50, null=True, blank=True)
last_name = models.CharField("Last name", max_length=50, null=True, blank=True)
date_joined = models.DateTimeField("date of joining", auto_now_add=True)
is_online = models.BooleanField(default=False)
# Profile Fields
date_of_birth = models.DateField("date of birth", blank=True, null=True)
bio = models.TextField("bio", blank=True, null=True, max_length=500)
class Meta:
db_table = "user"Set this customer user model as the default user model in the Django project's settings.py:
AUTH_USER_MODEL = 'app_name.CustomUser'Make migration related commands in console to reflect database tables related to the app.
python manage.py makemigrations
python manage.py migrateAdd the package's URLs to your Django project's urls.py:
from django.urls import path, include
urlpatterns = [
...
path('api/auth/', include('blockauth.urls')),
...
]The available URLs will be shown in swagger after adding the above URL pattern:
Basic Auth:
-
auth/signup: Request an OTP for signup with email & password. -
auth/signup/otp/resend: Resend OTP for signup with email. -
auth/signup/confirm: Confirm sign up with email and otp. -
auth/login/basic: Login with email and password and get access token, refresh token. -
auth/login/passwordless: Request OTP for passwordless login with email. -
auth/login/passwordless/confirm: Confirm login with email and otp. -
auth/token/refresh: Refresh access token. -
auth/password/reset: Request OTP for password reset with email. -
auth/password/reset/confirm: Confirm password reset with email, otp and new password. -
auth/password/change: Change password with old password and new password while being an authenticated user -
auth/email/change: Request OTP for email change with current email and current password. -
auth/email/change/confirm: Confirm email change with current email, new email and otp.
Web3 Wallet Authentication:
auth/login/wallet: Login with Ethereum wallet signature verification.auth/wallet/email/add/: Add email address for wallet user and automatically send verification.auth/signup/confirm/: Verify email using OTP (works for both signup and wallet email verification).
Passkey/WebAuthn Authentication:
auth/passkey/register/options/: Get registration options for new passkey (requires auth)auth/passkey/register/verify/: Verify passkey registration (requires auth)auth/passkey/auth/options/: Get authentication options (public)auth/passkey/auth/verify/: Verify passkey authentication and get JWT tokens (public)auth/passkey/credentials/: List user's registered passkeys (requires auth)auth/passkey/credentials/<uuid>/: Get, update, or delete a specific passkey (requires auth)
Providers:
-
auth/google: Redirect URL to Google login page. -
auth/google/callback: Callback URL after succesfull Google login. This URL should be added to the Google OAuth2 client configuration. -
auth/linkedin: Redirect URL to LinkedIn login page. -
auth/linkedin/callback: Callback URL to LinkedIn login page. This URL should be added to the LinkedIn OAuth2 client configuration. -
auth/facebook: Redirect URL to Facebook login page. -
auth/facebook/callback: Callback URL to Facebook login page. This URL should be added to the Facebook OAuth2 client configuration.
- The user requests to
auth/signupwith email and password. It will do the following:- Validate the email and password. Also checks whether the email is already registered or not.
- Calls the
PRE_SIGNUP_TRIGGERclass with validated data to perform any pre-signup actions. (This class should be implemented in the project. Currently, a dummy class is used by default.) - Generates an OTP.
- Calls the
DEFAULT_NOTIFICATION_CLASSclass with OTP information to send the OTP to the user. (This class should be implemented in the project. Currently, a dummy class is used by default.) - User created with email & password and
is_verified=Falseby default.
- The user confirms the signup by calling
auth/signup/confirmwith email and OTP. It will do the following:- Validate the OTP and email.
- Updates the user attribute
is_verified=True. - Calls the
POST_SIGNUP_TRIGGERclass with user information to perform any post-signup actions. (This class should be implemented in the project. Currently, a dummy class is used by default.)
- In case if the user wants to resend the OTP, the user can call
auth/signup/otp/resendwith email.
The user can log in with email and password. After successful login, the user will get an access token and a refresh token.
Token validity can be configured in the settings.
- The user requests to
auth/login/passwordlesswith email. It will do the following:- Validate the email.
- Generates an OTP.
- Calls the
DEFAULT_NOTIFICATION_CLASSclass with OTP information to send the OTP to the user.
- The user confirms the login by calling
auth/login/passwordless/confirmwith email and OTP. It will do the following:- Validate the OTP and email.
- If the user is not found in the database, a new user is created with the email only and
is_verified=True. Then calls thePOST_SIGNUP_TRIGGERclass with user information to perform any post-signup actions. - Calls the
POST_LOGIN_TRIGGERclass with user information to perform any post-login actions. (This class should be implemented in the project. Currently, a dummy class is used by default.) - Returns an
access tokenand arefresh token.
The user can refresh the access token with the refresh token. The refresh token is used to generate a new access token. Token validity can be configured in the settings.
- The user requests to
auth/password/resetwith email. It will do the following:- Validate the email.
- Generates an OTP.
- Calls the
DEFAULT_NOTIFICATION_CLASSclass with OTP information to send the OTP to the user.
- The user confirms the password reset by calling
auth/password/reset/confirmwith email, OTP, and new password. It will do the following:- Validate the OTP, email and new password.
- Update the user password with the new password.
- The user requests to
auth/email/changewith current email and password. It will do the following:- Validate the current email and password.
- Generates an OTP.
- Calls the
DEFAULT_NOTIFICATION_CLASSclass with OTP information to send the OTP to the user.
- The user confirms the email change by calling
auth/email/change/confirmwith current email, new email, and OTP. It will do the following:- Validate the current email, new email, and OTP.
- Update the user email with the new email.
BlockAuth supports passwordless authentication using WebAuthn/FIDO2 standard. Users can authenticate using biometrics (Face ID, Touch ID, Windows Hello) or hardware security keys.
- User calls
auth/passkey/register/options/with optional display name - Backend generates registration options (challenge, RP info, user info)
- Frontend calls
navigator.credentials.create()with options (triggers biometric prompt) - User verifies identity with biometric/PIN
- Frontend sends credential response to
auth/passkey/register/verify/ - Backend verifies and stores the credential
- User calls
auth/passkey/auth/options/with optional username - Backend generates authentication options (challenge, allowed credentials)
- Frontend calls
navigator.credentials.get()with options (triggers biometric prompt) - User verifies identity with biometric/PIN
- Frontend sends assertion to
auth/passkey/auth/verify/ - Backend verifies signature and returns JWT tokens
BLOCK_AUTH_SETTINGS = {
"FEATURES": {
"PASSKEY_AUTH": True, # Enable passkey authentication
},
"PASSKEY_RP_ID": "example.com", # Your domain (no protocol)
"PASSKEY_RP_NAME": "My Application",
"PASSKEY_ALLOWED_ORIGINS": ["https://example.com"],
}For detailed documentation, see: Passkey Developer Guide
BlockAuth supports Ethereum wallet-based authentication using cryptographic signature verification. This allows users to authenticate using their Web3 wallets (like MetaMask) without requiring email or password.
- Frontend: User connects their wallet and signs a message (e.g., "ABC")
- Request: Frontend sends wallet address, message, and signature to
auth/login/wallet - Verification: Backend verifies the signature matches the wallet address
- User Creation: If no user exists with this wallet address, a new user is created automatically
- Response: Access token and refresh token are returned
POST /api/v1/auth/login/wallet/
{
"wallet_address": "0x742d35Cc...b4d8b6",
"message": "ABC",
"signature": "0x1234...abcd"
}{
"access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}- Automatic User Creation: New users are created automatically when first authenticating with a wallet
- Signature Verification: Uses Ethereum's cryptographic signature verification
- No Email Required: Users can authenticate using only their wallet address
- Standard JWT Tokens: Returns the same access/refresh token format as other authentication methods
- Trigger Support: Integrates with existing POST_SIGNUP_TRIGGER and POST_LOGIN_TRIGGER classes
- Optional Email Verification: Users can optionally add and verify email addresses after wallet login
- Configurable Email Requirements: Developers can enforce email verification for wallet users via settings
The Web3 wallet authentication requires the following dependencies (already included in pyproject.toml):
web3: Ethereum Web3 libraryeth-account: Ethereum account utilities for signature verification
Wallet users can optionally add and verify email addresses after authentication. This feature is controlled by the EMAIL_VERIFICATION_REQUIRED setting.
Configuration
# settings.py
BLOCK_AUTH_SETTINGS = {
"EMAIL_VERIFICATION_REQUIRED": True, # Enforce email verification for all users
}Email Management Endpoints
auth/wallet/email/add/: Add an email address to wallet user and automatically send verificationauth/signup/confirm/: Verify email using OTP (works for both signup and wallet email verification)
Request Examples
Add Email (automatically sends verification):
POST /api/v1/auth/wallet/email/add/
{
"email": "user@example.com",
"verification_type": "otp"
}Add Email with Link Verification:
POST /api/v1/auth/wallet/email/add/
{
"email": "user@example.com",
"verification_type": "link"
}Verify Email:
POST /api/v1/auth/signup/confirm/
{
"identifier": "user@example.com",
"code": "123456"
}Access Control
When EMAIL_VERIFICATION_REQUIRED is enabled, users without verified email addresses will be restricted from accessing protected endpoints. Use the EmailVerificationPermission class to enforce this restriction on your application endpoints:
from blockauth.utils.permissions import EmailVerificationPermission
class MyProtectedView(APIView):
permission_classes = [IsAuthenticated, EmailVerificationPermission]Protected Endpoints
When EMAIL_VERIFICATION_REQUIRED is enabled, you should use the EmailVerificationPermission class on your application endpoints to restrict access for users without verified email. This includes endpoints like:
- User profile management
- Sensitive operations
- Payment processing
- Data access endpoints
- Any endpoint that requires verified user identity
Note: The auth/wallet/email/add/ endpoint is specifically designed for adding email after wallet signup and will always allow users to add their email address, regardless of the EMAIL_VERIFICATION_REQUIRED setting.
Note: Wallet users authenticate solely via wallet signature verification. Email addresses are optional and can be added after login for additional functionality or compliance requirements. Verification is automatically sent when email is added or during signup. The system reuses existing signup endpoints for email verification to maintain API consistency.
Example: Using Email Verification Permission
# views.py
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from blockauth.utils.permissions import EmailVerificationPermission
class NFTMintingView(APIView):
permission_classes = [IsAuthenticated, EmailVerificationPermission]
def post(self, request):
# This endpoint will only be accessible to users with verified email
# when EMAIL_VERIFICATION_REQUIRED is True
return Response({"message": "NFT minted successfully"})First, create OAuth client configurations for the social providers (Google, LinkedIn, Facebook, etc.) and add the client id and client secret to the settings. Also set the redirect URL to the callback URL of the respective provider. Use the same redirect URL in the auth providers configuration.
- How to create Google OAuth client
- How to create Facebook OAuth client
- How to create LinkedIn OAuth client
- Call the URL
auth/{provider_name}to redirect to the respective provider login page. - The user will provide the credentials on the provider's login page & authorize the app.
- Upon successful login, the user will be redirected to the
REDIRECT_URIwith the code. Here,REDIRECT_URIshould redirect to the developer's frontend app. - The frontend app will call the backend API
auth/{provider_name}/callback?code={code}with the code in query params.
- By following the Oauth2 flow, user data (email, name, etc.) is fetched from the provider.
- The user is then searched in the database via email field provided by the social provider.
If the user is not found in the db, a new user is created with the
email,first_nameandis_verified=True. Then calls thePOST_SIGNUP_TRIGGERclass with provider name, user data from backend & provider to perform any post-signup actions. - Calls the
POST_LOGIN_TRIGGERclass with provider name, user data from backend & provider to perform any post-login actions. - Finally, access token and refresh token generated with user id is returned.
This class is used to send messages to the user. The default class is blockauth.notification.DummyNotification,
which is a dummy class that prints the message to the console.
Developers should implement their own class by inheriting blockauth.notification.BaseNotification and set the path in settings via DEFAULT_NOTIFICATION_CLASS.
Otherwise, the default class will be used.
Currently, the communication class is integrated in the following APIs:
auth/signup: To send OTP/link for signup (automatic).auth/wallet/email/add/: To send OTP/link for wallet email verification (automatic).auth/signup/otp/resend: To resend OTP for signup (legacy endpoint).auth/login/passwordless: To send OTP for passwordless login.auth/password/reset: To send OTP for password reset.auth/password/change: To send password change notification.auth/email/change: To send OTP for email change.
Usage example
from blockauth.notification import BaseNotification
class CustomNotification(BaseNotification):
def notify(self, method: str, event: str, context: dict) -> None:
"""
:param method: delivery method ('email', 'sms')
:param event: notification event (see NotificationEvent constants)
:param context: contains identifier and any extra data
"""
if event == 'otp_request':
self.send_otp(context)
elif event == 'password_change':
self.send_password_change_email(context)
def send_otp(self, context: dict) -> None:
email = context.get('email')
otp = context.get('otp')
print(f"Sending OTP {otp} to email {email}")
def send_password_change_email(self, context: dict) -> None:
email = context.get('email')
print(f"Sending password change notification to email {email}")The following notification events are available (see blockauth.notification.NotificationEvent):
class NotificationEvent:
OTP_REQUEST = "otp_request"
SUCCESS_PASSWORD_RESET = "success_password_reset"
SUCCESS_PASSWORD_CHANGE = "success_password_change"
SUCCESS_EMAIL_CHANGE = "success_email_change"These classes are used to perform some actions before and after the signup and login process.
PRE_SIGNUP_TRIGGER: Called before signup. Default:blockauth.triggers.DummyPreSignupTriggerPOST_SIGNUP_TRIGGER: Called after signup. Default:blockauth.triggers.DummyPostSignupTriggerPOST_LOGIN_TRIGGER: Called after login. Default:blockauth.triggers.DummyPostLoginTriggerPOST_PASSWORD_CHANGE_TRIGGER: Called after password change. Default:blockauth.triggers.DummyPostPasswordChangeTriggerPOST_PASSWORD_RESET_TRIGGER: Called after password reset. Default:blockauth.triggers.DummyPostPasswordResetTrigger
Developers have to implement their own classes by inheriting the respective base classes and set the path in the settings.
Usage example
from blockauth.triggers import BaseTrigger
class CustomPreSignupTrigger(BaseTrigger):
def trigger(self, context: dict) -> None:
# Custom logic before signup
print(f"Custom pre-signup logic with context: {context}")
class CustomPostSignupTrigger(BaseTrigger):
def trigger(self, context: dict) -> None:
# Custom logic after signup
print(f"Custom post-signup logic with context: {context}")
class CustomPostLoginTrigger(BaseTrigger):
def trigger(self, context: dict) -> None:
# Custom logic after login
print(f"Custom post-login logic with context: {context}")BlocAuth provides a unified logging interface for all authentication-related events. This logger supports multiple log levels, each with a unique icon for easy identification.
| Level | Icon | Description |
|---|---|---|
| debug | π | Detailed information for debugging |
| info | βΉοΈ | General information about application events |
| warning | Unusual or unexpected events, not necessarily errors | |
| error | β | Errors that prevent normal execution |
| critical | π₯ | Very serious errors requiring immediate attention |
| exception | π₯ | Exceptions, typically with stack traces |
| trace | π | Fine-grained tracing information |
| notice | π’ | Important but normal events requiring special attention |
| alert | π¨ | Events requiring immediate action, not yet critical |
| fatal | β οΈ | Fatal errors leading to shutdown or unrecoverable failure |
| success | β | Successful completion of an operation |
| pending | β³ | Operations in progress or waiting for completion |
| failure | π | Failed operations or processes |
To use your own logging backend, implement a callback class and set it in your Django settings inside the BLOCK_AUTH_SETTINGS dictionary as BLOCK_AUTH_LOGGER_CLASS.
# myapp/logging.py
class MyBlockAuthLogger:
def log(self, message, data=None, level="info", icon=None):
# You can integrate with Python's logging, send to a service, or print
print(f"{icon} [{level.upper()}] {message} | {data}")# settings.py
BLOCK_AUTH_SETTINGS = {
"BLOCK_AUTH_LOGGER_CLASS": "myapp.logging.MyBlockAuthLogger",
# ... other BlocAuth settings ...
}- The logger class must implement a
log(message, data, level, icon)method. - The
iconargument is a unicode symbol representing the log level. - If
BLOCK_AUTH_LOGGER_CLASSis not set inBLOCK_AUTH_SETTINGS, logging calls will be no-ops.
To protect sensitive user data, BlocAuth automatically removes sensitive fields (such as passwords, tokens, codes, etc.) from all log data before writing to logs.
By default, the following fields are removed: password, new_password, refresh, access, token, code. This list can be extended by maintainers if needed.
All BlocAuth logging calls use this utility to ensure no sensitive information is ever logged.
BlockAuth provides permission classes to control access to endpoints based on email verification status.
A generic permission class that checks if users have verified their email address. This permission can be used on any endpoint to restrict access for users who haven't verified their email.
Configuration:
# settings.py
BLOCK_AUTH_SETTINGS = {
"EMAIL_VERIFICATION_REQUIRED": True, # Enable email verification requirement
# ... other settings
}Usage:
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from blockauth.utils.permissions import EmailVerificationPermission
class ProtectedView(APIView):
permission_classes = [IsAuthenticated, EmailVerificationPermission]
def get(self, request):
# This endpoint will only be accessible to users with verified email
# when EMAIL_VERIFICATION_REQUIRED is True
return Response({"message": "Access granted"})What it checks:
- If email verification is required (configurable via
EMAIL_VERIFICATION_REQUIRED) - If the user has an email address
- If the user's email is verified (
is_verified=True)
Note: This permission is designed to be used on protected endpoints that require email verification. It should NOT be used on endpoints that are specifically for adding email addresses (like auth/wallet/email/add/) since users calling those endpoints are expected to be unverified.
Error Messages:
- "Email address required. Please add an email address to access this endpoint." - When user has no email
- "Email verification required. Please verify your email address to access this endpoint." - When email is not verified
from blockauth.kdf import get_kdf_manager
# Get KDF manager
kdf_manager = get_kdf_manager()
# Create wallet for email user
wallet_data = kdf_manager.create_wallet(
email="user@example.com",
password=EXAMPLE_PASSWORD,
wallet_name="primary"
)
print(f"Wallet Address: {wallet_data['wallet_address']}")
print(f"Wallet ID: {wallet_data['wallet_id']}")# Create wallet for passwordless user
wallet_data = kdf_manager.create_wallet(
email="user@example.com",
wallet_name="primary",
auth_method='passwordless' # No password required
)
print(f"Passwordless Wallet: {wallet_data['wallet_address']}")# Create multiple wallets with different names
wallets = kdf_manager.create_multiple_wallets(
email="user@example.com",
password=EXAMPLE_PASSWORD,
wallet_names=["primary", "trading", "savings"]
)
for wallet in wallets:
print(f"{wallet['wallet_name']}: {wallet['wallet_address']}")Password change triggers fire automatically from the PasswordChangeView. To handle password changes in your app, implement a custom trigger:
from blockauth.triggers import BaseTrigger
class MyPasswordChangeTrigger(BaseTrigger):
def trigger(self, context: dict) -> None:
# context contains: user_id, username, email, trigger_type, timestamp
# NOTE: plaintext passwords are never included in the context
user_id = context['user_id']
# Perform post-password-change actions (e.g., invalidate sessions)# In your Django settings
BLOCK_AUTH_SETTINGS = {
"KDF_ENABLED": True,
"KDF_ALGORITHM": "pbkdf2_sha256", # or "argon2id"
"KDF_ITERATIONS": 100000, # Production: 100k+, Dev: 1k
"KDF_SECURITY_LEVEL": "HIGH", # LOW, MEDIUM, HIGH, CRITICAL
"KDF_MASTER_SALT": "your-32-char-minimum-salt",
"MASTER_ENCRYPTION_KEY": "0x" + "64-char-hex-key",
"PLATFORM_MASTER_SALT": "your-platform-salt-32-chars-minimum",
}BlockAuth provides a flexible JWT claims provider system that allows you to add custom data to JWT tokens. This enables you to include application-specific information (like user roles, permissions, organization IDs, etc.) directly in the authentication token.
- Create a claims provider class with a
get_custom_claimsmethod - Register the provider with the JWT manager
- Custom claims are automatically included in all generated tokens
# myapp/jwt_claims.py
class MyClaimsProvider:
def get_custom_claims(self, user):
return {
"role": user.role,
"organization_id": str(user.organization_id),
"permissions": list(user.permissions.all())
}
# Register in Django app's ready() method
from blockauth.jwt.token_manager import jwt_manager
jwt_manager.register_claims_provider(MyClaimsProvider())For detailed documentation on creating and managing custom JWT claims providers, including best practices, advanced usage, and troubleshooting, see:
Rate limiting is implemented for requests currently. The rate limit is based on the number of requests and the duration. The rate limit can be configured in the settings.
BlockAuth emits observability events from the wallet-login flow via a single callback hook β it does not take a dependency on any particular metrics backend. Consumers wire the events into Prometheus, StatsD, OpenTelemetry, structured logs, or nothing at all.
Set BLOCK_AUTH_SETTINGS["METRICS_CALLBACK"] to the dotted path of a
callable with signature:
def emit(
event: str,
tags: dict | None = None,
*,
duration_s: float | None = None,
count: int = 1,
) -> None: ...If the setting is absent, events are dropped. Any exception raised by the callback is caught and logged β a broken metrics pipe will never fail an auth request.
| Event | When | Tags | Extra |
|---|---|---|---|
wallet_login.challenge_issued |
POST /login/wallet/challenge/ success |
β | β |
wallet_login.success |
POST /login/wallet/ success |
flow ("siwe") |
β |
wallet_login.failure |
POST /login/wallet/ rejection |
code (e.g. nonce_invalid, domain_mismatch, uri_host_mismatch, signature_mismatch, validation_error, ...) |
β |
wallet_login.latency |
Every POST /login/wallet/ (both outcomes) |
outcome ("success" / "failure") |
duration_s |
wallet_nonce.pruned |
Per batch in prune_wallet_nonces |
β | count = rows deleted |
Event names are a stable public contract; adding new events is non-breaking, renaming existing ones is not.
# myapp/observability.py
from prometheus_client import Counter, Histogram
_CHAL = Counter("wallet_login_challenge_issued_total", "...")
_SUCCESS = Counter("wallet_login_success_total", "...", ["flow"])
_FAIL = Counter("wallet_login_failure_total", "...", ["code"])
_LAT = Histogram("wallet_login_latency_seconds", "...", ["outcome"])
_PRUNED = Counter("wallet_nonce_pruned_total", "...")
def emit(event, tags=None, *, duration_s=None, count=1):
tags = tags or {}
if event == "wallet_login.challenge_issued":
_CHAL.inc(count)
elif event == "wallet_login.success":
_SUCCESS.labels(**tags).inc(count)
elif event == "wallet_login.failure":
_FAIL.labels(**tags).inc(count)
elif event == "wallet_login.latency":
_LAT.labels(**tags).observe(duration_s or 0.0)
elif event == "wallet_nonce.pruned":
_PRUNED.inc(count)# settings.py
BLOCK_AUTH_SETTINGS = {
# ...
"METRICS_CALLBACK": "myapp.observability.emit",
}The host service owns the /metrics exposition endpoint (via
django-prometheus, prometheus_client.make_wsgi_app, or a custom
middleware). The default Prometheus registry is process-wide, so any
counter the callback increments is scraped automatically.
The same callback works for any backend β translate the event in the consumer. For structured logging:
import logging
log = logging.getLogger("blockauth.metrics")
def emit(event, tags=None, *, duration_s=None, count=1):
log.info(event, extra={"tags": tags or {}, "duration_s": duration_s, "count": count})The wallet_nonce.pruned counter is only useful if the reaper actually
runs. BlockAuth does not ship a scheduler β the consumer decides.
Recommended cadence: every 5β15 minutes. Example invocations:
# cron / systemd timer
*/10 * * * * python manage.py prune_wallet_nonces
# Celery Beat
from celery import shared_task
from django.core.management import call_command
@shared_task
def prune_wallet_nonces():
call_command("prune_wallet_nonces")# Kubernetes CronJob (excerpt)
apiVersion: batch/v1
kind: CronJob
spec:
schedule: "*/10 * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: prune
image: my-auth-service:latest
command: ["python", "manage.py", "prune_wallet_nonces"]
restartPolicy: OnFailure# ECS scheduled task (Terraform sketch)
resource "aws_cloudwatch_event_rule" "prune_nonces" {
schedule_expression = "rate(10 minutes)"
}
# Target: RunTask on the auth-service task-definition with command
# override ["python", "manage.py", "prune_wallet_nonces"]Default behaviour deletes every expired row plus every consumed row
older than 1 hour. --older-than (seconds), --batch-size, and
--dry-run flags are available; see python manage.py prune_wallet_nonces --help. Failure mode if the command is never run:
unbounded nonce-table growth and replay-window memory pressure.
- Install KDF Dependencies
uv add cryptography web3 eth-account argon2-cffi- Configure KDF Settings
# In your Django settings
BLOCK_AUTH_SETTINGS = {
"KDF_ENABLED": True,
"KDF_ALGORITHM": "pbkdf2_sha256",
"KDF_ITERATIONS": 1000, # Lower for development
"KDF_SECURITY_LEVEL": "LOW",
"KDF_MASTER_SALT": "dev-salt-32-chars-minimum-for-development",
"MASTER_ENCRYPTION_KEY": "0x" + "a" * 64, # Development key
"PLATFORM_MASTER_SALT": "dev-platform-salt-32-chars-minimum",
}# Test KDF wallet creation
from blockauth.kdf import get_kdf_manager
kdf_manager = get_kdf_manager()
# Test with email/password
wallet = kdf_manager.create_wallet(
email="test@example.com",
password=EXAMPLE_PASSWORD
)
assert wallet['wallet_address'].startswith('0x')
assert len(wallet['wallet_address']) == 42- Generate Secure Keys
# Generate master encryption key
openssl rand -hex 32
# Generate platform master salt (minimum 32 characters)
openssl rand -base64 32- Update Production Settings
BLOCK_AUTH_SETTINGS = {
"KDF_ENABLED": True,
"KDF_ALGORITHM": "pbkdf2_sha256",
"KDF_ITERATIONS": 100000, # Production iterations
"KDF_SECURITY_LEVEL": "HIGH",
"KDF_MASTER_SALT": "your-production-salt-32-chars-minimum",
"MASTER_ENCRYPTION_KEY": "0x" + "your-64-char-hex-key",
"PLATFORM_MASTER_SALT": "your-platform-salt-32-chars-minimum",
}- Monitor Performance
- Wallet creation success rate
- Average creation time (< 3 seconds)
- Memory usage per operation (< 100MB)
- Database query performance
blockauth/
βββ __init__.py
βββ apps.py
βββ authentication.py
βββ conf.py
βββ migrations/
β βββ __init__.py
β βββ 0001_initial.py
βββ models/
β βββ __init__.py
β βββ otp.py
β βββ user.py
βββ notification.py
βββ docs/
β βββ __init__.py
β βββ auth_docs.py
β βββ wallet_docs.py
β βββ social_auth.py
βββ schemas/
β βββ __init__.py
β βββ account_settings.py
β βββ examples/
β β βββ __init__.py
β β βββ account_settings.py
β β βββ common.py
β β βββ login.py
β β βββ password_reset.py
β β βββ signup.py
β β βββ social_auth.py
β βββ factory.py
β βββ login.py
β βββ password_reset.py
β βββ signup.py
β βββ social_auth.py
βββ serializers/
β βββ __init__.py
β βββ otp_serializers.py
β βββ user_account_serializers.py
β βββ wallet_serializers.py
βββ triggers.py
βββ urls.py
βββ utils/
β βββ __init__.py
β βββ config.py
β βββ custom_exception.py
β βββ generics.py
β βββ logger.py
β βββ permissions.py
β βββ rate_limiter.py
β βββ social.py
β βββ token.py
β βββ validators.py
β βββ web3/
β βββ wallet.py
βββ kdf/ # π KDF System Module
β βββ __init__.py
β βββ services.py # Core KDF services
β βββ encryption.py # AES-256-GCM encryption
β βββ algorithms/ # KDF algorithms
β β βββ __init__.py
β β βββ pbkdf2.py # PBKDF2 implementation
β β βββ argon2.py # Argon2 implementation
β βββ utils.py # KDF utilities
βββ passkey/ # π Passkey/WebAuthn Module
β βββ __init__.py
β βββ views.py # API views
β βββ models.py # PasskeyCredential, PasskeyChallenge
β βββ config.py # Configuration manager
β βββ constants.py # Constants and defaults
β βββ exceptions.py # Custom exceptions
β βββ services/ # Business logic
β β βββ passkey_service.py # WebAuthn operations
β β βββ challenge_service.py # Challenge management
β βββ storage/ # Credential storage
β β βββ base.py # Abstract interface
β β βββ django_storage.py # Django ORM implementation
β βββ README.md # Developer guide
βββ triggers/ # π Password Management Triggers
β βββ __init__.py
β βββ password_triggers.py # Password change/reset triggers
β βββ dummy_triggers.py # Placeholder triggers
βββ views/
βββ __init__.py
βββ basic_auth_views.py
βββ facebook_auth_views.py
βββ google_auth_views.py
βββ linkedin_auth_views.py
βββ wallet_auth_views.py
MIT License. See LICENSE for details.
- Django
- Django REST framework
- PyJWT
- drf-yasg
- Google OAuth2
- LinkedIn OAuth2
- Facebook OAuth2
- Cryptography - For AES-256-GCM encryption
- Web3.py - For Ethereum integration
- Argon2 - For advanced KDF algorithms