Search code examples
djangooauth-2.0google-oauthdjango-allauth

Use Oauth2+Django to get authorization to access user's Gmail


I was using this tutorial: https://www.geeksforgeeks.org/python-django-google-authentication-and-fetching-mails-from-scratch/. However, the oauth2client library is deprecated and hasn't been touched in almost 9 years. I have looked for various tutorials, but they seem to be written by AI; they make no sense, and do things like tell you to include credentials in a piece of code that does not use credentials.

So my question is, how can I implement oauth2 into my Django application so that user's can go to the Django website and give permission to read their emails?


Solution

  • I will start from the beginning and describe a minimally working example without using the oauth2 libraries. This tutorial intentionally omits error handling and security work, for simplicity. You might want to try using django-allauth, to implement google oauth2 authentication, check out documentation. Also, I suggest you read this document, this and as well as this and get everything set up (if you haven't already).

    To work, we need some high-level python library to make http requests, for example: request or httpx. In this example, I will use httpx. In addition, since we will be working with gmail.api in test mode, you need to add Test users here, for example, your gmail account will do.

    Here's a minimal working example to get things working:

    #  views.py
    from typing import Self, Sequence  
    from urllib.parse import urlencode  
      
    import httpx  
      
    from django.http import HttpResponseRedirect, JsonResponse  
    from django.shortcuts import render  
      
    GOOGLE_OAUTH2_CREDENTIALS = {  
        'client_id': 'your_client_id',  
        'client_secret': 'your_client_secret',  
        'scope': 'profile email https://mail.google.com/'.split(),  
        'redirect_uri': 'your_redirect_uri',  
    }  
      
      
    class GoogleOauthBackend:  
        AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth'  
        ACCESS_TOKEN_URL = 'https://oauth2.googleapis.com/token'  
        USER_INFO_URL = 'https://www.googleapis.com/oauth2/v2/userinfo'  
    
        def __init__(  
                self,  
                client_id: str,  
                client_secret: str,  
                scope: Sequence[str],  
                redirect_uri: str,  
                **optional) -> None:  
    
            self.client_id = client_id  
            self.client_secret = client_secret  
            self.scope = scope  
            self.redirect_uri = redirect_uri  
            self.optional = optional  
    
        @classmethod  
        def from_credentials(cls, credentials: dict) -> Self:  
            return cls(  
                client_id=credentials['client_id'],  
                client_secret=credentials['client_secret'],  
                scope=credentials['scope'],  
                redirect_uri=credentials['redirect_uri'],  
            )  
    
        def get_auth_url(self) -> str:  
            params = {  
                'client_id': self.client_id,  
                'redirect_uri': self.redirect_uri,  
                'response_type': 'code',  
                'scope': ' '.join(self.scope),  
                **self.optional,  
            }  
            return f'{self.AUTH_URL}?{urlencode(params)}'  
    
        def get_user_info(self, access_token: str, token_type: str) -> dict:  
            response = httpx.get(  
                url=self.USER_INFO_URL,  
                headers={  
                    'Authorization': f'{token_type} {access_token}',  
                },  
            )  
            return response.json()  
    
        def get_access_token(self, code: str) -> tuple[str, str]:  
            params = {  
                'client_id': self.client_id,  
                'client_secret': self.client_secret,  
                'code': code,  
                'redirect_uri': self.redirect_uri,  
                'grant_type': 'authorization_code',  
            }  
            response = httpx.post(url=self.ACCESS_TOKEN_URL, data=params)  
            data = response.json()  
            return data['access_token'], data['token_type']  
    
    
    class GoogleGmailClient:  
        API_URL = 'https://gmail.googleapis.com'  
    
        def __init__(self, user_id: int, access_token: str, token_type: str):  
            self.user_id = user_id  
            self.access_token = access_token  
            self.token_type = token_type  
    
        def get_user_mail_messages(self):
            url = f'{self.API_URL}/gmail/v1/users/{self.user_id}/messages'  
            return httpx.get(url=url, headers=self.headers).json()['messages']  
    
        def get_user_mail_message_details(self, mail_id: str):
            url = f'{self.API_URL}/gmail/v1/users/{self.user_id}/messages/{mail_id}'  
            return httpx.get(url=url, headers=self.headers).json()  
    
        @property  
        def headers(self):  
            return {  
                'Authorization': f'{self.token_type} {self.access_token}',  
            }  
    
    
    def main_page(request):  
        return render(request=request, template_name='main_page.html')  
    
    
    def google_login(request):  
        google_oauth_backend = GoogleOauthBackend.from_credentials(  
            credentials=GOOGLE_OAUTH2_CREDENTIALS,  
        )  
        return HttpResponseRedirect(google_oauth_backend.get_auth_url())  
    
    
    def google_callback(request):  
        code = request.GET.get('code')  
        google_oauth_backend = GoogleOauthBackend.from_credentials(  
            credentials=GOOGLE_OAUTH2_CREDENTIALS,  
        )
        access_token, token_type = google_oauth_backend.get_access_token(code=code)  
        user_info = google_oauth_backend.get_user_info(  
            access_token=access_token,  
            token_type=token_type,  
        )  
        user_id = user_info['id']  
        first_user_mail_details = get_user_first_mail_message_details(
            GoogleGmailClient(  
                user_id=user_id,  
                access_token=access_token,  
                token_type=token_type,  
            ),  
        )  
        return JsonResponse(data=first_user_mail_details)  
    
    
    def get_user_first_mail_message_details(gmail_client: GoogleGmailClient):
        mails = gmail_client.get_user_mail_messages()
        first_mail_id = mails[0]['id']  
        return gmail_client.get_user_mail_message_details(mail_id=first_mail_id)
    
    {#main_page.html#}  
    <!doctype html>  
    <html lang="en">  
    <head>  
      <meta charset="UTF-8">  
      <meta http-equiv="X-UA-Compatible" content="ie=edge">  
      <title>Document</title>  
    </head>  
    <body>  
    <form method="POST" action="{% url 'google_login' %}">  
      {% csrf_token %}  
      <button type="submit">GOOGLE</button>  
    </form>  
    </body>  
    </html>
    
    # urls.py
    from django.urls import path
    
    from . import views  
      
    urlpatterns = [  
        path('', views.main_page),  
        path('google-login/', views.google_login, name='google_login'),  
        path('google-callback/', views.google_callback),  
    ]
    

    So. Essentially, there are three endpoints in this example. The home page loads HTML, with a GOOGLE button, clicking on the button will call the google-login/ endpoint, which redirects the user to log in via Google. After that google will invoke the google-callback/ endpoint, where if everything went smoothly, you will receive code which is exchanged for an access token that will allow you to make authenticated requests on behalf of the user to api google.

    In addition, here are some useful links:

    1. gmail scopes that you can request.
    2. gmail api endpoints.
    3. here is more detailed documentation, on gmail api endpoints.
    4. ready clients for different programming languages, in particular for python.

    This is an example, not for production, as I wrote above that many things are highly simplified, however it should give some insight for you and lots of links including off-the-shelf solutions. I hope this will be helpful to you.