PythonSnippets.Dev
A curated list of good to know Python Snippets. Visit PythonSnippets.dev.
Advertise with us
authentication   0   3858
Integrating GitHub login in Django without AllAuth Package

I recently integrated the 'Login with GitHub' button in a Django application. I had the option to use Django-AllAuth Package. However, neither I required all the functionality of this package nor I wanted to use any existing package. Hence I moved ahead with the plain vanilla GitHub OAuth integration in Django.


For this article, I used Django 3.2, and Python 3.9.


Here are the steps I followed to integrate GitHub OAuth in Django.


Creating an OAuth app in GitHub:

Go to https://github.com/settings/developers and create a new app. Enter the required details. In the Authorization callback URL, enter a valid endpoint of your application. 

Once the app is created, you will get a secret key and client id. Store them in your env file or in a secret file. This secret or env file should not be committed to your remote repository.



Creating URLs:

Create below three URLs in your application's urls.py file.


path(r'login/github/', views.login_github, name='login_github'),
path(r'login/github/callback/', views.login_github_callback, name='login_github_callback'),
path(r'logout/', views.mylogout, name='mylogout'),



Templates:

Add below HTML code snippet in your home page template to create Login with GitHub buttons. I used font-awesome version 4.7 for the GitHub icon.


<div style="margin-top:5px;">
<a href="{% url 'snip:login_github' %}" class="white">
Login with GitHub <i class="fa fa-github"></i>
</a>
</div>



Creating views:

import below modules in your views.py file.

from django.shortcuts import redirect, render
from django.urls import reverse
from django.conf import settings
from django.http import HttpResponse
from django.contrib.auth import logout, login
from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required
import requests
import logging
import traceback
from django.contrib import messages


In your views.py file create three views as below.

logout view:

This function will be called when clicking on the logout link or button.

@login_required
def mylogout(request):
logout(request)
return redirect(reverse("snip:index", args=(), kwargs={}))


login_github view:

This function will be called when a user clicks the 'Login with GitHub' button.


def login_github(request):
client_id = settings.GITHUB_CLIENT_ID
scope = 'read:user'
state = 'somerandomstring123' # to prevent csrf
return redirect(
'https://github.com/login/oauth/authorize?client_id={}&scope={}&state={}'.format(client_id,
scope, state,
))


the scope lets you specify what kind of access you need. What kind of data you need after you are successfully authenticated by GitHub. Read more here https://docs.github.com/en/developers/apps/building-oauth-apps/scopes-for-oauth-apps.

When the Login button is clicked, a call is made to Github's authorize API and we are redirected to a page where we need to Authorize Github to let our Django application access the user details.


login_github_callback view:

Once you click on the 'Authorize' button, GitHub redirects you back to the callback URL, you entered while creating App, with a temporary code. 

Now we need to get a token from GitHub using this temporary code. Using the token received, we can hit any GitHub API. In our case, we will be hitting user API.


def login_github_callback(request):
# verify the state variable value for csrf
code = request.GET.get('code', None)

if not code:
messages.error(request, "Invalid Code received from Github Auth API");
logging.getLogger('error').error('code not present in request {}'.format(request.GET))
return redirect(reverse("app:index", args=(), kwargs={}))

# first redirect
params = {
'client_id': settings.GITHUB_CLIENT_ID,
'client_secret': settings.GITHUB_SECRET,
'code': code,
'Content-Type': 'application/json'
}

headers = {
'Accept': 'application/json'
}

result = requests.post('https://github.com/login/oauth/access_token', data=params, headers=headers)
# print(result)
# print(result.text)
# print(result.json())
token = result.json().get('access_token')
logging.getLogger('info').info('token received in response from github')
# after getting the token, access the user api to get user details
user_api_url = 'https://api.github.com/user'
headers = {
'Authorization': 'token ' + token,
'Accept': 'application/json'
}
result = requests.get(user_api_url, headers=headers)
# print(result.json())
user_data = result.json()
logging.getLogger('info').info('user data from github {}'.format(user_data))
email = user_data.get('email', None)
if not email:
messages.error(request, "Invalid data received from GitHub");
logging.getLogger('error').error('email not present in data received from github {}'.format(user_data))
return redirect(reverse("app:index", args=(), kwargs={}))

# get the user details, now login this user.

try:
user = User.objects.get(email=email)
logging.getLogger('info').info('user already exists in db')
except User.DoesNotExist as e:
# if user does not exists in db, create a user and save it.
name = user_data.get('name', None)
if name:
splitted_name = name.split(' ')
first_name = splitted_name[0]
if len(splitted_name) > 1:
last_name = splitted_name[1]
else:
last_name = ''
else:
first_name = ''
last_name = ''

# create user
user = User()
# let's use the email as username
user.username = email
user.email = email
user.first_name = first_name
user.last_name = last_name
user.is_admin = False
user.is_active = True
user.is_superuser = False

user.save()
logging.getLogger('info').info('user created in db')

except Exception as e:
messages.error(request, "Some error occurred while logging. Please let us know.")
logging.getLogger('error').error(traceback.format_exc())

login(request, user)
messages.success(request, "Login Successful");
return redirect(reverse("app:index", args=(), kwargs={}))


Once the user details are received from GitHub API, extract the email address from user data. We will see if any user with the given email exists in the user table. If not, then we will create and save the user in the user table. If yes, we will fetch the user by given email address from the table.

Next, we will log in using the above user. If everything is executed without any error, we redirect to the home page with a success message. Else, we redirect to the home page with an error message.


Settings.py file:

We need the below settings in the settings.py file for the above code to work.


# LOGIN_URL is used by login_required decorator
LOGIN_URL = reverse_lazy('app:index')

# logout the user - invalidate the session - when browser is closed
SESSION_EXPIRE_AT_BROWSER_CLOSE = True

GITHUB_SECRET = env("GITHUB_SECRET")
GITHUB_CLIENT_ID = env("GITHUB_CLIENT_ID")
# for logging the messages to log files
try:
from .logger_settings import *
except Exception as e:
print("Unable to load logger settings")
pass

# these message tags will be used in bootstrap
try:
from django.contrib.messages import constants as messages

MESSAGE_TAGS = {
messages.DEBUG: 'alert-info',
messages.INFO: 'alert-info',
messages.SUCCESS: 'alert-success',
messages.WARNING: 'alert-warning',
messages.ERROR: 'alert-danger',
}
except Exception as e:
pass


ENV file:

GITHUB_SECRET=7863876q7e6q78e6q782e
GITHUB_CLIENT_ID=djq7e89jq27e



Dependencies used:

asgiref==3.4.1
certifi==2021.5.30
chardet==4.0.0
Django==3.2.5
django-environ==0.4.5
gunicorn==20.1.0
idna==2.10
mysqlclient==2.0.3
Pillow==8.3.0
Pygments==2.9.0
pytz==2021.1
requests==2.25.1
sqlparse==0.4.1
urllib3==1.26.6



Drop a mail in case of any query.


- Here is a list of some awesome python books
- Host your Django Application for free on PythonAnyWhere.
- If you want full control of your application and server, you should consider DigitalOcean.
- Create a DigitalOcean account with this link and get $100 credits.



cover image: https://unsplash.com/@synkevych


authentication   0   3858

Related Articles:
Creating custom user model  and custom authentication in Django
How to extend the default user model in Django, Defining your own custom user model in Django. Writing your own authentication backend in Django, Using Email and Password to login in Django...

SUBSCRIBE
Please subscribe to get the latest articles in your mailbox.





DigitalOcean Referral Badge

Get a .COM for just $5.98!


© 2021-2022 Python Circle   Contact   Sponsor   Archive   Sitemap