Skip to content

Commit

Permalink
Enable logging in with Google SSO
Browse files Browse the repository at this point in the history
This commit enables logging in with SSO through Google, overriding the
default behavior of `django-allauth`, which is to not let any social
providers vouch that a user definitively owns an email address.

We should soon add a "Sign up with Google" button to the sign up page
too, but I want to make sure this works in production before I get too
much further.
  • Loading branch information
DavidCain committed Aug 27, 2022
1 parent 5fde6bc commit c8585bc
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 3 deletions.
16 changes: 15 additions & 1 deletion ws/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@
]

LOGIN_REDIRECT_URL = '/'
ACCOUNT_LOGOUT_REDIRECT_URL = '/'

INSTALLED_APPS = [
'django.contrib.admin',
Expand Down Expand Up @@ -253,6 +252,21 @@
# Always "remember me"
ACCOUNT_SESSION_REMEMBER = True

SOCIALACCOUNT_AUTO_SIGNUP = True
SOCIALACCOUNT_ADAPTER = "ws.social.TrustGoogleEmailOwnershipAdapter"

SOCIALACCOUNT_PROVIDERS = {
"google": {
"APP": {
"client_id": "105568993872-llfunbenb7fndfl17b7bk4mv7uq1jgd5.apps.googleusercontent.com",
"secret": os.environ.get('GOOGLE_OAUTH_SECRET_KEY', ''),
"key": "",
},
"SCOPE": ["email"],
"AUTH_PARAMS": {"access_type": "online"},
}
}

BURSAR_NAME = os.getenv('BURSAR_NAME', 'MITOC Bursar')

ROOT_URLCONF = 'ws.urls'
Expand Down
59 changes: 59 additions & 0 deletions ws/social.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import logging

from allauth.account.models import EmailAddress
from allauth.account.utils import user_email
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
from allauth.socialaccount.models import SocialLogin
from django.http import HttpRequest

from ws import settings

logger = logging.getLogger(__name__)


class TrustGoogleEmailOwnershipAdapter(DefaultSocialAccountAdapter):
"""Let users with an existing account grant Google login access.
This adapter exists to provide a better UX in the following scenario:
1. `[email protected]` signs up for `mitoc-trips` with a email & password
2. Alice signs out, time passes.
3. Later, Alice "logs in with Google"
4. Because an account exists under `[email protected]`, we can't complete login
- Alice must first log in with her password, then associate Google
By default, `django-allauth` will not let you automatically claim ownership
of an account just because a social provider vouches that you exist under
that email address. This makes sense. If you own a Facebook account under
`[email protected]`, there's no guarantees that you're necessarily the same
person.
However, because Google is an email provider, I think it's fair to assume
that if Google vouches for your identity, auto sign-in can be completed.
Related: https://github.com/pennersr/django-allauth/issues/418
"""

def pre_social_login(self, request: HttpRequest, sociallogin: SocialLogin) -> None:
"""Connect any Google-asserted email to accounts if existing."""
if sociallogin.is_existing: # Social account exists (normal login)
return

# I don't think there's an easy way to identify the provider in use...
# `request.path` should be at least be '/accounts/google/login/callback/'
assert set(settings.SOCIALACCOUNT_PROVIDERS) == {'google'}

email: str = user_email(sociallogin.user)

try:
verified_email = EmailAddress.objects.get(
email__iexact=email,
# This is critical. If we didn't require a *verified* email, then
# we could end up linking this to an existing account which belongs
# to a malicious user hoping that somebody will link their Google account.
verified=True,
)
except EmailAddress.DoesNotExist:
return

sociallogin.connect(request, verified_email.user)
9 changes: 9 additions & 0 deletions ws/static/google.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
47 changes: 45 additions & 2 deletions ws/templates/account/login.html
Original file line number Diff line number Diff line change
@@ -1,15 +1,58 @@
{% extends "base_no_scripts.html" %}
{% load static %}
{% load crispy_forms_tags %}
{% load socialaccount %}

{% block head_title %}Log In{% endblock head_title %}
{% block css %}
{{ block.super }}
{# Google's guidelines require Roboto: https://developers.google.com/identity/branding-guidelines #}
<link href='http://fonts.googleapis.com/css?family=Roboto' rel='stylesheet' type='text/css'>
<style>
button.google-login {
font-family: 'Roboto', sans-serif;
width: 100%;

background-color: #fff;
{# https://commons.wikimedia.org/wiki/File:Google_%22G%22_Logo.svg #}
background-image: url({% static 'google.svg' %});
background-repeat: no-repeat;
background-size: 24px;
background-position: 16px;
font-size: 16px;
margin-top: 16px;
padding: 16px 50px;

position: relative;
text-align: center;
}
</style>
{% endblock css %}

{% block login_button %}{% endblock login_button%}

{% block content %}

<h1>Log In</h1>
<div class="row">
<div class="col-sm-6">
<form novalidate method="POST" action="{% provider_login_url 'google' %}">
{% csrf_token %}
<button class="btn btn-default google-login" type="submit">
Sign in with Google
</button>
</form>

<hr>
</div>
</div>


<h3>Or log in by email</h3>

<p class="lead">
<p>
If you have not created an account yet, then please <a href="{{ signup_url }}">sign up</a> first.
<br>
<small>(We do not automatically create an account after you pay dues or sign a waiver)</small>
</p>

<div class="row">
Expand Down

0 comments on commit c8585bc

Please sign in to comment.