payton / django-siwe-auth

A complete Django authentication system overhall for Web3 utilizing Sign-In with Ethereum.
MIT License
26 stars 6 forks source link
auth dapp django ethereum siwe web3

Contributors Forks Stargazers Issues MIT License GitPOAP Badge


Logo

Django Sign-In with Ethereum Auth

A complete Django authentication system overhaul for Web3. Treat Ethereum wallets as first-class citizens with authentication via Sign-In with Ethereum (EIP-4361) and authorization via on-chain attributes such as NFT (ERC721/ERC1155) and ERC20 token ownership.

DISCLAIMER: django-siwe-auth is still in early development (it will change often!)

Explore the docs »

Live Demo · Report Bug · Request Feature

Table of Contents
  1. About The Project
  2. Getting Started
  3. Usage
  4. Scenarios
  5. Contributing
  6. License
  7. Contact
  8. Acknowledgments
  9. Disclaimer

About The Project

This app provides four key features for new or existing Django projects.

  1. Authentication Backend
    • Authenticate incoming requests via the Sign-In with Ethereum (EIP-4361) standard.
  2. User Model
    • Replace original user model with wallet model that is dependent on an Ethereum address.
    • Removes the liability of storing user credentials in favor of managing transient session data (handled by Django out of the box).
    • ENS profile information is pulled by default.
  3. Groups
    • Create custom user groups based on on-chain (or off-chain!) attributes.
    • For example, we can easily define a group of BAYC owners by validating NFT ownership and subsequently serve them special content that non-owners don't have access to.
    • This extension builds off of Django's well-defined authorization system.

Below is the included example application that authenticates with an Ethereum address and utilizes on-chain attributes to authorize access to various notepads.

Example Project

(back to top)

Getting Started

With the included example applications, you can test out Sign-In with Ethereum along with using and creating custom groups. To get an example application up and running follow these steps.

Prerequisites

Requirements for developing and running examples apps:

Demo Application

  1. Install NPM dependencies
    npm --prefix examples/notepad/frontend install examples/notepad/frontend
  2. Build frontend
    npm run --prefix examples/notepad/frontend build
  3. Install Python dependencies
    poetry install
  4. Set Web3 provider environment variable
    export SIWE_AUTH_PROVIDER="https://mainnet.infura.io/v3/..." # Any provider works
  5. Django migrations
    # Create Django migrations
    poetry run examples/notepad/manage.py makemigrations
    # Apply Django migrations
    poetry run examples/notepad/manage.py migrate
  6. Run server
    poetry run examples/notepad/manage.py runserver
  7. Test application at https://localhost:8000

(back to top)

Rebuild Demo JavaScript

If you make an update to the frontend directory or siwe submodule, rebuild bundle.js:

cd examples/notepad/frontend \
npm install \
npm run build

Usage

This project is highly configurable and modular allowing for applications to be as simple or complex as required. Authentication and authorization can be completely replaced or supplemented depending upon the use case. See scenario docs for detailed tutorials.

Useful Resources

Install

Prerequisites

Requirements for using django-siwe-auth in a Django application:

  1. Install package
    pip install django-siwe-auth
  2. Add siwe_auth.apps.SiweAuthConfig to INSTALLED_APPS in your project's settings.py
    INSTALLED_APPS = [
    ...
    "siwe_auth.apps.SiweAuthConfig", # Adds django-siwe-auth
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    ]
  3. Add app-specific definitions to your project's settings.py (same files as previous step). For more information, see the Configuring Your Project section
    from siwe_auth.custom_groups.erc721 import ERC721OwnerManager
    ...
    # Django Sign-In with Ethereum Auth Settings
    AUTH_USER_MODEL = "siwe_auth.Wallet"
    AUTHENTICATION_BACKENDS = ["siwe_auth.backend.SiweBackend"]
    LOGIN_URL = "/"
    SESSION_COOKIE_AGE = 3 * 60 * 60 
    CREATE_GROUPS_ON_AUTHN = False # defaults to False
    CREATE_ENS_PROFILE_ON_AUTHN = True # defaults to True
    CUSTOM_GROUPS = [
       ('ens_owners', ERC721OwnerManager(config={'contract': '0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85'})),
       ('bayc_owners', ERC721OwnerManager(config={'contract': '0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D'})),
       ('shib_owners', ERC20OwnerManager(config={'contract': '0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce'})),
    ]  # See "Group Plugins" section
    PROVIDER = os.environ.get("SIWE_AUTH_PROVIDER", "https://mainnet.infura.io/v3/...")
    ...
  4. Add api/auth/ endpoint to your project's urls.py
    urlpatterns = [
    path("admin/", admin.site.urls),
    path("api/auth/", include("siwe_auth.urls")),
    ...
    ]
  5. Run manage.py makemigrations and manage.py migrate to create custom user model
  6. Update frontend to interface with the REST API as defined below. An example frontend is available in the notepad demo.
  7. Never handle a password ever again!

For more examples, please refer to the Documentation

(back to top)

REST API

Projects that use this app will interact via these endpoints for authentication. Tools for authorization are provided but not enforced via any endpoints.

Login

/api/auth/login * **Method:** `POST` * **URL Params** None * **Data Params** **Required:** `{"message": **SIWE message dict or formatted string**, "signature": **user signature of prior message**}` * **Success Response:** * **Code:** 200
**Content:** `{"success": True, "message": "Successful login."}` * **Error Response:** * **Code:** 401 UNAUTHORIZED
**Content:** `{"success": False, "message": "Wallet disabled."}` OR * **Code:** 403 FORBIDDEN
**Content:** `{"success": False, "message": "Invalid login."}` OR * **Code:** 429 TOO MANY REQUESTS
**Content:** `{"message": "Too many requests. Slow down."}` * **Sample Call:** ```javascript const res = await fetch(`/api/auth/login`, { method: "POST", headers: { 'Content-Type': 'application/json', 'X-CSRFToken': document.getElementsByName('csrfmiddlewaretoken')[0].value, }, body: JSON.stringify({ message, signature }), credentials: 'include' }); ```

Logout

/api/auth/logout * **Method:** `POST` * **URL Params** None * **Data Params** None * **Success Response:** * **Code:** 302
**Redirect:** `/` * **Error Response:** * **Code:** 429 TOO MANY REQUESTS
**Content:** `{"message": "Too many requests. Slow down."}` * **Sample Call:** ```javascript const res = await fetch(`/api/auth/logout`, { method: "POST", headers: { 'Content-Type': 'application/json', 'X-CSRFToken': document.getElementsByName('csrfmiddlewaretoken')[0].value, }, credentials: 'include' }); ```

Nonce

/api/auth/nonce * **Method:** `GET` * **URL Params** None * **Data Params** None * **Success Response:** * **Code:** 200
**Content:** `{"nonce": **one-time use nonce**}` * **Error Response:** * **Code:** 429 TOO MANY REQUESTS
**Content:** `{"message": "Too many requests. Slow down."}` * **Sample Call:** ```javascript const res = await fetch(`/api/auth/nonce`, { credentials: 'include', headers: { 'X-CSRFToken': document.getElementsByName('csrfmiddlewaretoken')[0].value, }, }); ```

(back to top)

Configuring Your Project

Relevant native Django settings

# in settings.py
AUTH_USER_MODEL = "siwe_auth.Wallet" # required for siwe as the default authentication
AUTHENTICATION_BACKENDS = ["siwe_auth.backend.SiweBackend"] # required for siwe as the default authentication
LOGIN_URL = "/" # optional, django's default is "/accounts/login/"
SESSION_COOKIE_AGE = 3 * 60 * 60 # Age of cookie, in seconds. Optional, django's default is weeks.

django-siwe-auth specific settings

# in settings.py
CREATE_GROUPS_ON_AUTHN = True # optional, default is False
CREATE_ENS_PROFILE_ON_AUTHN = True # optional, default is True
CUSTOM_GROUPS = [] # optional, see "Adding a Group" below
# Set environment variable SIWE_AUTH_PROVIDER for Web3.py
# - Required if CREATE_GROUPS_ON_AUTHN or CREATE_ENS_PROFILE_ON_AUTHN are True. Optional otherwise.
#   Any ethereum API key (infura or alchemy) will work.
PROVIDER = os.environ.get("SIWE_AUTH_PROVIDER", "https://mainnet.infura.io/v3/...")

Override Admin Login Page

In order to login to the django admin page with siwe-auth, we need to override the login template.

# in your app's admin.py, add the following line
admin.site.login_template = 'siwe_auth/login.html'

Group Plugins

A group plugin allows you to define your own group whose membership is defined by the output of a membership function. Some examples of what the membership function may check for include but are not limited to: NFT ownership, ERC-20 token ownership, ENS data, etc.

GroupManager Class

All group plugins must implement the GroupManager class. This includes a function called is_member that is called to determine group membership when a user authenticates with the server.

from abc import ABC, abstractmethod

class GroupManager(ABC):

   @abstractmethod
   def __init__(self, config: dict):
      pass

   @abstractmethod
    def is_member(self, wallet: object) -> bool:
        pass

Included Managers

Adding a Group

A custom group is defined by a tuple consisting of a name (string), MemberManager implementation.

The GroupManager's config can be used for anything, but some likely usecases are defining contract addresses or address include/exclude lists.

In your project's settings.py, append your group to the CUSTOM_GROUPS list added in the installation steps.

Suppose we want to have only one group for BAYC (ERC721 NFT) owners and another group for LPT (ERC20 Token) owners. Our list would then look like:

from siwe_auth.custom_groups.erc721 import ERC721OwnerManager
from siwe_auth.custom_groups.erc20 import ERC20OwnerManager
...
CUSTOM_GROUPS=[
   ('bayc_owners', ERC721OwnerManager(config={'contract': '0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d'})),
   ('lpt_owners', ERC20OwnerManager(config={'contract': '0x58b6a8a3302369daec383334672404ee733ab239'})),
]

Scenarios (WIP)

Examples:

  1. Start a new web app with wallet user model.
  2. Replace existing user model with wallet user model (non-trivial).
  3. Replace existing user model with weaker wallet user model that implements base model (less non-trivial but still non-trivial).
  4. Creating permissions around on-chain based groups.

Contributing

Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.

If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". Don't forget to give the project a star! Thanks again!

  1. Fork the Project
  2. Create your Feature Branch (git checkout -b feature/AmazingFeature)
  3. Commit your Changes (git commit -m 'Add some AmazingFeature')
  4. Push to the Branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

(back to top)

License

Distributed under the MIT License. See LICENSE.txt for more information.

(back to top)

Contact

payton - @paytongarland - paytongarland.eth

Project Link: https://github.com/payton/django-siwe-auth

(back to top)

Acknowledgments

(back to top)

Disclaimer

This django auth library has not yet undergone a formal security audit. We welcome continued feedback on the usability, architecture, and security of this implementation.

(back to top)