Авторизация в Django при помощи Metamask

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Сегодня мы напишем легкий сниппет для авторизации пользователей на сайте при помощи кошелька MetaMask.

Сниппет будет состоять из двух частей - django template и django view.


Начнем с django template.

Допустим у нас есть шаблон для авторизации templates/login.html

Создадим кнопку авторизации при нажатии на которую будет вызван MetaMask.

<button type="button" onclick="web3Login()">
    Log in with Metamask
</button>

Создадим обслуживающий скрипт.

<script src="https://cdn.ethers.io/lib/ethers-5.2.umd.min.js" type="application/javascript"></script>
<script>
function web3Login() {
	  /* Подписываем сообщение */
    try {
        window.ethereum.enable().then(function () {
            provider = new ethers.providers.Web3Provider(window.ethereum);
            provider.getNetwork().then(function (result) {
                if (result['chainId'] != 1) {
                    alert('Switch to Mainnet!');
                } else { 
                    provider.listAccounts().then(function (result) {
                        accountAddress = result[0]; 
                        signer = provider.getSigner();
                        signer.signMessage("Sign to auth {{ csrf_token }}").then((signature) => {web3LoginBackend(accountAddress, signature)});
                    })
                }
            })
        })
    } catch {
        alert('Please install MetaMask for your browser.')
    }
}

function web3LoginBackend(accountAddress, signature) {
		/* Отправляем сообщение на функцию авторизации/регистрации */
    var form = document.createElement('form');
    form.action = '{% url "main:auth_web3" %}'; // TODO замените на свой адрес
    form.method = 'POST';
 
    var input = document.createElement('input');
    input.type = 'hidden';
    input.name = 'csrfmiddlewaretoken';
    input.value = '{{ csrf_token }}';
    form.appendChild(input);    

    var input = document.createElement('input');
    input.type = 'hidden';
    input.name = 'accountAddress';
    input.value = accountAddress;
    form.appendChild(input);

    var input = document.createElement('input');
    input.type = 'hidden';
    input.name = 'signature';
    input.value = signature;
    form.appendChild(input);

    document.body.appendChild(form);
    form.submit();
}
</script>

Данный скрипт состоит из двух функций web3Login и web3LoginBackend.

При нажатии на кнопку авторизации будет вызвана функция web3Login, активирующая окно «Запрос подписи» в расширении MetaMask. Подписывать мы будем сообщение вида Sign to auth {{ csrf_token }}.

После нажатия на кнопку «Подписать» в окне «Запрос подписи» расширения MetaMask будет вызвана функция web3LoginBackend, которая отправит данные (csrf token, адрес кошелька и подпись сообщения) в django view.


Перейдем к django view.

import datetime
import secrets
import names
import secrets
import string
import shortuuid
from django.contrib import messages
from django.contrib.auth import authenticate
from django.contrib.auth import login
from django.contrib.auth.models import User
from django.http import HttpResponse
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.shortcuts import reverse
from eth_account.messages import defunct_hash_message
from web3.auto import w3
from user_profile.models import UserProfile # TODO измените на свою модель


def auth_web3(request):

    public_address = request.POST["accountAddress"]
    signature = request.POST["signature"]
    csrf_token = request.POST["csrfmiddlewaretoken"]

    original_message = f"Sign to auth {csrf_token}"
    message_hash = defunct_hash_message(text=original_message)
    signer = w3.eth.account.recoverHash(message_hash, signature=signature)
    short_uuid = shortuuid.uuid()

    if signer == public_address:

        user_profile = UserProfile.objects.filter(eth_account_address=public_address).first()
        if user_profile:
            try:
                user = user_profile.user
            except:
                messages.add_message(request, messages.WARNING, _("Профайл не найден"))
                return HttpResponseRedirect(
                    reverse(
                        "main:user_login",  # TODO измените на свой адрес
                    )
                )
            user.backend = "django.contrib.auth.backends.ModelBackend"
            login(request, user)
            return HttpResponseRedirect(reverse("main:dashboard")) # TODO измените на свой адрес

        else:
        		alphabet = string.ascii_letters + string.digits
						password = "".join(secrets.choice(alphabet) for i in range(20))
            
            first_name = names.get_first_name()
            last_name = names.get_last_name()
            email = f"{short_uuid}@{HOST}"
            
            user = User.objects.create_user(email=email, username=short_uuid, first_name=first_name, last_name=last_name, password=password)

            user_profile = UserProfile()
            user_profile.user = user
            user_profile.eth_account_address = public_address
            user_profile.save()

            user.backend = "django.contrib.auth.backends.ModelBackend"
            login(request, user)

            messages.success(request, _("Успешная регистрация."))
            return HttpResponseRedirect(reverse("main:dashboard")) # TODO измените на свой адрес

    else:
        messages.add_message(request, messages.WARNING, _("Обновите страницу и попробуйте еще раз"))
        return HttpResponseRedirect(
            reverse(
                "main:user_login",  # TODO измените на свой адрес
            )
        )

На строке 26 мы дублируем сообщение с django template

original_message = f"Sign to auth {csrf_token}"

И восстанавливаем адрес кошелька при помощи подписи signature полученной из django tempate после отправки данных в django view.

message_hash = defunct_hash_message(text=original_message)

signer = w3.eth.account.recoverHash(message_hash, signature=signature)

Если восстановленный адрес signer равен адресу public_address (адресу кошелька пользователя) отправленному из django template, то авторизация/регистрация прошла успешно и все что нам остается это сделать немного "магии" для авторизации/регистрации.

P.S. Рабочий пример можно найти по адресу workhours.space. А еще это удобный и бесплатный трекер времени с удобным ботом в телеграм - пользуйтесь.

Источник: https://habr.com/ru/post/684958/


Интересные статьи

Интересные статьи

Привет! Это вторая часть статьи, в которой мы будем разбирать практическое применение платформы Graylog.В первой части мы разобрали как платформу установить и произвести ...
Для написания данной статьи был изучен очень большой пласт материала, разбросанного по всему Интернету, по форумам, чатам, сайтам-блогам, stackoverflow. Я собрал все воед...
В прошлой части мы поговорили о советах директору по разработке бизнес-процесса в Битрикс24, сейчас же я постараюсь дать советы руководителям отделов, поскольку по моему опыту почти всегд...
SWAP (своп) — это механизм виртуальной памяти, при котором часть данных из оперативной памяти (ОЗУ) перемещается на хранение на HDD (жёсткий диск), SSD (твёрдотельный накоп...
Однажды, в понедельник, мне пришла в голову мысль — "а покопаюсь ка я в новом ядре" (новым относительно, но об этом позже). Мысль не появилась на ровном месте, а предпосылками для нее стали: ...