Skip to content

🐍 Django boilerplate App for a full Google OAuth2 authentication flow

License

Notifications You must be signed in to change notification settings

amindeed/Google-OAuthLib-Django

Repository files navigation

Google OAuthLib Django

Lines of code Python Django Google API Python Client Google Auth OAuthLib License

Google APIs + OAuth2 + OIDC + Django

Google OAuthLib Django is a Django boilerplate app implementing a full Google OAuth2 authentication flow using google-auth-oauthlib with Sessions.

This is not intended as a replacement for Django's users sign up/in mechanism. Django AllAuth is more suited for such use case.

The App uses basic Django Sessions to let users make authenticated requests that target their Google accounts’ resources. No user data is kept after logout.

Table of Contents

google-oauthlib-django.gif

Features

  • Full OAuth2 authentication flow, with automatic access token refresh.
  • Supporting two mechanisms:
    • Using a Google service API client library, via the Google API Discovery resource constructor (googleapiclient.discovery.build).
    • Sending plain HTTP requests to a Google service API's endpoint, using a Bearer authentication scheme.
  • Identifying users by parsing Open ID Connect (OIDC) ID tokens.
  • Handling backend errors, warnings and infos, and storing their respective messages to the session key messages, which is added as a variable to template's context, and then rendered on the frontend.
  • Handling access to some special paths like /login and /auth.
  • @require_auth decorator for views.
  • logout view function, that clears browser cookies along with the corresponding session from Django's database.

Compatibility

The code was tested in the following environments (all OSs are x64):

Windows 10 CentOS 7 CentOS 8 Stream Ubuntu 16.04 Ubuntu 18.04 Ubuntu 20.04
Python 3.9.1 3.6.8 3.9.2 - - -
Django 3.2 -
3.1.8
3.2[*] 3.2 - - -
Google API Python Client 2.1.0 -
1.12.8
2.1.0 2.2.0 - - -
Google Auth OAuthLib 0.4.4 -
0.4.2
0.4.4 0.4.4 - - -

Setup

Install and configure requirements

  • Create a GCP project, enable Drive API, add the following scopes and then download webapp Client ID credentials file from the GCP console:

    https://www.googleapis.com/auth/drive
    https://www.googleapis.com/auth/userinfo.email
    https://www.googleapis.com/auth/userinfo.profile
    openid
    
  • Create a virtual environment and install required libraries:

    virtualenv venv
    source venv/Scripts/activate
    pip install Django google-api-python-client google-auth-oauthlib
    ## OR: 'pip install -r requirements.txt'
    ## You might also need to install 'pysqlite3-binary' (check note below)
  • Since Django 2.2, the minimum supported version of SQLite is 3.8.3. So if your system does not meet this requirement (like CentOS 7), you can either make a system-wide upgrade of the SQLite3 package, or (better) install pysqlite3-binary in your Python virtual environment and override the sqlite3 module by prepending the following lines to settings.py:

    __import__('pysqlite3')
    import sys
    sys.modules['sqlite3'] = sys.modules.pop('pysqlite3')

Option 1: Cloning and running the code from this repository

Clone the repository, create the Django database and launch the dev server:

git clone [email protected]:amindeed/Google-OAuthLib-Django.git
cd Google-OAuthLib-Django
# Migrate changes to Django's DB (only in first time run)
python manage.py migrate
python manage.py runserver

Option 2: Update an existing Django project/app

Diffing changes between the code in this repository and a newly created Django project (django-admin startproject my_django_app .):

βž• Add files:

  • credentials.json (GCP project Client ID credentials)
  • my_django_app/templates/home.html ( templates/home.html sample base template)
  • my_django_app/views.py (views.py)
  • db.sqlite3 (automatically created by Django by running python manage.py migrate)

✎ Modify files:

  • Modify my_django_app/settings.py: Django admin site and static files apps are disabled. For middleware, only SessionMiddleware is kept, which is enough for the intended use case.

    @@ -8,7 +8,7 @@ BASE_DIR = Path(__file__).resolve().parent.parent
    # See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
    
    # SECURITY WARNING: keep the secret key used in production secret!
    -SECRET_KEY = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
    +SECRET_KEY = 'YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY'
    
    # SECURITY WARNING: don't run with debug turned on in production!
    DEBUG = True
    @@ -19,12 +19,12 @@ ALLOWED_HOSTS = []
    # Application definition
    
    INSTALLED_APPS = [
    -    'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
    -    'django.contrib.staticfiles',
    +
    +    'my_django_app',
    ]
    
    MIDDLEWARE = [
    @@ -32,8 +32,6 @@ MIDDLEWARE = [
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.common.CommonMiddleware',
        'django.middleware.csrf.CsrfViewMiddleware',
    -    'django.contrib.auth.middleware.AuthenticationMiddleware',
    -    'django.contrib.messages.middleware.MessageMiddleware',
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
    ]
  • Modify my_django_app/urls.py: All lines of code related to the Django admin site are removed.

    -from django.contrib import admin
    from django.urls import path
    +from my_django_app import views
    
    urlpatterns = [
    -    path('admin/', admin.site.urls),
    +    path('', views.home),
    +    path('login/', views.login),
    +    path('auth/', views.auth, name='auth'),
    +    path('logout/', views.logout_view),
    ]

Background

The code in this repository was a result of my researches for another project: GMail-AutoResponder.

The structure of the code was built upon the AuthLib library demo for Django and the Google Apps Script API Python Quickstart (and later, the Drive V3 Python Quickstart, for simpler API calls):

About AuthLib

AuthLib would have been my goto library, but I dropped it after many tries, due to confusing instructions about OAuth2 refresh token support for Django (at least, up until February 2021):

Google AppsScript/Drive API Python Quickstart code adaptation:

  • credentials.json contains OAuth client ID credentials of the GCP project, that will provide access to Google user's resources for the Django app.

  • Flow class was used instead of InstalledAppFlow.

  • Instead of pickles, JSON Serialization of Credentials is used: OAuth2 token (a Credentials instance) is converted to a dictionary and saved to the session (as a session key named token):

    {
        'token': 'XXXXXXXXX',
        'refresh_token': 'YYYYYYY',
        'token_uri': 'https://oauth2.googleapis.com/token',
        'client_id': 'a0a0a0a0a0a0a0a0a0.apps.googleusercontent.com',
        'client_secret': 'ZZZZZZZZZ',
        'scopes': [
            'https://www.googleapis.com/auth/drive.metadata.readonly', 
            'https://www.googleapis.com/auth/userinfo.email', 
            'https://www.googleapis.com/auth/userinfo.profile', 
            'openid'
            ],
        'expiry': '2021-03-06T12:34:52.214490Z'
    }
    
  • Value of the session key user (which identifies the logged in user) is retrieved by parsing the Open ID Connect ID Token contained in the Credentials object resulting from a complete and successful OAuth2 flow.

    {
        'iss': 'https://accounts.google.com',
        'azp': 'xxxxxxxxxxx.apps.googleusercontent.com',
        'aud': 'yyyyyyyyyyy.apps.googleusercontent.com',
        'sub': '0000000000000000000',
        'hd': 'mydomain.com',
        'email': '[email protected]',
        'email_verified': True,
        'at_hash': 'ZZZZZZZZZZZZZ',
        'name': 'Awesome User',
        'picture': 'https://xy0.googleusercontent.com/aa/bb/photo.jpg',
        'given_name': 'Awesome',
        'family_name': 'User',
        'locale': 'en',
        'iat': 111111111,
        'exp': 222222222
    }
    

Online Resources:

License

MIT license.

Releases

No releases published