How to connect flutter and firebase authentication with your Django Rest Framework Backend
Writing Custom Authentication Class in Django Rest Framework to work with firebase
Prerequisites
If you know how to setup a project in Django Rest Framework and flutter and have some basic understanding of REST API then you are good to go. If you want to learn the basics of Django/Flutter, Please head on to the official documentation for Django and Flutter and feel free to come back to this article.
Introduction
This article explores a way to write a custom authentication backend for Django Rest Framework which can be used to identify users from your flutter app which uses firebase as the authentication backend. The flutter app connects with firebase authentication backend and validates the user. Once the user is validated, the firebase authentication backend returns an ID Token, which is added to every request to our Django Rest Backend. Our custom authentication backend validates the ID token and identifies the user.
Contents:
- Create a Django Rest framework Project
- Install firebase-admin and setup the admin SDK on your server
- Writing custom authentication class for Django Rest Framework
- Creating a simple API endpoint to test the implementation
- Create the flutter app
- Adding firebase to the flutter app
- Adding FirebaseAuth and Integrating the Django API into the app
Create a Django Rest framework Project
Create a Django project and add Django Rest Framework to the installed app settings.
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'))
]
Install firebase-admin and setup the admin SDK on your server
Go to Firebase Console click Add project, then enter a Project name and click continue. If required, you can enable Google Analytics for your project. Click Create project. After creating the project, Go to project Settings > Service Accounts and generate a new private key. This will be used by the admin SDK to communicate with the firebase backend. This private key has to be stored securely and should not be made public. The downloaded JSON file has the necessary details to initialize the app. To let the Admin SDK find these details, you can either set the GOOGLE_APPLICATION_CREDENTIALS environment variable, or you can explicitly pass the path to the service account key in code. The first option is more secure and is strongly recommended.
Further reading :
Writing custom authentication class for Django Rest Framework
The dart package for FirebaseAuth will allow us to have an ID Token once our user is authenticated in our Flutter App. It is a JSON Web Token which can be validated on the server side to identify the user sending the request. We will be sending the ID Token with every request in the Authorization
header and in our authentication class we will be using the Firebase Admin SDK to validate the ID Token on our DRF server.
Lets create a new app for managing the users and authentication. If you want you could also use this app to extend the default user model.
python manage.py startapp auth
Create ‘backends.py’ file inside the auth app. This is were we will be writing our custom authentication backend. To create a custom Authentication scheme in DRF, we subclass the BaseAuthentication
class and override the authenticate()
method. The authenticate method should validate the credential and return a tuple (user,auth)
if the credential is validated successfully and None
otherwise.
Get the Id token from the request header using the below code.
authorization_header =
request.META.get("HTTP_AUTHORIZATION")
id_token = authorization_header.split(" ").pop()
The above ID Token can be validated using the below statment.
decoded_token = auth.verify_id_token(id_token)
uid = decoded_token['uid'] #get the unique user id
We can store this uid
as primary key in our user model so that it will be easier to identify users based on this uid
.
The below code shows the final ‘backends.py’ file.
from django.contrib.auth.models import User
from rest_framework import authentication
from rest_framework import exceptionsfrom 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)
Go to the root settings.py file and add the following settings to add our custom authentication backend to our Django server.
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'auth.backends.FirebaseBackend',
]
}
Further Reading
Creating a simple API endpoint to test the implementation
We will be creating a serializer based on the User
model in Django. I will creating another app for the API endpoint. Add another model named userprofile
so that we can store some extra details related to the user.
class UserProfile(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE)
bio = models.CharField(max_length=200)
dob = models.DateField()
Now we have to add serializers and views for these Models so that it can be accessed from the API. A simple ModelSerializer
is being used here. Note that we have to override the update()
and create()
since the ModelSerializer
does not support writing to nested serializers. Here only update()
method is being overwritten as we are not using a direct API request to create users.
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']
In order to make this serializers accessible through our api we are using the default router in DRF along with a ModelViewSet
#views.py
from django.shortcuts import render
from .serializers import UserSerializer
from django.contrib.auth.models import User
from rest_framework import viewsetsclass 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 routersrouter =routers.DefaultRouter()
router.register(r'users', UserViewSet)urlpatterns = [
path('', include(router.urls)),
]
Keep in mind to include this urls file in the root urls file.
Further Reading:
Create the flutter app
We are going to create a demo flutter app with 3 screens(login/register, profile details and home). I am not going into details of creating the flutter app, but I will be adding the source code to github.
Adding firebase to the flutter app
Sign your flutter app using the keytool in Android SDK. Now add the flutter app in the firebase project that you created in one of the previous step. Signing is required so that we can get the SHA Signature which is required by FirebaseAuth. You can find the details steps in the official documentation.
After that add firebase_core and firebase_auth packages to pubspec.yaml
firebase_core: ^1.4.0
firebase_auth: ^3.0.1
Authentication and Integrating the Django API into the app
FirebaseAuth instance has a method userChanges()
which returns a Stream that can be listened to, using the Stream Builder Widget. This allows us to get updates of auth state changes or the change in id token( when it is refreshed etc.).
Once the user is logged in you can use the getIdToken()
function from firebase auth to obtain the ID token. This Id token can be used to send requests to your DRF server.
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
},
.....
)
Further reading:
You can find the source code for the flutter app and Django backend on Github. Please keep in mind that this is not a complete solution. A lot can be improved in the app such as input validation and proper error handling for API calls and register and login methods. It was not included here to keep the article concise.
Hope this article helped you! Thanks or reading.