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.
- Full OAuth2 authentication flow, with automatic access token refresh.
- Supporting two mechanisms:
- 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.
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 |
- | - | - |
-
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 tosettings.py
:__import__('pysqlite3') import sys sys.modules['sqlite3'] = sys.modules.pop('pysqlite3')
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
Diffing changes between the code in this repository and a newly created Django project (django-admin startproject my_django_app .
):
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 runningpython manage.py migrate
)
-
Modify
my_django_app/settings.py
: Django admin site and static files apps are disabled. For middleware, onlySessionMiddleware
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), ]
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):
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):
- python - Getting refresh_token with lepture/authlib - Stack Overflow
-
client_credentials
won't issue refresh token. You need to use authorization_code flow to get the refresh token.- A OAuth server-side configuration seems to be needed: stackoverflow.com/questions/51305430/β¦
-
- Refresh and Auto Update Token Β· Issue #245 Β· lepture/authlib
-
Also be aware, unless you're on authlib 0.14.3 or later, the django integration is broken for refresh (If you're using the metadata url): RemoteApp.request fails to use token_endpoint to refresh the access token Β· Issue #193 Β· lepture/authlib
-
-
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 ofInstalledAppFlow
. -
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 namedtoken
):{ '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 }
-
Flow.fetch_token(code=code)
method of thegoogle_auth_oauthlib.flow.Flow
class (google-auth-oauthlib 0.4.1
documentation). -
Flow.credentials
constructs agoogle.oauth2.credentials.Credentials
class, which in turn is a child class ofgoogle.auth.credentials.Credentials
-
Key
google.auth.credentials.Credentials
members, that are inherited by thegoogle_auth_oauthlib.flow.Flow.credentials
class:to_json()
: Returns A JSON representation of this instance. When converted into a dictionary, it can be passed tofrom_authorized_user_info()
to create a new Credentials instance.id_token
: can be verified and decoded (parsed) usinggoogle.oauth2.id_token.verify_oauth2_token()
-
Featured Google OAuth implementations and libraries:
-
π¦
oauth2client
(googleapis/oauth2client): deprecated in favor ofgoogle-auth
.
βββπ» $ pip install --upgrade oauth2client
βββπ from oauth2client.client import GoogleCredentials
-
π¦
google-auth
(googleapis/google-auth-library-python): provides the ability to authenticate to Google APIs using various methods. It comprises two sub-packages:google.auth
andgoogle.oauth2
.
βββπ» $ pip install google-auth
βββπ from google.auth.transport.requests import Request
βββπ from google.oauth2.credentials import Credentials
-
π¦
OAuthLib
(oauthlib/oauthlib): a Python framework which implements the logic of OAuth1 or OAuth2 without assuming a specific HTTP request object or web framework.
βββπ» $ pip install oauthlib
βββπ from oauthlib.oauth2 import WebApplicationClient
-
π¦
google-auth-oauthlib
(googleapis/google-auth-library-python-oauthlib): used for this project. It contains experimentalOAuthLib
integration withgoogle-auth
.
βββπ» $ pip install google-auth-oauthlib
βββπ from google_auth_oauthlib.flow import Flow
-
MIT license.