django-allauth icon indicating copy to clipboard operation
django-allauth copied to clipboard

Implement authorization using Google Identity Service (Google Sign-In for Websites)

Open ndt080 opened this issue 2 years ago • 1 comments

Google has been switching to the Google Identity SDK since April of this year. Now Google returns a JWT token as a Credential Response. Now django-allauth + dj-rest-auth requires the transfer of access_token from the old GAPI. How to implement work with JWT?

ndt080 avatar Apr 15 '22 09:04 ndt080

https://developers.googleblog.com/2021/08/gsi-jsweb-deprecation.html

Google intends to deprecate the old API by March 31, 2023

steverecio avatar Aug 27 '22 17:08 steverecio

Hi, any progress or at least a roadmap for this?

This issue prevents usage of "django-allauth" in apps with newly created Google accounts! Namely, not only "The library will be unavailable for download after the March 31, 2023", BUT "By default, newly created Client IDs are now blocked from using the older Platform Library" (from: https://developers.google.com/identity/sign-in/web/sign-in)

sinisarudan avatar Sep 16 '22 19:09 sinisarudan

Quick update. The handleCredentialResponse will return an object which contains the access token under the variable credential.

handleCredentialResponse(opts) {
    const access_token = opts.credential;
    // send to django-allauth for validation
}

Follow this guide for updating from the old Google sign-in: https://developers.google.com/identity/gsi/web/guides/migration

This issue should be closed as no update is required to maintain compatibility @pennersr

steverecio avatar Dec 01 '22 19:12 steverecio

Hey @steverecio ! Would you mind explaining your solution a bit more in detail?

I got to this point where I have the custom callback function, but the API of allauth expects some query params, at least the "code" one.

The code I see using the old google authentication doesn't look like anything present in the new credential, so how did you solve this?

Thanks

mcosti avatar Dec 23 '22 19:12 mcosti

any update on this?

nasir733 avatar Jul 14 '23 19:07 nasir733

Isn't this already handled here https://github.com/pennersr/django-allauth/pull/3053/files ?

pennersr avatar Jul 15 '23 09:07 pennersr

Hi all, I managed to make this work with the JavaScript One Tap, so the user can log in without refreshing the page. Just create a new view /accounts/google-one-tap/,

import json

from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from django.contrib.auth import login as auth_login

from allauth.socialaccount.providers.google.views import GoogleOAuth2Adapter
from allauth.socialaccount.providers.oauth2.client import OAuth2Error
from allauth.socialaccount.helpers import complete_social_login
from allauth.socialaccount.models import SocialApp

from google.oauth2 import id_token
from google.auth.transport import requests


@csrf_exempt
@require_http_methods(["POST"])
def google_one_tap(request):

    credential = json.loads(request.body).get('credential')

    google_app = SocialApp.objects.get(provider='google')
    client_id = google_app.client_id

    payload = None
    try:
        payload = id_token.verify_oauth2_token(credential, requests.Request(), client_id)
    except ValueError as e:
        return JsonResponse({"success": False, "error": str(e)})

    try:
        # Add the JWT token to the payload
        # as the AllAuth GoogleOAuth2Adapter expects the token to be in the key 'id_token'
        payload['id_token'] = credential

        adapter = GoogleOAuth2Adapter(request)
        app = adapter.get_provider().get_app(request)
        login = adapter.complete_login(request, app, credential, response=payload)

        complete_social_login(request, login)

        # Use Django's login
        auth_login(request, login.user)

        return JsonResponse({"success": True, "detail": "Login successful"})

    except OAuth2Error as e:
        return JsonResponse({"success": False, "error": str(e)})

    <script src="https://accounts.google.com/gsi/client" async></script>
    <script>
        function postCredentialToServer(credential) {
            fetch('/accounts/google-one-tap/', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    credential: credential
                })
            }).then(response => response.json()).then(data => {
                window.dispatchEvent(new CustomEvent('ONE_TAP_LOGIN', {detail: data}));
            });
        }
        window.onload = function () {
            google.accounts.id.initialize({
                client_id: 'CLIENT_ID',
                callback: (response) => postCredentialToServer(response.credential),
            });
        }
    </script>

Would love to hear any criticism about security and/or alternatives. Hope it might be useful for someone else.

slig avatar Aug 07 '23 20:08 slig

Now supported via 9246957e

pennersr avatar Dec 15 '23 21:12 pennersr