Social Authentication
flowchart LR
a(potential signup) --button with github icon is clicked--> z(AUTH_USER_MODEL populated)
Overview
Social auth is also built-in, courtesty of django-allauth.
For instance, users who choose Github login to the local address 127.0.0.1
will be prompted with a Github consent form.
The callback function, generated in Github, will allow Github users to be authenticated without having to submit a username and password.
Settings
This boilerplate also adopts the following allauth settings with respect social authentication providers:
SOCIALACCOUNT_EMAIL_VERIFICATION = "none" # (1)
SOCIALACCOUNT_AUTO_SIGNUP = False # (2)
SOCIALACCOUNT_PROVIDERS = { # (3)
"google": {
"APP": {
"client_id": env("GOOGLE_ID", None),
"secret": env("GOOGLE_KEY", None),
}
},
"github": {
"APP": {
"client_id": env("GITHUB_ID", None),
"secret": env("GITHUB_KEY", None),
"key": "",
}
},
}
- Prevent email verification if auth is by social
- Although email verifification is not required, can prevent bypass and still require the user to consent to terms / conditions before signing up.
- I've already preconfigured Github and Google to use a local address
127.0.0.1
with my credentials but see the section on Contexts.
To add a social provider to each live Django site, I'll need to do a few things:
- The desired social provider has been added to both
SOCIALACCOUNT_PROVIDERS
andINSTALLED_APPS
. - Environment variables containing specific credentials match the requesting environment's user request.
- The
callback url
based on allauth template is working.
In order to get these credentials, an application must be made in the provider's website. See specific details for:
Context
context | url | purpose | reusable | ENV_NAME |
---|---|---|---|---|
local | http://127.0.0.1 :8000 |
does it work? | yes | dev |
container | http://0.0.0.0 :8080 |
does it work? | yes | test |
start-django | https://start-django.fly.dev (or http:// ) |
limited, ideally for secured (https ) use only by designated url |
no | prod |
Reusability
In each of these environments, I'll need to secure key-value pairs to authenticate via Github, Google, Facebook, Apple, Telegram or whatever service that employs oAuth.
Note the first two environments will be reusable for whatever website I'll develop with Django (or whatever framework, in whatever language, really) since this is really just my machine accessing the service using the declared urls.
The third environment's url will only be useful in https://start-django.fly.dev.
Issuance
django-allauth instructs:
Most providers require you to sign up for a so called API client or app, containing a client ID and API secret. You must add a SocialApp record per provider via the Django admin containing these app credentials.
I like to add the record via .env
so that I can programatically reuse them rather than having to manually access the Django admin, adding a record per provider.
Before I'm able to use this record however, I need to get issued a key-value pair and each provider has its own process of issuing secrets:
Storage
Once a provider issues a key-value pair in the form of:
<service_name>_id=the-id-supplied-by-service
<service_name>_secret=the-secret-issued-based-on-id-above
... I should be able to store the pair for use but this carries inherent risks.
Let's say the future social media giant Mastodon decides to become an oAuth Provider and is able to give me an id
and secret
for my website so I can create a Login with Mastodon button.
How do I go about storing these fields for future use? The .env
file in the root directory would be the most likely storage file.
This "plain-text method", copy/pasting secrets into .env
files, is a de facto practice absent a "best practice" system where developers can specify snippets for their apps / databases / websites from a secure location.
environment | mastodon_id |
mastodon_secret |
---|---|---|
local | id-supplied-for-127.0.0.1 |
secret-id-of-127.0.0.1 |
container | id-supplied-for-0.0.0.0 |
secret-id-of-0.0.0.0 |
site | id-supplied-for-start-django.fly.dev |
secret-id-of-start-django.fly.dev |
It's simpler to run a container with an .env
file populated with plain-text:
This compose.sq.yml should
make reference to the env_file
key:
I'm a 1Password subscriber and I like that I'm able to use secret references as an add-on to their main service. So what I store in my .env file is not the token itself but rather a reference to the token:
environment | mastodon_id |
mastodon_secret |
---|---|---|
local | op://dev/auth-local /mastodon_client/id |
op://dev/auth-local /mastodon_client/secret |
container | op://dev/auth-container /mastodon_client/id |
op://dev/auth-container /mastodon_client/secret |
start-django | op://dev/<site> /mastodon_client/id |
op://dev/<site> /mastodon_client/secret |
Where are the tokens actually stored? In the 1Password client residing in the device. So when I run a management command requiring the use of these secrets, I'd do so via:
There is more complexity involved, certainly, but with the upside of guarding against:
- storing plain-text secrets residing in my machine
- accidentally committing these to the repository, and
- risks attendant in either of these cases.
Unfortunately, using op run
to run a container doesn't work:
It's necessary to employ an "injection" mechanism using a template file: