logo
September 14, 2024

[TIL] Google Oauth2 with ReactJS x Django - The easy way

Learn how to implement Google OAuth2 authentication with a Django backend and ReactJS frontend. This comprehensive guide walks you through setting up Google API credentials, handling user login and consent, and retrieving user data from Google. Follow detailed steps for integrating Google login using @react-oauth/google in ReactJS and creating secure backend APIs with Django to manage JWT tokens and user information. Perfect for developers looking to integrate Google authentication into their web applications, this tutorial includes practical code examples and best practices for seamless user authentication.

Overview of the Flow

1. The user clicks “Continue/Login with Google.”

2. The frontend redirects the user to the Google User Consent Screen.

3. The user signs in to Google.

4. Google returns an Authentication Code.

5. The frontend sends this Authentication Code to the backend API.

6. The backend uses the Google API to exchange the Authentication Code for user information.

7. If the user does not already exist, the backend creates a new user.

8. The backend generates a JWT token and returns it to the frontend.

Prerequisite

First thing, you need to create a project in Google Console.

In that project, create an API credential Api -> Console and retrieve the client_id and client_secret to your settings.py:

python
GOOGLE_OAUTH2_CLIENT_ID = "Your_google_client_id"
GOOGLE_OAUTH2_CLIENT_SECRET = "Your_google_client_secret"

Django

  • rest_framework
  • rest_framework_simplejwt
  • requests
  • Implementation

    Frontend ReactJS

    To implement the login button, I use the @react-oauth/google package to design the button and handle the redirection to the Google User Consent Screen. When the user clicks “Login with Google,” they are directed to the User Consent Screen, where they choose their account and grant permissions to your app.

    You can see the demo here

  • https://www.npmjs.com/package/@react-oauth/google
  • https://react-oauth.vercel.app/
  • I choose the Authorization Code Flow with backend API.

    A visual depiction of what is being written about

    Frontend implementation

    javascript
    const googleLogin = useGoogleLogin({
    onSuccess: async ({ code }) => {
    const tokens = await axios.post('http://localhost:5000/auth/google', { // http://localhost:5000/auth/google backend that will exchange the code
    code,
    });
    console.log(tokens);
    },
    flow: 'auth-code',
    });

    Note, this flow use postmessage as redirect_uri as mentioned here https://github.com/MomenSherif/react-oauth/issues/252#issuecomment-1514552320

    A visual depiction of what is being written about

    Backend Django

    Retrieving User Data from Google

    Once the user logs in with their Google account, the frontend sends an authorization code to the backend (http://localhost:5000/auth/google). This code is then used to request the user’s data from Google.

    Serializer for Authorization Code

    python
    # serializers.py
    from rest_framework import serializers
    class AuthSerializer(serializers.Serializer):
    code = serializers.CharField(required=True, allow_null=False, allow_blank=False)

    The post method handles the request:

  • Validates the incoming data using AuthSerializer.
  • Retrieves user data from Google.
  • Checks if the user exists in the database; if not, creates a new user.
  • Generates a JWT token and sends it back to the frontend.
  • python
    # views.py
    from rest_framework.response import Response
    from rest_framework.views import APIView
    from rest_framework_simplejwt.tokens import RefreshToken
    from libs.google import google_get_user_data
    from ..models import User
    from ..serializers import AuthSerializer
    logger = logging.getLogger(__name__)
    class GoogleLoginView(APIView):
    serializer_class = AuthSerializer
    def post(self, request, *args, **kwargs):
    auth_serializer = self.serializer_class(data=request.data)
    auth_serializer.is_valid(raise_exception=True)
    validated_data = auth_serializer.validated_data
    # get user data from google
    user_data = google_get_user_data(validated_data)
    # Creates user in DB if first time login
    user, _ = User.objects.get_or_create(
    email=user_data.get('email'),
    first_name=user_data.get('given_name'),
    last_name=user_data.get('given_name'),
    provider=User.PROVIDER_GOOGLE,
    )
    # generate jwt token for the user
    token = RefreshToken.for_user(user)
    response = {
    'access': str(token.access_token),
    'refresh': str(token),
    }
    return Response(response, status=200)

    Get user data by using that Authorization Code

    python
    # libs/google.py
    from typing import Any
    from typing import Dict
    import requests
    from django.conf import settings
    from rest_framework.exceptions import APIException
    GOOGLE_ID_TOKEN_INFO_URL = 'https://www.googleapis.com/oauth2/v3/tokeninfo'
    GOOGLE_ACCESS_TOKEN_OBTAIN_URL = 'https://accounts.google.com/o/oauth2/token'
    GOOGLE_USER_INFO_URL = 'https://www.googleapis.com/oauth2/v3/userinfo'
    # Exchange authorization token with access token
    # https://developers.google.com/identity/protocols/oauth2/web-server#obtainingaccesstokens
    def google_get_access_token(code: str, redirect_uri: str) -> str:
    data = {
    'code': code,
    'client_id': settings.GOOGLE_OAUTH2_CLIENT_ID,
    'client_secret': settings.GOOGLE_OAUTH2_CLIENT_SECRET,
    'redirect_uri': redirect_uri,
    'grant_type': 'authorization_code',
    }
    response = requests.post(GOOGLE_ACCESS_TOKEN_OBTAIN_URL, data=data)
    if not response.ok:
    raise APIException(f'Could not get access token from Google: {response.json()}')
    access_token = response.json()['access_token']
    return access_token
    # Get user info from google
    # https://developers.google.com/identity/protocols/oauth2/web-server#callinganapi
    def google_get_user_info(access_token: str) -> Dict[str, Any]:
    response = requests.get(
    GOOGLE_USER_INFO_URL,
    params={'access_token': access_token},
    )
    if not response.ok:
    raise APIException('Could not get user info from Google: {response.json()}')
    return response.json()
    def google_get_user_data(validated_data):
    # https://github.com/MomenSherif/react-oauth/issues/252
    redirect_uri = 'postmessage'
    code = validated_data.get('code')
    access_token = google_get_access_token(code=code, redirect_uri=redirect_uri)
    return google_get_user_info(access_token=access_token)

  • google_get_access_token Exchanges the Authorization Code for an access_token.
  • google_get_user_info Retrieves user information using the access_token.
  • Summary

    This blog covers the implementation of Google OAuth2 authentication using Django for the backend and ReactJS for the frontend. I hope it can explain the complete flow of user authentication, including how to create Google API credentials, handle user consent, and retrieve user data from Google.

    Reference
  • https://www.npmjs.com/package/@react-oauth/google
  • https://medium.com/@rahadianpanjipanji/how-to-django-x-react-authentication-with-google-oauth2-2d9995b7cb91
  • https://csdcorp.com/blog/coding/oauth2-get-a-token-via-rest-google-sign-in/