Написание пользовательского класса аутентификации в Django Rest Framework для работы с firebase

Предварительные требования

Если вы знаете, как настроить проект в Django Rest Framework и флаттер, и имеете некоторое базовое представление о REST API, тогда вам хорошо. Если вы хотите изучить основы Django / Flutter, перейдите к официальной документации по Django и Flutter и не стесняйтесь возвращаться к этой статье.

Введение

В этой статье исследуется способ написания пользовательского бэкэнда аутентификации для Django Rest Framework, который можно использовать для идентификации пользователей из вашего приложения flutter, которое использует firebase в качестве бэкэнда аутентификации. Приложение flutter подключается к серверу аутентификации firebase и проверяет пользователя. После проверки пользователя серверная часть аутентификации firebase возвращает токен идентификатора, который добавляется к каждому запросу в нашу серверную часть Django Rest. Наша настраиваемая серверная часть аутентификации проверяет токен идентификатора и идентифицирует пользователя.

Содержание:

Создание проекта фреймворка Django Rest

Создайте проект Django и добавьте Django Rest Framework в настройки установленного приложения.

python -m venv .venv #Create a virtual environment if required
.venv\Scripts\activate #activate virtual environment on windows
source .venv\bin\activate #activate virtual environment on Linux
#installing required libraries
pip install django
pip install djangorestframework
pip install markdown       
pip install django-filter
pip install firebase-admin
#Create Django project
django-admin startproject firebaseauth
#Add 'rest_framework' to your INSTALLED_APPS setting.
INSTALLED_APPS = [
    ...
    'rest_framework',
]
#Add the following to your root urls.py file.
from django.urls import path,include
urlpatterns = [
    ...
    path('api-auth/', include('rest_framework.urls'))
]

Установите firebase-admin и настройте административный SDK на своем сервере.

Перейдите в Firebase Console, нажмите Добавить проект, затем введите имя проекта и нажмите Продолжить. При необходимости вы можете включить Google Analytics для своего проекта. Нажмите Создать проект. После создания проекта перейдите в Настройки проекта ›Учетные записи служб и сгенерируйте новый закрытый ключ. Он будет использоваться административным SDK для связи с серверной частью firebase. Этот закрытый ключ должен храниться в надежном месте и не подлежит разглашению. В загруженном файле JSON есть сведения, необходимые для инициализации приложения. Чтобы позволить Admin SDK найти эти сведения, вы можете либо установить переменную среды GOOGLE_APPLICATION_CREDENTIALS, либо явно передать путь к ключу учетной записи службы в коде. Первый вариант более безопасен и настоятельно рекомендуется.

Дальнейшее чтение :

Написание пользовательского класса аутентификации для Django Rest Framework

Пакет dart для FirebaseAuth позволит нам иметь токен идентификатора, как только наш пользователь будет аутентифицирован в нашем приложении Flutter. Это веб-токен JSON, который можно проверить на стороне сервера для идентификации пользователя, отправляющего запрос. Мы будем отправлять токен ID с каждым запросом в заголовке Authorization, а в нашем классе аутентификации мы будем использовать Firebase Admin SDK для проверки токена ID на нашем сервере DRF.

Давайте создадим новое приложение для управления пользователями и аутентификации. Если вы хотите, вы также можете использовать это приложение для расширения пользовательской модели по умолчанию.

python manage.py startapp auth

Создайте файл backends.py в приложении auth. Здесь мы будем писать наш собственный сервер аутентификации. Чтобы создать собственную схему аутентификации в DRF, мы создаем подкласс класса BaseAuthentication и переопределяем метод authenticate(). Метод аутентификации должен проверять учетные данные и возвращать кортеж (user,auth), если учетные данные проверены успешно, и None в противном случае.

Получите токен Id из заголовка запроса, используя приведенный ниже код.

authorization_header =   
                    request.META.get("HTTP_AUTHORIZATION")
id_token = authorization_header.split(" ").pop()

Вышеупомянутый идентификационный токен можно проверить с помощью приведенного ниже статуса.

decoded_token = auth.verify_id_token(id_token)
uid = decoded_token['uid'] #get the unique user id

Мы можем сохранить этот uid как первичный ключ в нашей модели пользователя, чтобы было легче идентифицировать пользователей на основе этого uid.

В приведенном ниже коде показан последний файл «backends.py».

from django.contrib.auth.models import User
from rest_framework import authentication
from rest_framework import exceptions
from firebase_admin import auth,initialize_app
initialize_app()
class FirebaseBackend(authentication.BaseAuthentication):
    def authenticate(self, request):
        authorization_header = 
request.META.get("HTTP_AUTHORIZATION")
        if not authorization_header:
            raise exceptions.AuthenticationFailed('Authorization credentials not provided')
        id_token = authorization_header.split(" ").pop()
        if not id_token:
            raise exceptions.AuthenticationFailed('Authorization credentials not provided')
        decoded_token = None
        try:
            decoded_token = auth.verify_id_token(id_token)
        except Exception:
            raise exceptions.AuthenticationFailed('Invalid ID Token')
try:
            uid = decoded_token.get("uid")
        except Exception:
            raise exceptions.AuthenticationFailed('No such user exists')
        
        user, created = User.objects.get_or_create(username=uid)
        
        if((not user.first_name or not hasattr(user,'userprofile'))      
        and not (request.method == 'PUT' 
                  and request.path.startswith("/api/users/"))):
            raise exceptions.PermissionDenied('User profile is incomplete. Please update the Profile Details')
#allow users to make API calls only after profile is completed
      return (user, None)

Перейдите в корневой файл settings.py и добавьте следующие настройки, чтобы добавить наш собственный сервер аутентификации на наш сервер Django.

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'auth.backends.FirebaseBackend',
    ]
}

Дальнейшее чтение

Создание простой конечной точки API для тестирования реализации

Мы будем создавать сериализатор на основе модели User в Django. Я создам другое приложение для конечной точки API. Добавьте еще одну модель с именем userprofile, чтобы мы могли хранить некоторые дополнительные сведения, относящиеся к пользователю.

class UserProfile(models.Model):
    user = models.OneToOneField(User,on_delete=models.CASCADE)
    bio = models.CharField(max_length=200)
    dob = models.DateField()

Теперь нам нужно добавить сериализаторы и представления для этих моделей, чтобы к ним можно было получить доступ из API. Здесь используется простой ModelSerializer. Обратите внимание, что мы должны переопределить update() и create(), поскольку ModelSerializer не поддерживает запись во вложенные сериализаторы. Здесь перезаписывается только метод update(), поскольку мы не используем прямой запрос API для создания пользователей.

class UserProfileSerializer(serializers.ModelSerializer):
    class Meta:
        model=UserProfile
        fields=['bio','dob']
class UserSerializer(serializers.ModelSerializer):    
    userprofile = UserProfileSerializer()
    def update(self, instance,validated_data):
        if(not instance.username == 
                        self.context['request'].user.username):
            raise exceptions.PermissionDenied('You do not have permission to update')
        profile_data = validated_data.pop('userprofile')
        if(not hasattr(instance,'userprofile')):
            instance.userprofile = 
           UserProfile.objects.create(user=instance,**profile_data)
        else:
            instance.userprofile.dob = profile_data["dob"]
            instance.userprofile.bio = profile_data["bio"]
            instance.userprofile.save()
        instance.first_name = validated_data.get('first_name',instance.first_name)
        instance.last_name = validated_data.get('last_name',instance.last_name)
        instance.email = validated_data.get('email',instance.email)
        instance.save()
        return instance
        
    class Meta:
        model = User
        fields = ['last_name','first_name','userprofile']

Чтобы сделать эти сериализаторы доступными через наш api, мы используем маршрутизатор по умолчанию в DRF вместе с ModelViewSet

#views.py
from django.shortcuts import render
from .serializers import UserSerializer
from django.contrib.auth.models import User
from rest_framework import viewsets
class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    lookup_field = 'username'
#urls.py
from django.urls import path, include
from .views import UserViewSet
from rest_framework import routers
router =routers.DefaultRouter()
router.register(r'users', UserViewSet)
urlpatterns = [
    path('', include(router.urls)),
]

Не забудьте включить этот файл URL-адресов в файл корневых URL-адресов.

Дальнейшее чтение:

Создайте приложение Flutter

Мы собираемся создать демонстрационное приложение Flutter с 3 экранами (вход / регистрация, данные профиля и главная). Я не буду вдаваться в подробности создания приложения flutter, но я добавлю исходный код в github.

Добавление firebase в приложение flutter

Подпишите свое приложение Flutter с помощью keytool в Android SDK. Теперь добавьте приложение flutter в проект firebase, который вы создали на одном из предыдущих шагов. Подпись необходима, чтобы мы могли получить подпись SHA, которая требуется для FirebaseAuth. Вы можете найти подробные инструкции в официальной документации.

После этого добавьте пакеты firebase_core и firebase_auth в pubspec.yaml

firebase_core: ^1.4.0
firebase_auth: ^3.0.1

Аутентификация и интеграция Django API в приложение

Экземпляр FirebaseAuth имеет метод userChanges(), который возвращает поток, который можно прослушивать с помощью виджета Stream Builder. Это позволяет нам получать обновления об изменениях состояния аутентификации или изменении токена идентификатора (когда он обновляется и т. Д.).

Как только пользователь вошел в систему, вы можете использовать функцию getIdToken() из firebase auth для получения токена идентификатора. Этот токен Id можно использовать для отправки запросов на ваш сервер DRF.

String id_token = await FirebaseAuth.instance.currentUser!.getIdToken();
http.put(
    Uri.parse("http://10.0.2.2:8000/api/users/" +                                    FirebaseAuth.instance.currentUser!.uid + "/"),
    headers: {
        "Content-Type": "application/json",
        "Authorization":"Token " + id_token
    },
    .....    
)

Дальнейшее чтение:

Вы можете найти исходный код приложения flutter и бэкэнда Django на Github. Имейте в виду, что это не полное решение. В приложении можно многое улучшить, например, проверить вводимые данные и надлежащую обработку ошибок для вызовов API, а также методов регистрации и входа в систему. Он не был включен сюда для краткости статьи.

Надеюсь, эта статья вам помогла! Спасибо или читаю.