{"id":48634882,"url":"https://github.com/bloclabshq/auth-pack","last_synced_at":"2026-04-24T05:01:08.925Z","repository":{"id":350121263,"uuid":"913222392","full_name":"BloclabsHQ/auth-pack","owner":"BloclabsHQ","description":"Comprehensive Python authentication package bridging Web2 and Web3 for Django REST Framework","archived":false,"fork":false,"pushed_at":"2026-04-21T05:02:35.000Z","size":1550,"stargazers_count":4,"open_issues_count":3,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-21T06:42:11.721Z","etag":null,"topics":["authentication","django","drf","ethereum","jwt","kdf","oauth","passkeys","python","rest-api","security","totp","web3","webauthn"],"latest_commit_sha":null,"homepage":"https://github.com/BloclabsHQ/auth-pack/wiki","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/BloclabsHQ.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-01-07T09:12:17.000Z","updated_at":"2026-04-21T05:02:22.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/BloclabsHQ/auth-pack","commit_stats":null,"previous_names":["bloclabshq/auth-pack"],"tags_count":20,"template":false,"template_full_name":null,"purl":"pkg:github/BloclabsHQ/auth-pack","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BloclabsHQ%2Fauth-pack","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BloclabsHQ%2Fauth-pack/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BloclabsHQ%2Fauth-pack/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BloclabsHQ%2Fauth-pack/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/BloclabsHQ","download_url":"https://codeload.github.com/BloclabsHQ/auth-pack/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BloclabsHQ%2Fauth-pack/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32209895,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-24T03:15:14.334Z","status":"ssl_error","status_checked_at":"2026-04-24T03:15:11.608Z","response_time":64,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authentication","django","drf","ethereum","jwt","kdf","oauth","passkeys","python","rest-api","security","totp","web3","webauthn"],"created_at":"2026-04-09T08:59:51.800Z","updated_at":"2026-04-24T05:01:08.913Z","avatar_url":"https://github.com/BloclabsHQ.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# BlockAuth\n\n[![Documentation](https://img.shields.io/badge/docs-GitHub%20Pages-blue)](https://bloclabshq.github.io/auth-pack/)\n[![Release](https://img.shields.io/github/v/release/BloclabsHQ/auth-pack)](https://github.com/BloclabsHQ/auth-pack/releases/latest)\n[![Tests](https://img.shields.io/badge/tests-333%20passing-brightgreen)]()\n[![License](https://img.shields.io/github/license/BloclabsHQ/auth-pack)](LICENSE)\n\nComprehensive 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.\n\n**[Read the full documentation](https://bloclabshq.github.io/auth-pack/)**\n\n## Table of Contents\n- [Features](#features)\n- [Requirements](#requirements)\n- [Installation](#installation)\n- [Configuration Setup](#configuration-Setup)\n  - [Django Configs](#django-configs)\n  - [BlockAuth Configs](#blockauth-configs)\n  - [Spectacular(API documentation) Configs](#spectacularapi-documentation-configs)\n  - [Inherit Blockauth User Model](#inherit-blockauth-user-model)\n  - [Add URLs](#add-urls)\n- [User journey of some functionalities](#user-journey-of-some-functionalities)\n  - [Sign up](#sign-up)\n  - [Basic Login](#basic-login)\n  - [Passwordless Login](#passwordless-login)\n  - [Token Refresh](#token-refresh)\n  - [Password Reset](#password-reset)\n  - [Change Email](#change-email)\n  - [Passkey/WebAuthn Authentication](#passkeywebauthn-authentication)\n  - [Web3 Wallet Authentication](#web3-wallet-authentication)\n- [Social Providers Login Mechanism (Google, LinkedIn, Facebook, etc.)](#social-providers-login-mechanism-google-linkedin-facebook-etc)\n- [Utility Classes](#utility-classes)\n  - [Communication Class](#communication-class)\n  - [Trigger Classes](#trigger-classes)\n- [Custom JWT Claims](#custom-jwt-claims)\n- [Logging in BlocAuth](#logging-in-blocauth)\n  - [Supported Log Levels and Icons](#supported-log-levels-and-icons)\n  - [Custom Logger Integration](#custom-logger-integration)\n  - [Example: Custom Logger Class](#example-custom-logger-class)\n  - [Django Settings Configuration](#django-settings-configuration)\n- [Rate Limiting](#rate-limiting)\n- [Step-Up Authentication (TOTP Receipt)](#step-up-authentication-totp-receipt)\n- [Contributors](#contributors)\n- [License](#license)\n- [Acknowledgments](#acknowledgments)\n\n## Features\n\n- JWT Authentication (HS256, RS256, ES256 — symmetric and asymmetric)\n- Token refresh functionality\n- SignUp with email and password\n- Login with email and password (Basic Auth)\n- Login via OTP (Passwordless login)\n- Web3 Wallet Authentication (Ethereum/MetaMask)\n- **🔐 Passkey/WebAuthn Authentication** - Face ID, Touch ID, Windows Hello, hardware keys\n- Reset password\n- Change password\n- Change email\n- Google, Facebook, LinkedIn login (OAuth2)\n- **🔐 KDF (Key Derivation Function) System** - Complete Web2→Web3 bridge\n- **🚀 Smart Contract Account Integration** - ERC-4337 account abstraction\n- **🔑 Dual Encryption** - User password + platform key security\n- **📱 Passwordless Authentication** - Email-only blockchain wallet generation\n- **🔄 Password Management Triggers** - Automatic wallet re-encryption\n- **🎯 Custom JWT Claims** - Extensible claims provider system for adding custom data to tokens\n- **🛡️ Step-Up Authentication** - RFC 9470 receipt-based step-up auth for sensitive operations\n\n---\n\n## Step-Up Authentication (TOTP Receipt)\n\n### Overview\n\nThe **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).\n\nAfter 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.\n\n### Key Properties\n\n- **Short-lived**: 120-second TTL by default (configurable)\n- **Scoped**: `aud` claim prevents cross-service replay, `scope` restricts to operation classes\n- **Anti-IDOR**: `sub` must match the authenticated user\n- **Django-independent**: Pure Python + PyJWT, usable in any service\n- **Generic**: Not tied to TOTP specifically -- works with any step-up factor\n\n### Quick Start\n\n#### Issuing Service (e.g., auth service)\n\n```python\nfrom blockauth.stepup import ReceiptIssuer\n\nissuer = ReceiptIssuer(\n    secret=RECEIPT_SHARED_SECRET,  # min 32 chars\n    issuer=\"my-auth-service\",\n    default_audience=\"my-wallet-service\",\n    default_scope=\"mpc\",\n    default_ttl_seconds=120,\n)\n\n# After user passes TOTP verification:\nreceipt_token = issuer.issue(subject=str(user.id))\n# Return receipt_token in the API response\n```\n\n#### Consuming Service (e.g., wallet service)\n\n```python\nfrom blockauth.stepup import ReceiptValidator, ReceiptValidationError\n\nvalidator = ReceiptValidator(\n    secret=RECEIPT_SHARED_SECRET,  # min 32 chars\n    expected_audience=\"my-wallet-service\",\n    expected_scope=\"mpc\",\n)\n\ntry:\n    claims = validator.validate(\n        token=receipt_from_header,\n        expected_subject=authenticated_user_id,  # anti-IDOR check\n    )\n    # claims.subject, claims.scope, claims.jti, etc.\nexcept ReceiptValidationError as e:\n    # e.reason — human-readable message\n    # e.code — machine-readable code (e.g., \"receipt_expired\", \"receipt_subject_mismatch\")\n    return 403, {\"error\": e.reason}\n```\n\n### Receipt JWT Claims\n\n```json\n{\n  \"sub\": \"user-uuid\",\n  \"type\": \"stepup_receipt\",\n  \"aud\": \"my-wallet-service\",\n  \"scope\": \"mpc\",\n  \"iat\": 1740000000,\n  \"exp\": 1740000120,\n  \"jti\": \"random-hex-16-bytes\",\n  \"iss\": \"my-auth-service\"\n}\n```\n\n### Middleware Pattern (Header-Based)\n\nThe receipt is typically passed as an HTTP header (`X-TOTP-Receipt`). The consuming service applies middleware to protected endpoints:\n\n- **Header present**: Must be valid or request is rejected (403)\n- **Header absent**: Pass through (users who didn't do TOTP, e.g., passkey/EOA users)\n- **Enforce mode**: Reject ALL requests without a valid receipt (opt-in, for strict environments)\n\n### Validation Checks\n\n| Check | Error Code |\n|---|---|\n| HS256 signature valid | `receipt_signature_invalid` |\n| Not expired (`exp \u003e now`) | `receipt_expired` |\n| `type == \"stepup_receipt\"` | `receipt_wrong_type` |\n| `aud` matches expected | `receipt_audience_mismatch` |\n| `scope` matches expected | `receipt_scope_mismatch` |\n| `sub` matches authenticated user | `receipt_subject_mismatch` |\n\n### API Reference\n\n#### `ReceiptIssuer(secret, *, issuer, default_audience, default_scope, default_ttl_seconds)`\n\nCreate an issuer. `secret` must be \u003e= 32 characters.\n\n- `issue(subject, *, audience=None, scope=None, ttl_seconds=None)` -\u003e `str` (JWT)\n\n#### `ReceiptValidator(secret, *, expected_audience, expected_scope)`\n\nCreate a validator.\n\n- `validate(token, *, expected_subject=None)` -\u003e `ReceiptClaims`\n\n#### `ReceiptClaims` (frozen dataclass)\n\n- `subject`, `audience`, `scope`, `issued_at`, `expires_at`, `jti`, `issuer`\n\n#### `ReceiptValidationError`\n\n- `reason` (str) — human-readable\n- `code` (str) — machine-readable\n\n---\n\n## 🔐 KDF (Key Derivation Function) System\n\n### Overview\n\nThe **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.\n\n### How It Works\n\n```\nEmail + Password → KDF → Private Key → EOA → Smart Contract Account\n     ↓              ↓         ↓         ↓           ↓\n  Web2 Auth    Key Derivation  Hidden   Internal   User's Wallet\n```\n\n### Key Features\n\n- **🔐 Deterministic Generation**: Same email/password always generates same private key\n- **🚀 Smart Contract Accounts**: ERC-4337 accounts with programmable logic\n- **🔑 Dual Encryption**: Private keys encrypted with both user password and platform key\n- **📱 Passwordless Support**: Email-only authentication for blockchain wallets\n- **🔄 Automatic Recovery**: Platform can recover any user wallet when needed\n- **🛡️ Enterprise Security**: PBKDF2/Argon2 with configurable iterations\n\n### Security Architecture\n\n```\nLayer 1: User Credentials (Email + Password)\nLayer 2: Key Derivation (PBKDF2/Argon2 with 100k+ iterations)\nLayer 3: Private Key Generation (32-byte deterministic)\nLayer 4: Dual Encryption (AES-256-GCM)\nLayer 5: Secure Storage (Database + Platform key backup)\n```\n\n### Use Cases\n\n- **E-commerce**: Users can own NFTs without crypto knowledge\n- **Gaming**: True ownership of in-game items on blockchain\n- **Social Media**: Content creators can monetize with minimal fees\n- **DeFi**: Access to decentralized finance with familiar authentication\n- **Governance**: Participate in DAOs without MetaMask\n\n### Configuration\n\n```python\nBLOCK_AUTH_SETTINGS = {\n    \"KDF_ENABLED\": True,\n    \"KDF_ALGORITHM\": \"pbkdf2_sha256\",  # or \"argon2id\"\n    \"KDF_ITERATIONS\": 100000,           # Production: 100k+, Dev: 1k\n    \"KDF_SECURITY_LEVEL\": \"HIGH\",       # LOW, MEDIUM, HIGH, CRITICAL\n    \"KDF_MASTER_SALT\": \"your-32-char-minimum-salt\",\n    \"MASTER_ENCRYPTION_KEY\": \"0x\" + \"64-char-hex-key\",\n    \"PLATFORM_MASTER_SALT\": \"your-platform-salt-32-chars-minimum\",\n}\n```\n\n---\n\n## Requirements\n\n- python = ^3.12\n- django = 5.1.4\n- pyjwt = 2.9.0\n- requests = 2.32.3\n- djangorestframework = 3.15.2\n- setuptools = ^75.6.0\n- drf-spectacular = 0.28.0\n- drf-spectacular-sidecar = 2025.7.1\n\n### KDF System Requirements\n\n- **cryptography** = ^41.0.0 (for AES-256-GCM encryption)\n- **web3** = ^6.0.0 (for Ethereum integration)\n- **eth-account** = ^0.9.0 (for wallet management)\n- **argon2-cffi** = ^21.3.0 (for Argon2 KDF algorithm)\n\n## Installation\n\n#### From GitHub Releases (recommended)\n\n```bash\nuv add \"blockauth @ https://github.com/BloclabsHQ/auth-pack/releases/download/v0.3.0/blockauth-0.3.0-py3-none-any.whl\"\n```\n\nOr with pip:\n```bash\npip install https://github.com/BloclabsHQ/auth-pack/releases/download/v0.3.0/blockauth-0.3.0-py3-none-any.whl\n```\n\n#### From Git (development)\n\n```bash\nuv add \"blockauth @ git+https://github.com/BloclabsHQ/auth-pack.git@dev\"\n```\n\n#### Editable Mode (local development)\n\n```bash\ngit clone https://github.com/BloclabsHQ/auth-pack.git\nuv pip install -e ./auth-pack\n```\n\n\n## Configuration Setup\n\n### Django Configs\nAdd the package to your Django project's `INSTALLED_APPS`:\n\n```python\nINSTALLED_APPS = [\n    ...\n    'rest_framework',\n    'blockauth',\n    ...\n]\n```\n\nAdd the Blockauth authentication classe to your Django project's `REST_FRAMEWORK` settings.\nBy this way, the package's authentication classes will be used for the APIs.:\n\n```python\nREST_FRAMEWORK = {\n    ...\n    'DEFAULT_AUTHENTICATION_CLASSES': (\n        'blockauth.authentication.JWTAuthentication',\n    ),\n    ...\n}\n```\n\n\n### BlockAuth Configs\nConfigs which can be added to the Django project's `settings.py`. \nIf you don't add these configs, the default values will be used which are shown here:\n\n- _**AUTH_PROVIDERS** has no default values, you need to add the values for the providers you want to use. \nIf you do not add them then the social auth URLs related to the providers won't be available.\nSee the following [Video tutorials](#social-providers-login-mechanism-google-linkedin-facebook-etc) to create OAuth client id \u0026 client secret._\n- _**DEFAULT_TRIGGER_CLASSES** has default classes implemented within blockauth package. It's recommended to implement\nown class and add the class path in the settings. Details disccussed in the [Utility Classes](#utility-classes) section._ \n- _**DEFAULT_NOTIFICATION_CLASS** has default class implemented within blockauth package. It's recommended to implement\nown class and add the class path in the settings. Details disccussed in the [Utility Classes](#utility-classes) section._\n\n```python\nBLOCK_AUTH_SETTINGS = {\n    \"BLOCK_AUTH_USER_MODEL\": \"{{app_name.model_class_name}}\",  # replace it with your custom user model class name for Blockauth users\n    \"CLIENT_APP_URL\": \"http://localhost:3000\", # this is the URL of the client app which will communicate with the backend API\n\n    \"ACCESS_TOKEN_LIFETIME\": timedelta(seconds=3600),\n    \"REFRESH_TOKEN_LIFETIME\": timedelta(days=1),\n    \"ALGORITHM\": \"HS256\",           # Supports: HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512\n    \"AUTH_HEADER_NAME\": \"HTTP_AUTHORIZATION\",\n    \"USER_ID_FIELD\": \"id\",   # Field name in the user model which will be used as user id in the JWT token\n    \"JWT_SECRET_KEY\": \"your-jwt-secret-key-here\",  # For HS256: shared secret (optional, falls back to Django SECRET_KEY)\n    # \"JWT_PRIVATE_KEY\": \"\u003cPEM-encoded signing key\u003e\",   # For RS256/ES256: PEM key (signing)\n    # \"JWT_PUBLIC_KEY\": \"\u003cPEM-encoded verification key\u003e\", # For RS256/ES256: PEM key (verification)\n    # NOTE: For RS256/ES256, never expose signing keys in logs, version control, or client-side code.\n    # Use environment variables or a secrets manager (1Password, AWS Secrets Manager) in production.\n\n    \"OTP_VALIDITY\": timedelta(minutes=3),\n    \"OTP_LENGTH\": 6,\n    \"REQUEST_LIMIT\": (3, 30),  # (number of request, duration in second) rate limits based on per (identifier, subject, and IP address)\n    \n    # Email verification settings\n    \"EMAIL_VERIFICATION_REQUIRED\": False,  # Whether users must verify email before accessing non-auth endpoints\n    \n    # Feature flags - Enable/disable specific authentication features\n    \"FEATURES\": {\n        # Core authentication features\n        \"SIGNUP\": True,                    # Enable user registration with email/password\n        \"BASIC_LOGIN\": True,               # Enable email/password login authentication\n        \"PASSWORDLESS_LOGIN\": True,        # Enable passwordless login with OTP\n        \"WALLET_LOGIN\": True,              # Enable wallet-based authentication\n        \"TOKEN_REFRESH\": True,             # Enable JWT token refresh functionality\n        \n        # Password management\n        \"PASSWORD_RESET\": True,            # Enable password reset functionality\n        \"PASSWORD_CHANGE\": True,           # Enable password change for authenticated users\n        \n        # Email management\n        \"EMAIL_CHANGE\": True,              # Enable email change functionality\n        \"EMAIL_VERIFICATION\": True,        # Enable email verification requirement\n        \n        # Wallet features\n        \"WALLET_EMAIL_ADD\": True,          # Enable adding email to wallet accounts\n        \n        # Social authentication (controlled by provider configuration)\n        \"SOCIAL_AUTH\": True,               # Master switch for social authentication\n\n        # Passkey/WebAuthn authentication\n        \"PASSKEY_AUTH\": True,              # Enable passkey authentication (Face ID, Touch ID, Windows Hello)\n    },\n        \n    \"AUTH_PROVIDERS\": {\n        \"GOOGLE\": {\n            \"CLIENT_ID\": os.getenv('GOOGLE_CLIENT_ID'),\n            \"CLIENT_SECRET\": os.getenv('GOOGLE_CLIENT_SECRET'),\n            \"REDIRECT_URI\": os.getenv('GOOGLE_REDIRECT_URI'),\n        },\n        \"LINKEDIN\": {\n            \"CLIENT_ID\": os.getenv('LINKEDIN_CLIENT_ID'),\n            \"CLIENT_SECRET\": os.getenv('LINKEDIN_CLIENT_SECRET'),\n            \"REDIRECT_URI\": os.getenv('LINKEDIN_REDIRECT_URI'),\n        },\n        \"FACEBOOK\": {\n            \"CLIENT_ID\": os.getenv('FACEBOOK_CLIENT_ID'),\n            \"CLIENT_SECRET\": os.getenv('FACEBOOK_CLIENT_SECRET'),\n            \"REDIRECT_URI\": os.getenv('FACEBOOK_REDIRECT_URI'),\n        }\n    },\n    \n    # don't need to add DEFAULT_TRIGGER_CLASSES \u0026 DEFAULT_NOTIFICATION_CLASS object if you want to use default classes\n    \"DEFAULT_TRIGGER_CLASSES\": {\n        \"POST_SIGNUP_TRIGGER\": '{{path.to.your.Class}}',  # replace it with your own class path\n        \"PRE_SIGNUP_TRIGGER\": '{{path.to.your.Class}}',   # replace it with your own class path\n        \"POST_LOGIN_TRIGGER\": '{{path.to.your.Class}}',   # replace it with your own class path\n    },\n    \n    \"DEFAULT_NOTIFICATION_CLASS\": \"{{path.to.your.Class}}\",   # replace it with your own class path\n    \"BLOCK_AUTH_LOGGER_CLASS\": '{{path.to.your.Class}}',   # replace it with your own class path\n}\n```\n\n### Spectacular(API documentation) Configs\n\nBlockAuth provides comprehensive Swagger/OpenAPI documentation with detailed descriptions, examples, and security information for all endpoints.\n\nAdd the following related things to the Django project's `settings.py`:\n\n```python\nINSTALLED_APPS = [\n    ...\n    'drf_spectacular',\n    'drf_spectacular_sidecar',\n    ...\n]\n\nREST_FRAMEWORK = {\n    ...\n    'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema', \n    ...\n}\n\n# read more about the settings here: https://drf-spectacular.readthedocs.io/en/latest/readme.html#installation\nSPECTACULAR_SETTINGS = {\n    'TITLE': 'Your API Title',\n    'DESCRIPTION': 'Your API description here',\n    'VERSION': '1.0.0',\n    'SERVE_INCLUDE_SCHEMA': False,\n    'SWAGGER_UI_SETTINGS': {\n        'deepLinking': True,\n    },\n}\n```\n\nAdd the following URL pattern to the Django project's `urls.py`:\n\n```python\nfrom drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView\n\nurlpatterns += [\n    # Schema generation endpoint\n    path('api/schema/', SpectacularAPIView.as_view(), name='schema'),\n    # Optional UI endpoints\n    path('api/swagger/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),\n    path('api/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),\n]\n```\n\nAfter adding the above URL pattern, you can access the swagger documentation by going to the URL `http://localhost:8000/api/swagger/` or\nthe redoc documentation by going to the URL `http://localhost:8000/api/redoc/`.\n\n### Feature Flags\n\nBlockAuth supports feature flags to enable/disable specific authentication features. This allows you to customize which endpoints are available in your application.\n\n#### Available Features\n\n- **SIGNUP**: Enable user registration with email and password\n- **BASIC_LOGIN**: Enable email/password login authentication  \n- **PASSWORDLESS_LOGIN**: Enable passwordless login with OTP\n- **WALLET_LOGIN**: Enable wallet-based authentication\n- **TOKEN_REFRESH**: Enable JWT token refresh functionality\n- **PASSWORD_RESET**: Enable password reset functionality\n- **PASSWORD_CHANGE**: Enable password change for authenticated users\n- **EMAIL_CHANGE**: Enable email change functionality\n- **EMAIL_VERIFICATION**: Enable email verification requirement\n- **WALLET_EMAIL_ADD**: Enable adding email to wallet accounts\n- **SOCIAL_AUTH**: Master switch for social authentication\n- **PASSKEY_AUTH**: Enable passkey/WebAuthn authentication (Face ID, Touch ID, Windows Hello, hardware keys)\n\n#### Example Configuration\n\nTo disable email change functionality:\n\n```python\nfrom blockauth.constants import Features\n\nBLOCK_AUTH_SETTINGS = {\n    # ... other settings ...\n    \"FEATURES\": {\n        Features.SIGNUP: True,\n        Features.BASIC_LOGIN: True,\n        Features.PASSWORDLESS_LOGIN: True,\n        Features.WALLET_LOGIN: True,\n        Features.TOKEN_REFRESH: True,\n        Features.PASSWORD_RESET: True,\n        Features.PASSWORD_CHANGE: True,\n        Features.EMAIL_CHANGE: False,  # Disable email change\n        Features.EMAIL_VERIFICATION: True,\n        Features.WALLET_EMAIL_ADD: True,\n        Features.SOCIAL_AUTH: True,\n        Features.PASSKEY_AUTH: True,  # Passkey/WebAuthn authentication\n    },\n}\n```\n\n#### Using Constants\n\nBlockAuth provides constants for better type safety and IDE support:\n\n```python\nfrom blockauth.constants import Features, SocialProviders, URLNames\nfrom blockauth.utils.feature_flags import is_feature_enabled\nfrom blockauth.utils.config import is_social_auth_configured\n\n# Check if a feature is enabled\nif is_feature_enabled(Features.EMAIL_CHANGE):\n    # Show email change form\n    pass\n\n# Use social provider constants\nif is_social_auth_configured(SocialProviders.GOOGLE):\n    # Configure Google auth\n    pass\n\n# Use URL name constants\nfrom django.urls import reverse\nsignup_url = reverse(URLNames.SIGNUP)  # Get signup URL\n```\n\n#### Advanced Usage\n\nYou can also use constants in your configuration for better maintainability:\n\n```python\nfrom blockauth.constants import Features, ConfigKeys\n\nBLOCK_AUTH_SETTINGS = {\n    ConfigKeys.FEATURES: {\n        Features.SIGNUP: True,\n        Features.EMAIL_CHANGE: False,\n    },\n    ConfigKeys.ACCESS_TOKEN_LIFETIME: timedelta(hours=1),\n    ConfigKeys.OTP_VALIDITY: timedelta(minutes=5),\n}\n```\n\n\n\n\n\n#### Feature Dependencies\n\nSome features have logical dependencies:\n- `EMAIL_CHANGE` requires `EMAIL_VERIFICATION` to be enabled\n- `PASSWORDLESS_LOGIN` requires `SIGNUP` to be enabled\n\nThe system will automatically validate these dependencies and warn about any configuration issues.\n\n#### Benefits\n\n✅ **Type Safety** - Use constants instead of string literals  \n✅ **IDE Support** - Autocomplete and error detection  \n✅ **Maintainable** - Centralized constants in one place  \n✅ **Consistent** - Uniform naming across the codebase  \n✅ **Scalable** - Easy to add new features and constants  \n✅ **Documented** - Comprehensive documentation and examples\n\n\n### Inherit Blockauth User Model\n\nInherit the `blockauth.models.BlockUser` model in your custom User model in django project. An example is shown below:\n\n```python\nfrom django.db import models\nfrom blockauth.models.user import BlockUser\n\nclass CustomUser(BlockUser):\n    first_name = models.CharField(\"First name\", max_length=50, null=True, blank=True)\n    last_name = models.CharField(\"Last name\", max_length=50, null=True, blank=True)\n    date_joined = models.DateTimeField(\"date of joining\", auto_now_add=True)\n    is_online = models.BooleanField(default=False)\n\n    # Profile Fields\n    date_of_birth = models.DateField(\"date of birth\", blank=True, null=True)\n    bio = models.TextField(\"bio\", blank=True, null=True, max_length=500)\n\n\n    class Meta:\n        db_table = \"user\"\n```\n\nSet this customer user model as the default user model in the Django project's `settings.py`:\n```python\nAUTH_USER_MODEL = 'app_name.CustomUser'\n```\n\nMake migration related commands in console to reflect database tables related to the app.\n```shell\npython manage.py makemigrations\npython manage.py migrate\n```\n\n### Add URLs\n\nAdd the package's URLs to your Django project's `urls.py`:\n\n```python\nfrom django.urls import path, include\n\nurlpatterns = [\n    ...\n    path('api/auth/', include('blockauth.urls')),\n    ...\n]\n```\nThe available URLs will be shown in swagger after adding the above URL pattern:\n\nBasic Auth:\n- `auth/signup`: Request an OTP for signup with email \u0026 password.\n- `auth/signup/otp/resend`: Resend OTP for signup with email.\n- `auth/signup/confirm`:  Confirm sign up with email and otp.\n\n\n- `auth/login/basic`: Login with **email** and **password** and get access token, refresh token.\n- `auth/login/passwordless`: Request OTP for passwordless login with email.\n- `auth/login/passwordless/confirm`: Confirm login with email and otp.\n- `auth/token/refresh`: Refresh access token.\n\n\n- `auth/password/reset`: Request OTP for password reset with email.\n- `auth/password/reset/confirm`: Confirm password reset with email, otp and new password.\n- `auth/password/change`: Change password with old password and new password while being an authenticated user\n\n\n- `auth/email/change`: Request OTP for email change with current email and current password.\n- `auth/email/change/confirm`: Confirm email change with current email, new email and otp.\n\n**Web3 Wallet Authentication:**\n- `auth/login/wallet`: Login with Ethereum wallet signature verification.\n- `auth/wallet/email/add/`: Add email address for wallet user and automatically send verification.\n- `auth/signup/confirm/`: Verify email using OTP (works for both signup and wallet email verification).\n\n**Passkey/WebAuthn Authentication:**\n- `auth/passkey/register/options/`: Get registration options for new passkey (requires auth)\n- `auth/passkey/register/verify/`: Verify passkey registration (requires auth)\n- `auth/passkey/auth/options/`: Get authentication options (public)\n- `auth/passkey/auth/verify/`: Verify passkey authentication and get JWT tokens (public)\n- `auth/passkey/credentials/`: List user's registered passkeys (requires auth)\n- `auth/passkey/credentials/\u003cuuid\u003e/`: Get, update, or delete a specific passkey (requires auth)\n\nProviders:\n- `auth/google`: Redirect URL to Google login page.\n- `auth/google/callback`: Callback URL after succesfull Google login. **This URL should be added to the Google OAuth2 client configuration**.\n\n\n- `auth/linkedin`: Redirect URL to LinkedIn login page.\n- `auth/linkedin/callback`: Callback URL to LinkedIn login page. **This URL should be added to the LinkedIn OAuth2 client configuration**.\n\n\n- `auth/facebook`: Redirect URL to Facebook login page.\n- `auth/facebook/callback`: Callback URL to Facebook login page. **This URL should be added to the Facebook OAuth2 client configuration**.\n\n## User journey of some functionalities\n\n### Sign up\n1. The user requests to `auth/signup` with email and password. It will do the following:\n   - Validate the email and password. Also checks whether the email is already registered or not.\n   - Calls the `PRE_SIGNUP_TRIGGER` class 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.)_\n   - Generates an OTP.\n   - Calls the `DEFAULT_NOTIFICATION_CLASS` class 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.)_\n   - User created with email \u0026 password and `is_verified=False` by default.\n2. The user confirms the signup by calling `auth/signup/confirm` with email and OTP. It will do the following:\n   - Validate the OTP and email.\n   - Updates the user attribute `is_verified=True`.\n   - Calls the `POST_SIGNUP_TRIGGER` class 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.)_\n3. In case if the user wants to resend the OTP, the user can call `auth/signup/otp/resend` with email.\n\n### Basic Login\nThe user can log in with email and password. After successful login, the user will get an `access token` and a `refresh token`.\nToken validity can be configured in the settings.\n\n### Passwordless Login\n1. The user requests to `auth/login/passwordless` with email. It will do the following:\n   - Validate the email.\n   - Generates an OTP.\n   - Calls the `DEFAULT_NOTIFICATION_CLASS` class with OTP information to send the OTP to the user.\n2. The user confirms the login by calling `auth/login/passwordless/confirm` with email and OTP. It will do the following:\n   - Validate the OTP and email.\n   - If the user is not found in the database, a new user is created with the email only and `is_verified=True`. Then calls the `POST_SIGNUP_TRIGGER` class with user information to perform any post-signup actions.\n   - Calls the `POST_LOGIN_TRIGGER` class 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.)_\n   - Returns an `access token` and a `refresh token`.\n\n### Token Refresh\nThe user can refresh the access token with the refresh token. The refresh token is used to generate a new access token.\nToken validity can be configured in the settings.\n\n### Password Reset\n1. The user requests to `auth/password/reset` with email. It will do the following:\n   - Validate the email.\n   - Generates an OTP.\n   - Calls the `DEFAULT_NOTIFICATION_CLASS` class with OTP information to send the OTP to the user.\n2. The user confirms the password reset by calling `auth/password/reset/confirm` with email, OTP, and new password. It will do the following:\n   - Validate the OTP, email and new password.\n   - Update the user password with the new password.\n\n\n### Change Email\n1. The user requests to `auth/email/change` with current email and password. It will do the following:\n   - Validate the current email and password.\n   - Generates an OTP.\n   - Calls the `DEFAULT_NOTIFICATION_CLASS` class with OTP information to send the OTP to the user.\n2. The user confirms the email change by calling `auth/email/change/confirm` with current email, new email, and OTP. It will do the following:\n   - Validate the current email, new email, and OTP.\n   - Update the user email with the new email.\n\n### Passkey/WebAuthn Authentication\nBlockAuth supports passwordless authentication using WebAuthn/FIDO2 standard. Users can authenticate using biometrics (Face ID, Touch ID, Windows Hello) or hardware security keys.\n\n#### Registration Flow (Authenticated User)\n1. User calls `auth/passkey/register/options/` with optional display name\n2. Backend generates registration options (challenge, RP info, user info)\n3. Frontend calls `navigator.credentials.create()` with options (triggers biometric prompt)\n4. User verifies identity with biometric/PIN\n5. Frontend sends credential response to `auth/passkey/register/verify/`\n6. Backend verifies and stores the credential\n\n#### Authentication Flow (Public)\n1. User calls `auth/passkey/auth/options/` with optional username\n2. Backend generates authentication options (challenge, allowed credentials)\n3. Frontend calls `navigator.credentials.get()` with options (triggers biometric prompt)\n4. User verifies identity with biometric/PIN\n5. Frontend sends assertion to `auth/passkey/auth/verify/`\n6. Backend verifies signature and returns JWT tokens\n\n#### Configuration\n```python\nBLOCK_AUTH_SETTINGS = {\n    \"FEATURES\": {\n        \"PASSKEY_AUTH\": True,  # Enable passkey authentication\n    },\n    \"PASSKEY_RP_ID\": \"example.com\",  # Your domain (no protocol)\n    \"PASSKEY_RP_NAME\": \"My Application\",\n    \"PASSKEY_ALLOWED_ORIGINS\": [\"https://example.com\"],\n}\n```\n\nFor detailed documentation, see: **[Passkey Developer Guide](blockauth/passkey/README.md)**\n\n### Web3 Wallet Authentication\nBlockAuth 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.\n\n#### Authentication Flow\n1. **Frontend**: User connects their wallet and signs a message (e.g., \"ABC\")\n2. **Request**: Frontend sends wallet address, message, and signature to `auth/login/wallet`\n3. **Verification**: Backend verifies the signature matches the wallet address\n4. **User Creation**: If no user exists with this wallet address, a new user is created automatically\n5. **Response**: Access token and refresh token are returned\n\n#### Request Format\n```json\nPOST /api/v1/auth/login/wallet/\n{\n  \"wallet_address\": \"0x742d35Cc...b4d8b6\",\n  \"message\": \"ABC\",\n  \"signature\": \"0x1234...abcd\"\n}\n```\n\n#### Response Format\n```json\n{\n  \"access\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...\",\n  \"refresh\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...\"\n}\n```\n\n#### Features\n- **Automatic User Creation**: New users are created automatically when first authenticating with a wallet\n- **Signature Verification**: Uses Ethereum's cryptographic signature verification\n- **No Email Required**: Users can authenticate using only their wallet address\n- **Standard JWT Tokens**: Returns the same access/refresh token format as other authentication methods\n- **Trigger Support**: Integrates with existing POST_SIGNUP_TRIGGER and POST_LOGIN_TRIGGER classes\n- **Optional Email Verification**: Users can optionally add and verify email addresses after wallet login\n- **Configurable Email Requirements**: Developers can enforce email verification for wallet users via settings\n\n#### Dependencies\nThe Web3 wallet authentication requires the following dependencies (already included in pyproject.toml):\n- `web3`: Ethereum Web3 library\n- `eth-account`: Ethereum account utilities for signature verification\n\n#### Email Verification for Wallet Users\nWallet users can optionally add and verify email addresses after authentication. This feature is controlled by the `EMAIL_VERIFICATION_REQUIRED` setting.\n\n**Configuration**\n```python\n# settings.py\nBLOCK_AUTH_SETTINGS = {\n    \"EMAIL_VERIFICATION_REQUIRED\": True,  # Enforce email verification for all users\n}\n```\n\n**Email Management Endpoints**\n- `auth/wallet/email/add/`: Add an email address to wallet user and automatically send verification\n- `auth/signup/confirm/`: Verify email using OTP (works for both signup and wallet email verification)\n\n**Request Examples**\n\nAdd Email (automatically sends verification):\n```json\nPOST /api/v1/auth/wallet/email/add/\n{\n  \"email\": \"user@example.com\",\n  \"verification_type\": \"otp\"\n}\n```\n\nAdd Email with Link Verification:\n```json\nPOST /api/v1/auth/wallet/email/add/\n{\n  \"email\": \"user@example.com\",\n  \"verification_type\": \"link\"\n}\n```\n\nVerify Email:\n```json\nPOST /api/v1/auth/signup/confirm/\n{\n  \"identifier\": \"user@example.com\",\n  \"code\": \"123456\"\n}\n```\n\n**Access Control**\nWhen `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:\n\n```python\nfrom blockauth.utils.permissions import EmailVerificationPermission\n\nclass MyProtectedView(APIView):\n    permission_classes = [IsAuthenticated, EmailVerificationPermission]\n```\n\n**Protected Endpoints**\nWhen `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:\n\n- User profile management\n- Sensitive operations\n- Payment processing\n- Data access endpoints\n- Any endpoint that requires verified user identity\n\n**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.\n\n**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.\n\n**Example: Using Email Verification Permission**\n```python\n# views.py\nfrom rest_framework.views import APIView\nfrom rest_framework.permissions import IsAuthenticated\nfrom blockauth.utils.permissions import EmailVerificationPermission\n\nclass NFTMintingView(APIView):\n    permission_classes = [IsAuthenticated, EmailVerificationPermission]\n    \n    def post(self, request):\n        # This endpoint will only be accessible to users with verified email\n        # when EMAIL_VERIFICATION_REQUIRED is True\n        return Response({\"message\": \"NFT minted successfully\"})\n```\n\n## Social Providers Login Mechanism (Google, LinkedIn, Facebook, etc.)\n\nFirst, create OAuth client configurations for the social providers (Google, LinkedIn, Facebook, etc.) and add the **client id** \nand **client secret** to the **settings**. Also set the **redirect URL** to the callback URL of the respective provider.\nUse the same **redirect URL** in the **auth providers** configuration.\n\n#### Video tutorial for creating OAuth client\n- [How to create Google OAuth client](https://www.youtube.com/watch?v=OKMgyF5ezFs\u0026ab_channel=LearnWithDexter)\n- [How to create Facebook OAuth client](https://youtu.be/LLlpH3vZVkg?t=133)\n- [How to create LinkedIn OAuth client](https://www.youtube.com/watch?v=aV8d09e8nnA\u0026ab_channel=LearnwithNAK)\n\n#### Login Flow\n1. Call the URL `auth/{provider_name}` to redirect to the respective provider login page.\n2. The user will provide the credentials on the provider's login page \u0026 authorize the app.\n3. Upon successful login, the user will be redirected to the `REDIRECT_URI` with the code. \nHere, `REDIRECT_URI` should redirect to the developer's frontend app.\n4. The frontend app will call the backend API `auth/{provider_name}/callback?code={code}` with the code in query params.\n\n#### What happens inside `auth/{provider_name}/callback` API?\n1. By following the Oauth2 flow, user data (email, name, etc.) is fetched from the provider.\n2. The user is then searched in the database via **email** field provided by the social provider. \nIf the user is not found in the db, a new user is created with the `email`, `first_name` and `is_verified=True`.\nThen calls the `POST_SIGNUP_TRIGGER` class with **provider name, user data from backend \u0026 provider** to perform any post-signup actions.\n3. Calls the `POST_LOGIN_TRIGGER` class with **provider name, user data from backend \u0026 provider** to perform any post-login actions.\n4. Finally, **access token and refresh token** generated with **user id** is returned.\n\n## Utility Classes\n### Communication Class\nThis class is used to send messages to the user. The default class is `blockauth.notification.DummyNotification`, \nwhich is a dummy class that prints the message to the console. \n\nDevelopers should implement their own class by inheriting `blockauth.notification.BaseNotification` and set the path in settings via `DEFAULT_NOTIFICATION_CLASS`.\nOtherwise, the default class will be used.\n\nCurrently, the communication class is integrated in the following APIs:\n- `auth/signup`: To send OTP/link for signup (automatic).\n- `auth/wallet/email/add/`: To send OTP/link for wallet email verification (automatic).\n- `auth/signup/otp/resend`: To resend OTP for signup (legacy endpoint).\n- `auth/login/passwordless`: To send OTP for passwordless login.\n- `auth/password/reset`: To send OTP for password reset.\n- `auth/password/change`: To send password change notification.\n- `auth/email/change`: To send OTP for email change.\n\n**Usage example**\n\n```python\nfrom blockauth.notification import BaseNotification\n\n\nclass CustomNotification(BaseNotification):\n    def notify(self, method: str, event: str, context: dict) -\u003e None:\n        \"\"\"\n       :param method: delivery method ('email', 'sms')\n       :param event: notification event (see NotificationEvent constants)\n       :param context: contains identifier and any extra data\n       \"\"\"\n        if event == 'otp_request':\n            self.send_otp(context)\n        elif event == 'password_change':\n            self.send_password_change_email(context)\n\n    def send_otp(self, context: dict) -\u003e None:\n        email = context.get('email')\n        otp = context.get('otp')\n        print(f\"Sending OTP {otp} to email {email}\")\n\n    def send_password_change_email(self, context: dict) -\u003e None:\n        email = context.get('email')\n        print(f\"Sending password change notification to email {email}\")\n```\n\nThe following notification events are available (see `blockauth.notification.NotificationEvent`):\n```python\nclass NotificationEvent:\n    OTP_REQUEST = \"otp_request\"\n    SUCCESS_PASSWORD_RESET = \"success_password_reset\"\n    SUCCESS_PASSWORD_CHANGE = \"success_password_change\"\n    SUCCESS_EMAIL_CHANGE = \"success_email_change\"\n```\n\n### Trigger Classes\nThese classes are used to perform some actions before and after the signup and login process.\n- `PRE_SIGNUP_TRIGGER`: Called before signup. Default: `blockauth.triggers.DummyPreSignupTrigger`\n- `POST_SIGNUP_TRIGGER`: Called after signup. Default: `blockauth.triggers.DummyPostSignupTrigger`\n- `POST_LOGIN_TRIGGER`: Called after login. Default: `blockauth.triggers.DummyPostLoginTrigger`\n- `POST_PASSWORD_CHANGE_TRIGGER`: Called after password change. Default: `blockauth.triggers.DummyPostPasswordChangeTrigger`\n- `POST_PASSWORD_RESET_TRIGGER`: Called after password reset. Default: `blockauth.triggers.DummyPostPasswordResetTrigger`\n\nDevelopers have to implement their own classes by inheriting the respective base classes and set the path in the settings.\n\n**Usage example**\n```python\nfrom blockauth.triggers import BaseTrigger\n\nclass CustomPreSignupTrigger(BaseTrigger):\n    def trigger(self, context: dict) -\u003e None:\n        # Custom logic before signup\n        print(f\"Custom pre-signup logic with context: {context}\")\n\nclass CustomPostSignupTrigger(BaseTrigger):\n    def trigger(self, context: dict) -\u003e None:\n        # Custom logic after signup\n        print(f\"Custom post-signup logic with context: {context}\")\n\nclass CustomPostLoginTrigger(BaseTrigger):\n    def trigger(self, context: dict) -\u003e None:\n        # Custom logic after login\n        print(f\"Custom post-login logic with context: {context}\")\n```\n\n## Logging in BlocAuth\n\nBlocAuth provides a unified logging interface for all authentication-related events. This logger supports multiple log levels, each with a unique icon for easy identification.\n\n### Supported Log Levels and Icons\n\n| Level      | Icon  | Description                                                        |\n|------------|-------|--------------------------------------------------------------------|\n| debug      | 🐞    | Detailed information for debugging                                 |\n| info       | ℹ️    | General information about application events                       |\n| warning    | ⚠️    | Unusual or unexpected events, not necessarily errors               |\n| error      | ❌    | Errors that prevent normal execution                               |\n| critical   | 🔥    | Very serious errors requiring immediate attention                  |\n| exception  | 💥    | Exceptions, typically with stack traces                            |\n| trace      | 🔍    | Fine-grained tracing information                                   |\n| notice     | 📢    | Important but normal events requiring special attention            |\n| alert      | 🚨    | Events requiring immediate action, not yet critical                |\n| fatal      | ☠️    | Fatal errors leading to shutdown or unrecoverable failure          |\n| success    | ✅    | Successful completion of an operation                              |\n| pending    | ⏳    | Operations in progress or waiting for completion                   |\n| failure    | 💔    | Failed operations or processes                                     |\n\n### Custom Logger Integration\n\nTo 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`.\n\n#### Example: Custom Logger Class\n\n```python\n# myapp/logging.py\nclass MyBlockAuthLogger:\n    def log(self, message, data=None, level=\"info\", icon=None):\n        # You can integrate with Python's logging, send to a service, or print\n        print(f\"{icon} [{level.upper()}] {message} | {data}\")\n```\n\n#### Django Settings Configuration\n\n```python\n# settings.py\nBLOCK_AUTH_SETTINGS = {\n    \"BLOCK_AUTH_LOGGER_CLASS\": \"myapp.logging.MyBlockAuthLogger\",\n    # ... other BlocAuth settings ...\n}\n```\n\n- The logger class must implement a `log(message, data, level, icon)` method.\n- The `icon` argument is a unicode symbol representing the log level.\n- If `BLOCK_AUTH_LOGGER_CLASS` is not set in `BLOCK_AUTH_SETTINGS`, logging calls will be no-ops.\n\n\n### Log Context Sanitization\n\nTo protect sensitive user data, BlocAuth automatically removes sensitive fields (such as passwords, tokens, codes, etc.) from all log data before writing to logs.\n\nBy default, the following fields are removed: `password`, `new_password`, `refresh`, `access`, `token`, `code`. This list can be extended by maintainers if needed.\n\nAll BlocAuth logging calls use this utility to ensure no sensitive information is ever logged.\n\n## Permission Classes\n\nBlockAuth provides permission classes to control access to endpoints based on email verification status.\n\n### EmailVerificationPermission\n\nA 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.\n\n**Configuration:**\n```python\n# settings.py\nBLOCK_AUTH_SETTINGS = {\n    \"EMAIL_VERIFICATION_REQUIRED\": True,  # Enable email verification requirement\n    # ... other settings\n}\n```\n\n**Usage:**\n```python\nfrom rest_framework.views import APIView\nfrom rest_framework.permissions import IsAuthenticated\nfrom blockauth.utils.permissions import EmailVerificationPermission\n\nclass ProtectedView(APIView):\n    permission_classes = [IsAuthenticated, EmailVerificationPermission]\n    \n    def get(self, request):\n        # This endpoint will only be accessible to users with verified email\n        # when EMAIL_VERIFICATION_REQUIRED is True\n        return Response({\"message\": \"Access granted\"})\n```\n\n**What it checks:**\n1. If email verification is required (configurable via `EMAIL_VERIFICATION_REQUIRED`)\n2. If the user has an email address\n3. If the user's email is verified (`is_verified=True`)\n\n**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.\n\n**Error Messages:**\n- \"Email address required. Please add an email address to access this endpoint.\" - When user has no email\n- \"Email verification required. Please verify your email address to access this endpoint.\" - When email is not verified\n\n---\n\n## 🔐 KDF Usage Examples\n\n### Basic KDF Wallet Creation\n\n```python\nfrom blockauth.kdf import get_kdf_manager\n\n# Get KDF manager\nkdf_manager = get_kdf_manager()\n\n# Create wallet for email user\nwallet_data = kdf_manager.create_wallet(\n    email=\"user@example.com\",\n    password=EXAMPLE_PASSWORD,\n    wallet_name=\"primary\"\n)\n\nprint(f\"Wallet Address: {wallet_data['wallet_address']}\")\nprint(f\"Wallet ID: {wallet_data['wallet_id']}\")\n```\n\n### Passwordless Wallet Creation\n\n```python\n# Create wallet for passwordless user\nwallet_data = kdf_manager.create_wallet(\n    email=\"user@example.com\",\n    wallet_name=\"primary\",\n    auth_method='passwordless'  # No password required\n)\n\nprint(f\"Passwordless Wallet: {wallet_data['wallet_address']}\")\n```\n\n### Multiple Wallets per User\n\n```python\n# Create multiple wallets with different names\nwallets = kdf_manager.create_multiple_wallets(\n    email=\"user@example.com\",\n    password=EXAMPLE_PASSWORD,\n    wallet_names=[\"primary\", \"trading\", \"savings\"]\n)\n\nfor wallet in wallets:\n    print(f\"{wallet['wallet_name']}: {wallet['wallet_address']}\")\n```\n\n### Password Change Integration\n\nPassword change triggers fire automatically from the `PasswordChangeView`. To handle password changes in your app, implement a custom trigger:\n\n```python\nfrom blockauth.triggers import BaseTrigger\n\nclass MyPasswordChangeTrigger(BaseTrigger):\n    def trigger(self, context: dict) -\u003e None:\n        # context contains: user_id, username, email, trigger_type, timestamp\n        # NOTE: plaintext passwords are never included in the context\n        user_id = context['user_id']\n        # Perform post-password-change actions (e.g., invalidate sessions)\n```\n\n### KDF Configuration\n\n```python\n# In your Django settings\nBLOCK_AUTH_SETTINGS = {\n    \"KDF_ENABLED\": True,\n    \"KDF_ALGORITHM\": \"pbkdf2_sha256\",  # or \"argon2id\"\n    \"KDF_ITERATIONS\": 100000,           # Production: 100k+, Dev: 1k\n    \"KDF_SECURITY_LEVEL\": \"HIGH\",       # LOW, MEDIUM, HIGH, CRITICAL\n    \"KDF_MASTER_SALT\": \"your-32-char-minimum-salt\",\n    \"MASTER_ENCRYPTION_KEY\": \"0x\" + \"64-char-hex-key\",\n    \"PLATFORM_MASTER_SALT\": \"your-platform-salt-32-chars-minimum\",\n}\n```\n\n---\n\n## Custom JWT Claims\n\nBlockAuth 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.\n\n### Quick Start\n\n1. **Create a claims provider class** with a `get_custom_claims` method\n2. **Register the provider** with the JWT manager\n3. **Custom claims are automatically included** in all generated tokens\n\n### Example\n\n```python\n# myapp/jwt_claims.py\nclass MyClaimsProvider:\n    def get_custom_claims(self, user):\n        return {\n            \"role\": user.role,\n            \"organization_id\": str(user.organization_id),\n            \"permissions\": list(user.permissions.all())\n        }\n\n# Register in Django app's ready() method\nfrom blockauth.jwt.token_manager import jwt_manager\njwt_manager.register_claims_provider(MyClaimsProvider())\n```\n\n### Documentation\n\nFor detailed documentation on creating and managing custom JWT claims providers, including best practices, advanced usage, and troubleshooting, see:\n\n📚 **[Custom JWT Claims Guide](docs/CUSTOM_JWT_CLAIMS.md)**\n\n---\n\n## Rate Limiting\nRate limiting is implemented for requests currently. The rate limit is based on the number of requests and the duration.\nThe rate limit can be configured in the settings.\n\n---\n\n## Observability / Metrics\n\nBlockAuth emits observability events from the wallet-login flow via a\nsingle callback hook — it does **not** take a dependency on any\nparticular metrics backend. Consumers wire the events into Prometheus,\nStatsD, OpenTelemetry, structured logs, or nothing at all.\n\nSet `BLOCK_AUTH_SETTINGS[\"METRICS_CALLBACK\"]` to the dotted path of a\ncallable with signature:\n\n```python\ndef emit(\n    event: str,\n    tags: dict | None = None,\n    *,\n    duration_s: float | None = None,\n    count: int = 1,\n) -\u003e None: ...\n```\n\nIf the setting is absent, events are dropped. Any exception raised by\nthe callback is caught and logged — a broken metrics pipe will never\nfail an auth request.\n\n### Events\n\n| Event | When | Tags | Extra |\n|---|---|---|---|\n| `wallet_login.challenge_issued` | `POST /login/wallet/challenge/` success | — | — |\n| `wallet_login.success` | `POST /login/wallet/` success | `flow` (`\"siwe\"`) | — |\n| `wallet_login.failure` | `POST /login/wallet/` rejection | `code` (e.g. `nonce_invalid`, `domain_mismatch`, `uri_host_mismatch`, `signature_mismatch`, `validation_error`, ...) | — |\n| `wallet_login.latency` | Every `POST /login/wallet/` (both outcomes) | `outcome` (`\"success\"` / `\"failure\"`) | `duration_s` |\n| `wallet_nonce.pruned` | Per batch in `prune_wallet_nonces` | — | `count` = rows deleted |\n\nEvent names are a stable public contract; adding new events is\nnon-breaking, renaming existing ones is not.\n\n### Example: Prometheus\n\n```python\n# myapp/observability.py\nfrom prometheus_client import Counter, Histogram\n\n_CHAL = Counter(\"wallet_login_challenge_issued_total\", \"...\")\n_SUCCESS = Counter(\"wallet_login_success_total\", \"...\", [\"flow\"])\n_FAIL = Counter(\"wallet_login_failure_total\", \"...\", [\"code\"])\n_LAT = Histogram(\"wallet_login_latency_seconds\", \"...\", [\"outcome\"])\n_PRUNED = Counter(\"wallet_nonce_pruned_total\", \"...\")\n\n\ndef emit(event, tags=None, *, duration_s=None, count=1):\n    tags = tags or {}\n    if event == \"wallet_login.challenge_issued\":\n        _CHAL.inc(count)\n    elif event == \"wallet_login.success\":\n        _SUCCESS.labels(**tags).inc(count)\n    elif event == \"wallet_login.failure\":\n        _FAIL.labels(**tags).inc(count)\n    elif event == \"wallet_login.latency\":\n        _LAT.labels(**tags).observe(duration_s or 0.0)\n    elif event == \"wallet_nonce.pruned\":\n        _PRUNED.inc(count)\n```\n\n```python\n# settings.py\nBLOCK_AUTH_SETTINGS = {\n    # ...\n    \"METRICS_CALLBACK\": \"myapp.observability.emit\",\n}\n```\n\nThe host service owns the `/metrics` exposition endpoint (via\n`django-prometheus`, `prometheus_client.make_wsgi_app`, or a custom\nmiddleware). The default Prometheus registry is process-wide, so any\ncounter the callback increments is scraped automatically.\n\n### Example: StatsD / OTel / logs\n\nThe same callback works for any backend — translate the event in the\nconsumer. For structured logging:\n\n```python\nimport logging\n\nlog = logging.getLogger(\"blockauth.metrics\")\n\ndef emit(event, tags=None, *, duration_s=None, count=1):\n    log.info(event, extra={\"tags\": tags or {}, \"duration_s\": duration_s, \"count\": count})\n```\n\n### Scheduling `prune_wallet_nonces`\n\nThe `wallet_nonce.pruned` counter is only useful if the reaper actually\nruns. BlockAuth does **not** ship a scheduler — the consumer decides.\nRecommended cadence: every 5–15 minutes. Example invocations:\n\n```bash\n# cron / systemd timer\n*/10 * * * *  python manage.py prune_wallet_nonces\n\n# Celery Beat\nfrom celery import shared_task\nfrom django.core.management import call_command\n\n@shared_task\ndef prune_wallet_nonces():\n    call_command(\"prune_wallet_nonces\")\n```\n\n```yaml\n# Kubernetes CronJob (excerpt)\napiVersion: batch/v1\nkind: CronJob\nspec:\n  schedule: \"*/10 * * * *\"\n  jobTemplate:\n    spec:\n      template:\n        spec:\n          containers:\n          - name: prune\n            image: my-auth-service:latest\n            command: [\"python\", \"manage.py\", \"prune_wallet_nonces\"]\n          restartPolicy: OnFailure\n```\n\n```hcl\n# ECS scheduled task (Terraform sketch)\nresource \"aws_cloudwatch_event_rule\" \"prune_nonces\" {\n  schedule_expression = \"rate(10 minutes)\"\n}\n# Target: RunTask on the auth-service task-definition with command\n# override [\"python\", \"manage.py\", \"prune_wallet_nonces\"]\n```\n\nDefault behaviour deletes every expired row plus every consumed row\nolder than 1 hour. `--older-than` (seconds), `--batch-size`, and\n`--dry-run` flags are available; see `python manage.py\nprune_wallet_nonces --help`. Failure mode if the command is never run:\nunbounded nonce-table growth and replay-window memory pressure.\n\n---\n\n## 🧪 KDF Development \u0026 Testing\n\n### Development Setup\n\n1. **Install KDF Dependencies**\n```bash\nuv add cryptography web3 eth-account argon2-cffi\n```\n\n2. **Configure KDF Settings**\n```python\n# In your Django settings\nBLOCK_AUTH_SETTINGS = {\n    \"KDF_ENABLED\": True,\n    \"KDF_ALGORITHM\": \"pbkdf2_sha256\",\n    \"KDF_ITERATIONS\": 1000,  # Lower for development\n    \"KDF_SECURITY_LEVEL\": \"LOW\",\n    \"KDF_MASTER_SALT\": \"dev-salt-32-chars-minimum-for-development\",\n    \"MASTER_ENCRYPTION_KEY\": \"0x\" + \"a\" * 64,  # Development key\n    \"PLATFORM_MASTER_SALT\": \"dev-platform-salt-32-chars-minimum\",\n}\n```\n\n### Testing KDF Functionality\n\n```python\n# Test KDF wallet creation\nfrom blockauth.kdf import get_kdf_manager\n\nkdf_manager = get_kdf_manager()\n\n# Test with email/password\nwallet = kdf_manager.create_wallet(\n    email=\"test@example.com\",\n    password=EXAMPLE_PASSWORD\n)\n\nassert wallet['wallet_address'].startswith('0x')\nassert len(wallet['wallet_address']) == 42\n```\n\n### Production Deployment\n\n1. **Generate Secure Keys**\n```bash\n# Generate master encryption key\nopenssl rand -hex 32\n\n# Generate platform master salt (minimum 32 characters)\nopenssl rand -base64 32\n```\n\n2. **Update Production Settings**\n```python\nBLOCK_AUTH_SETTINGS = {\n    \"KDF_ENABLED\": True,\n    \"KDF_ALGORITHM\": \"pbkdf2_sha256\",\n    \"KDF_ITERATIONS\": 100000,  # Production iterations\n    \"KDF_SECURITY_LEVEL\": \"HIGH\",\n    \"KDF_MASTER_SALT\": \"your-production-salt-32-chars-minimum\",\n    \"MASTER_ENCRYPTION_KEY\": \"0x\" + \"your-64-char-hex-key\",\n    \"PLATFORM_MASTER_SALT\": \"your-platform-salt-32-chars-minimum\",\n}\n```\n\n3. **Monitor Performance**\n- Wallet creation success rate\n- Average creation time (\u003c 3 seconds)\n- Memory usage per operation (\u003c 100MB)\n- Database query performance\n\n---\n\n## Folder Structure\n\n```\nblockauth/\n├── __init__.py\n├── apps.py\n├── authentication.py\n├── conf.py\n├── migrations/\n│   ├── __init__.py\n│   └── 0001_initial.py\n├── models/\n│   ├── __init__.py\n│   ├── otp.py\n│   └── user.py\n├── notification.py\n├── docs/\n│   ├── __init__.py\n│   ├── auth_docs.py\n│   ├── wallet_docs.py\n│   └── social_auth.py\n├── schemas/\n│   ├── __init__.py\n│   ├── account_settings.py\n│   ├── examples/\n│   │   ├── __init__.py\n│   │   ├── account_settings.py\n│   │   ├── common.py\n│   │   ├── login.py\n│   │   ├── password_reset.py\n│   │   ├── signup.py\n│   │   └── social_auth.py\n│   ├── factory.py\n│   ├── login.py\n│   ├── password_reset.py\n│   ├── signup.py\n│   └── social_auth.py\n├── serializers/\n│   ├── __init__.py\n│   ├── otp_serializers.py\n│   ├── user_account_serializers.py\n│   └── wallet_serializers.py\n├── triggers.py\n├── urls.py\n├── utils/\n│   ├── __init__.py\n│   ├── config.py\n│   ├── custom_exception.py\n│   ├── generics.py\n│   ├── logger.py\n│   ├── permissions.py\n│   ├── rate_limiter.py\n│   ├── social.py\n│   ├── token.py\n│   ├── validators.py\n│   └── web3/\n│       └── wallet.py\n├── kdf/                           # 🔐 KDF System Module\n│   ├── __init__.py\n│   ├── services.py                # Core KDF services\n│   ├── encryption.py              # AES-256-GCM encryption\n│   ├── algorithms/                # KDF algorithms\n│   │   ├── __init__.py\n│   │   ├── pbkdf2.py             # PBKDF2 implementation\n│   │   └── argon2.py              # Argon2 implementation\n│   └── utils.py                   # KDF utilities\n├── passkey/                       # 🔐 Passkey/WebAuthn Module\n│   ├── __init__.py\n│   ├── views.py                   # API views\n│   ├── models.py                  # PasskeyCredential, PasskeyChallenge\n│   ├── config.py                  # Configuration manager\n│   ├── constants.py               # Constants and defaults\n│   ├── exceptions.py              # Custom exceptions\n│   ├── services/                  # Business logic\n│   │   ├── passkey_service.py    # WebAuthn operations\n│   │   └── challenge_service.py  # Challenge management\n│   ├── storage/                   # Credential storage\n│   │   ├── base.py               # Abstract interface\n│   │   └── django_storage.py     # Django ORM implementation\n│   └── README.md                  # Developer guide\n├── triggers/                      # 🔄 Password Management Triggers\n│   ├── __init__.py\n│   ├── password_triggers.py       # Password change/reset triggers\n│   └── dummy_triggers.py          # Placeholder triggers\n└── views/\n    ├── __init__.py\n    ├── basic_auth_views.py\n    ├── facebook_auth_views.py\n    ├── google_auth_views.py\n    ├── linkedin_auth_views.py\n    └── wallet_auth_views.py\n```\n\n## License\nMIT License. See [LICENSE](LICENSE) for details.\n\n## Acknowledgments\n- [Django](https://www.djangoproject.com/)\n- [Django REST framework](https://www.django-rest-framework.org/)\n- [PyJWT](https://pyjwt.readthedocs.io/en/stable/)\n- [drf-yasg](https://drf-yasg.readthedocs.io/en/stable/)\n- [Google OAuth2](https://developers.google.com/identity/protocols/oauth2)\n- [LinkedIn OAuth2](https://learn.microsoft.com/en-us/linkedin/shared/authentication/)\n- [Facebook OAuth2](https://developers.facebook.com/docs/facebook-login/)\n- [Cryptography](https://cryptography.io/) - For AES-256-GCM encryption\n- [Web3.py](https://web3py.readthedocs.io/) - For Ethereum integration\n- [Argon2](https://github.com/P-H-C/phc-winner-argon2) - For advanced KDF algorithms\n\n## Contributors\n\n- [Pramod Kodag](https://github.com/PramodTKodag)\n- [Cris R](https://github.com/Madgeniusblink)\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbloclabshq%2Fauth-pack","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbloclabshq%2Fauth-pack","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbloclabshq%2Fauth-pack/lists"}