&partial_token=`.
If you are using Django, you can do it with:
```default
from django.core.urlresolvers import reverse
url = strategy.build_absolute_uri(
reverse('social:complete', args=(strategy.backend_name,))
) + '?verification_code=' + code.code + '&partial_token=' + partial_token
```
On Flask:
```default
from flask import url_for
url = url_for('social.complete', backend=strategy.backend_name,
_external=True) + '?verification_code=' + code.code + '&partial_token=' + partial_token
```
For Django, resume email validation through `social:complete` so the normal
pipeline completion selects the login backend. If you implement a separate
account activation view that calls `django.contrib.auth.login()` directly,
follow [Logging in users from custom views](configuration/django.html.md#django-custom-login) when multiple backends are configured.
This pipeline can be used globally with any backend if this setting is defined:
```default
SOCIAL_AUTH_FORCE_EMAIL_VALIDATION = True
```
Or individually by defining the setting per backend basis like
`SOCIAL_AUTH_TWITTER_FORCE_EMAIL_VALIDATION = True`.
# Extending the Pipeline
The pipeline system is designed for extensibility. You can add custom functions to:
* Modify authentication data
* Create or update related model instances
* Request additional user information
* Implement custom authorization logic
* Integrate with external systems
## Steps to Add a Custom Pipeline Function
1. **Write your function** with the appropriate signature
2. **Place it in an importable location** in your project
3. **Add it to the pipeline** in your settings at the appropriate position
#### IMPORTANT
Function placement matters! The order determines what data is available.
For example, placing your function after `create_user` ensures you receive a `user`
instance rather than `None`.
## Writing Custom Pipeline Functions
### Function Signature
Your function should accept the common parameters and `**kwargs`:
```default
def my_pipeline_function(strategy, backend, user=None, **kwargs):
# Your code here
pass
```
#### TIP
Always include `**kwargs` to handle additional parameters from other
pipeline functions or future versions.
See [Common Function Parameters](pipeline.html.md#common-function-parameters) for details on the parameters available to pipeline functions.
## Practical Example: Saving User Profile Data
This example creates a `Profile` instance to store additional user information from Facebook.
**Understanding the Facebook Response**
The `response` parameter from Facebook typically looks like:
```default
{
'username': 'foobar',
'access_token': 'CAAD...',
'first_name': 'Foo',
'last_name': 'Bar',
'verified': True,
'name': 'Foo Bar',
'locale': 'en_US',
'gender': 'male',
'expires': '5183999',
'email': 'foo@bar.com',
'updated_time': '2014-01-14T15:58:35+0000',
'link': 'https://www.facebook.com/foobar',
'timezone': -3,
'id': '100000126636010',
}
```
Let’s say we are interested in storing the user profile link, the gender and
the timezone in our `Profile` model:
```default
def save_profile(backend, user, response, *args, **kwargs):
if backend.name == 'facebook':
profile = user.get_profile()
if profile is None:
profile = Profile(user_id=user.id)
profile.gender = response.get('gender')
profile.link = response.get('link')
profile.timezone = response.get('timezone')
profile.save()
```
Now all that’s needed is to tell `python-social-auth` to use our function in
the pipeline. Since the function uses user instance, we need to put it after
`social_core.pipeline.user.create_user`:
```default
SOCIAL_AUTH_PIPELINE = (
'social_core.pipeline.social_auth.social_details',
'social_core.pipeline.social_auth.social_names',
'social_core.pipeline.social_auth.social_uid',
'social_core.pipeline.social_auth.auth_allowed',
'social_core.pipeline.social_auth.social_user',
'social_core.pipeline.user.get_username',
'social_core.pipeline.user.create_user',
'path.to.save_profile', # <--- set the path to the function
'social_core.pipeline.social_auth.associate_user',
'social_core.pipeline.social_auth.load_extra_data',
'social_core.pipeline.user.user_details',
)
```
**Passing Data Forward**
The function above returns `None`, which is fine if subsequent functions don’t need
the profile. To make the `profile` available to later pipeline functions, return a dict:
```default
def save_profile(backend, user, response, *args, **kwargs):
if backend.name == 'facebook':
profile = user.get_profile()
if profile is None:
profile = Profile(user_id=user.id)
profile.gender = response.get('gender')
profile.link = response.get('link')
profile.timezone = response.get('timezone')
profile.save()
return {'profile': profile} # Make profile available to next functions
```
## Common Patterns and Tips
**Conditional Execution**
Check the backend name to run logic for specific providers:
```default
def my_function(backend, **kwargs):
if backend.name == 'google-oauth2':
# Google-specific logic
pass
elif backend.name == 'facebook':
# Facebook-specific logic
pass
```
**Accessing Settings**
Use the strategy to access settings:
```default
def my_function(strategy, **kwargs):
custom_setting = strategy.setting('MY_CUSTOM_SETTING')
```
**Making API Calls**
Use the access token from `response` to call provider APIs:
```default
def fetch_additional_data(backend, response, **kwargs):
access_token = response.get('access_token')
# Make API call using the token
import requests
api_response = requests.get(
'https://provider-api.com/endpoint',
headers={'Authorization': f'Bearer {access_token}'}
)
return {'additional_data': api_response.json()}
```
**Debugging**
Log pipeline execution to understand the flow:
```default
import logging
logger = logging.getLogger(__name__)
def my_function(user, **kwargs):
logger.debug(f'Pipeline function called for user: {user}')
logger.debug(f'Available kwargs: {kwargs.keys()}')
# Your logic here
```
# security.html.md
# Security considerations
## Stable social account identifiers
Social authentication associations are authorization bindings. Their `uid`
must therefore come from a provider identifier that is immutable and cannot be
reassigned, rather than a display name, email address, UPN, or other
human-readable login name.
Bundled backends use stable provider identifiers where available, including
OIDC `sub` claims, provider account IDs, UUIDs, and verified OpenID identity
URLs. Applications overriding a backend’s `ID_KEY` are responsible for
ensuring the selected claim has the same stability properties. Backends whose
provider responses expose no stable identifier, including Drip, Last.fm, and
Mixcloud, are association-only and require an authenticated local user.
When upgrading an existing deployment, read [the configurable user ID
key documentation](configuration/settings.html.md#configurable-user-id-key) before authenticating users. The
default compatibility migration preserves existing logins but accepts a
one-time first-login race for associations that lack stored stable identity
data. Security-sensitive deployments should disable that fallback and migrate
the affected records administratively.
## Host header validation
The library may use the incoming HTTP `Host` header when generating absolute URLs
or redirects during the authentication and authorization flow. If the `Host`
header is not validated by the deployment stack, it may allow host header
injection attacks.
This is a deployment and configuration concern rather than a defect in the
library itself. The behavior is intentional, as the library needs to construct
absolute URLs for OAuth callbacks and redirects. Proper upstream validation is
required to ensure that only legitimate `Host` header values are accepted by your
application.
## Reverse proxy configuration
When deploying behind a reverse proxy (such as nginx, Apache, HAProxy, or a
cloud load balancer), the proxy must validate the Host header before forwarding
requests to the application.
Key requirements:
* **Validate the Host header**: Only expected hostnames should be forwarded
upstream to the application. Requests with unexpected or malicious Host
values should be rejected by the proxy.
* **Forwarded headers**: If your deployment uses forwarded headers such as
`X-Forwarded-Host` or the standard `Forwarded` header:
* These headers must be accepted **only from trusted proxies**.
* They must **not** be blindly trusted from direct client requests.
* They must be configured explicitly in the proxy configuration.
#### NOTE
Configuration syntax varies across reverse
proxy implementations. Consult your proxy’s documentation for Host header
validation and forwarded header handling.
## Django configuration
When using Python Social Auth with Django, proper Host header validation must
be configured at the application level using Django’s built-in security
features.
Key requirements:
* **Configure ALLOWED_HOSTS**: The `ALLOWED_HOSTS` setting must be explicitly
configured with the canonical hostname(s) for your application. For example:
```default
ALLOWED_HOSTS = ['example.com', 'www.example.com']
```
* **Never use wildcard in production**: The wildcard value `"*"` must not be
used in production environments, as it disables Host header validation
entirely.
* **Host validation behind proxies**: Host validation must remain enabled even
when the application is deployed behind a reverse proxy. Do not disable
`ALLOWED_HOSTS` validation based on the assumption that the proxy will
handle it.
* **Forwarded header settings**: If your deployment uses forwarded headers,
configure Django’s `USE_X_FORWARDED_HOST` setting carefully. This setting
should only be enabled when:
* The application is behind a trusted reverse proxy.
* The proxy is properly configured to set or strip forwarded headers.
* The proxy prevents clients from sending malicious forwarded headers
directly.
For more information on Django security settings, refer to the [Django security
documentation](https://docs.djangoproject.com/en/stable/topics/security/).
# storage.html.md
# Storage
Different frameworks support different ORMs, Storage solves the different
interfaces moving the common API to mixins classes. These mixins are used on
apps when defining the different models used by `python-social-auth`.
## Social User
This model associates a social account data with a user in the system, it
contains the provider name, the user ID (`uid`) which identifies the social
account in the remote provider, and `id_key` naming the provider field from
which that ID was obtained. It also contains JSON-encoded `extra_data` with
additional provider information. Existing rows created before `id_key` was
introduced use an empty string until social-core migrates them.
When implementing this model, it must inherits from [UserMixin](https://github.com/python-social-auth/social-core/blob/master/social_core/storage.py#L21) and extend the
needed methods:
* Username:
```default
@classmethod
def get_username(cls, user):
"""Return the username for given user"""
raise NotImplementedError('Implement in subclass')
@classmethod
def username_max_length(cls):
"""Return the max length for username"""
raise NotImplementedError('Implement in subclass')
```
* User model:
```default
@classmethod
def user_model(cls):
"""Return the user model"""
raise NotImplementedError('Implement in subclass')
@classmethod
def changed(cls, user):
"""The given user instance is ready to be saved"""
raise NotImplementedError('Implement in subclass')
@classmethod
def user_exists(cls, username):
"""
Return True/False if a User instance exists with the given arguments.
Arguments are directly passed to filter() manager method.
"""
raise NotImplementedError('Implement in subclass')
@classmethod
def create_user(cls, username, email=None):
"""Create a user with given username and (optional) email"""
raise NotImplementedError('Implement in subclass')
@classmethod
def get_user(cls, pk):
"""Return user instance for given id"""
raise NotImplementedError('Implement in subclass')
```
* Social user:
```default
@classmethod
def get_social_auth(cls, provider, uid, id_key=None):
"""Return UserSocialAuth for the provider, uid, and optional key"""
raise NotImplementedError('Implement in subclass')
@classmethod
def get_social_auth_by_extra_data(cls, provider, key, value, id_key=''):
"""Return one unambiguous association matching stable provider data"""
raise NotImplementedError('Implement in subclass')
@classmethod
def get_social_auth_for_user(cls, user):
"""Return all the UserSocialAuth instances for given user"""
raise NotImplementedError('Implement in subclass')
@classmethod
def create_social_auth(cls, user, uid, provider, id_key=''):
"""Create a UserSocialAuth instance for given user"""
raise NotImplementedError('Implement in subclass')
@classmethod
def migrate_social_auth(cls, social, uid, id_key):
"""Atomically replace the association identifier and its key"""
raise NotImplementedError('Implement in subclass')
```
Identifier migration must preserve the storage’s provider/UID uniqueness
guarantee, lock the association while updating it, and fail rather than select
an arbitrary row when stored provider data matches multiple associations.
* Social disconnection:
```default
@classmethod
def allowed_to_disconnect(cls, user, backend_name, association_id=None):
"""Return if it's safe to disconnect the social account for the
given user"""
raise NotImplementedError('Implement in subclass')
@classmethod
def disconnect(cls, name, user, association_id=None):
"""Disconnect the social account for the given user"""
raise NotImplementedError('Implement in subclass')
```
## Nonce
This is a helper class for OpenID mechanism, it stores a one-use number,
shouldn’t be used by the project since it’s for internal use only.
When implementing this model, it must inherit from [NonceMixin](https://github.com/python-social-auth/social-core/blob/master/social_core/storage.py#L166), and override
the needed methods:
```default
@classmethod
def use(cls, server_url, timestamp, salt):
"""Create a Nonce instance"""
raise NotImplementedError('Implement in subclass')
@classmethod
def get(cls, server_url, salt):
"""Retrieve a Nonce instance"""
raise NotImplementedError('Implement in subclass')
@classmethod
def delete(cls, nonce):
"""Delete a Nonce instance"""
raise NotImplementedError('Implement in subclass')
```
## Association
This internal model stores OpenID protocol associations and temporary OpenID
Connect login nonces. It is separate from the model linking a user to a provider.
For OIDC, `handle` stores the nonce, `secret` is empty, and `assoc_type`
stores OAuth state. `issued` is the integer Unix creation timestamp and
`lifetime` is the duration in seconds.
`AssociationMixin.is_expired(now=None)` returns whether the lifetime has
elapsed, including the exact expiry boundary. Nonpositive lifetimes are expired.
OIDC validation removes matching expired records and rejects the login.
Storage integrations must preserve `issued` and `lifetime`. During upgrade,
existing OIDC nonce records with an empty secret and both values zero can receive
the upgrade timestamp and a 1,800-second grace period. Other unbounded records
are rejected. Stop old login-serving processes before this conversion so they
do not create additional undated nonces afterward.
Django provides `Association.cleanup_expired(now=None)`, which deletes expired
OpenID associations and OIDC nonces and returns the deleted record count.
`now` is an optional integer Unix timestamp; it defaults to the current time.
Other integrations should implement scheduled cleanup using the same expiry
rule. OpenID’s existing on-demand cleanup only visits associations for the
provider being queried.
When implementing this model, it must inherits from [AssociationMixin](https://github.com/python-social-auth/social-core/blob/master/social_core/storage.py#L178), and
override the needed methods:
```default
@classmethod
def store(cls, server_url, association):
"""Create an Association instance"""
raise NotImplementedError('Implement in subclass')
@classmethod
def get(cls, *args, **kwargs):
"""Get an Association instance"""
raise NotImplementedError('Implement in subclass')
@classmethod
def remove(cls, ids_to_delete):
"""Remove an Association instance"""
raise NotImplementedError('Implement in subclass')
```
## Validation code
This class is used to keep track of email validations codes following the usual
email validation mechanism of sending an email to the user with a unique code.
This model is used by the partial pipeline `social_core.pipeline.mail.mail_validation`.
Check the docs at *Email validation* in [pipeline docs](pipeline.html#email-validation).
The model must inherit from `CodeMixin` and persist the `email`, `code`,
`verified`, and `timestamp` attributes. `make_code()` initializes
`timestamp` with the creation time in UTC. A framework-managed creation
timestamp can also be used. Naive timestamps are interpreted as UTC by the
default `is_expired(seconds)` implementation; override this method if your
storage uses another timezone. The Django integration interprets naive
timestamps in Django’s configured timezone.
Codes without a timestamp are rejected when expiry is enabled. Existing storage
integrations must add timestamp persistence and migrate existing data, or have
users request new codes. Do not assign a new creation time to old codes.
The storage lookup method must also be overridden:
```default
@classmethod
def get_code(cls, code):
"""Return the Code instance with the given code value"""
raise NotImplementedError('Implement in subclass')
```
## Storage interface
There’s a helper class used by strategies to hide the real models names under
a common API, an instance of this class is used by strategies to access the
storage modules.
When implementing this class it must inherits from [BaseStorage](https://github.com/python-social-auth/social-core/blob/master/social_core/storage.py#L248), add the needed
models references and implement the needed method:
```default
class StorageImplementation(BaseStorage):
user = UserModel
nonce = NonceModel
association = AssociationModel
code = CodeModel
@classmethod
def is_integrity_error(cls, exception):
"""Check if given exception flags an integrity error in the DB"""
raise NotImplementedError('Implement in subclass')
```
## SQLAlchemy and Django mixins
Currently there are partial implementations of mixins for [SQLAlchemy ORM](https://github.com/python-social-auth/social-storage-sqlalchemy/blob/master/social_sqlalchemy/storage.py) and
[Django ORM](https://github.com/python-social-auth/social-app-django/blob/master/social_django/storage.py) with common code used later on current implemented applications.
#### NOTE
When using [SQLAlchemy ORM](https://github.com/python-social-auth/social-storage-sqlalchemy/blob/master/social_sqlalchemy/storage.py) and `ZopeTransactionExtension`, it’s
recommended to use the [transaction](https://pypi.python.org/pypi/transaction) application to handle them.
## Models Examples
Check for current implementations for [Django App](https://github.com/python-social-auth/social-app-django/blob/master/social_django/models.py), [Flask App](https://github.com/python-social-auth/social-app-flask/blob/master/social_flask/models.py), [Pyramid
App](https://github.com/python-social-auth/social-app-pyramid/blob/master/social_pyramid/models.py), and [Webpy App](https://github.com/python-social-auth/social-app-webpy/blob/master/social_webpy/models.py) for examples of implementations.
# strategies.html.md
# Strategies
Different strategies are defined to encapsulate the different frameworks
capabilities under a common API to reuse as much code as possible.
## Description
A strategy’s responsibility is to provide access to:
* Request data and host information and URI building
* Session access
* Project settings
* Response types (HTML and redirects)
* HTML rendering
Different frameworks implement these features on different ways, thus the need
for these interfaces.
## Implementing a new Strategy
The following methods must be defined on strategies sub-classes.
Request:
```default
def get_request_data(self, merge=True):
"""Return current framework request data (POST or GET)"""
raise NotImplementedError('Implement in subclass')
def request_host(self):
"""Return current host value"""
raise NotImplementedError('Implement in subclass')
def build_absolute_uri(self, path=None):
"""Build absolute URI with given (optional) path"""
raise NotImplementedError('Implement in subclass')
```
Session:
```default
def session_get(self, name):
"""Return session value for given key"""
raise NotImplementedError('Implement in subclass')
def session_set(self, name, value):
"""Set session value for given key"""
raise NotImplementedError('Implement in subclass')
def session_pop(self, name):
"""Pop session value for given key"""
raise NotImplementedError('Implement in subclass')
```
Settings:
```default
def get_setting(self, name):
"""Return value for given setting name"""
raise NotImplementedError('Implement in subclass')
```
Responses:
```default
def html(self, content):
"""Return HTTP response with given content"""
raise NotImplementedError('Implement in subclass')
def redirect(self, url):
"""Return a response redirect to the given URL"""
raise NotImplementedError('Implement in subclass')
def render_html(self, tpl=None, html=None, context=None):
"""Render given template or raw html with given context"""
raise NotImplementedError('Implement in subclass')
```
## Effective pipeline request data
`strategy.request` holds the native framework request, when the integration
provides one. It is never replaced by saved partial-pipeline parameters.
`strategy.get_request_data(merge=True)` reads the current framework request.
With `merge=False`, it reads the data for the current request method, following
the framework integration’s existing behavior. Custom strategies implement this
hook instead of overriding `request_data()`.
`strategy.request_data(merge=True)` returns the effective parameters for the
active pipeline. During a partial resume, this includes any confirmed data
replayed from an external validation link. Both values of `merge` return the
same effective mapping during replay, because saved data no longer has a
separate GET or POST origin. Use `get_request_data()`, `request_get()`, or
`request_post()` when the current framework request is required.
The effective mapping is scoped to execution of the resumed authentication or
disconnect pipeline. The previous mapping is restored when execution completes,
returns a response, or raises an exception. Nested executions restore the outer
mapping. Integration code can establish the same scope with
`strategy.pipeline_request_data(data)` as a context manager.
## Migrating to social-auth-core 6 and social-auth-app-django 7
* Rename custom `request_data()` overrides to `get_request_data()`. Keep
framework extraction, validation, and application-specific defaults in that
hook; allow the inherited `request_data()` method to handle replay.
* Replace pipeline `request` parameters and `kwargs['request']` reads with
`strategy.request_data()` for effective parameters, or `strategy.request`
for a native framework request. Pipeline steps no longer receive an automatic
`request` argument.
* Upgrade framework adapters together with social-auth-core. Adapters that
override the old hook must migrate before using core 6.
Partial request data is stored separately from pipeline arguments. Existing
partials with a request mapping in `kwargs['request']` are converted when
loaded, so upgrading does not require a database schema migration or discarding
pending authentication flows.
Partials are bound to their authentication or disconnect pipeline. Legacy
disconnect partials without a pipeline type must restart the disconnect flow
so its permission checks run.
# tests.html.md
# Testing python-social-auth
Testing the application is fairly simple, just met the dependencies and run the
testing suite.
The testing suite uses [HTTPretty](https://github.com/gabrielfalcao/HTTPretty) to mock server responses, it’s not a live
test against the providers API, to do it that way, a browser and a tool like
Selenium are needed, that’s slow, prone to errors on some cases, and some of
the application examples must be running to perform the testing. Plus real Key
and Secret pairs, in the end it’s a mess to test functionality which is the
real point.
By mocking the server responses, we can test the backends functionality (and
other areas too) easily and quick.
## Installing dependencies
Go to the [tests](https://github.com/python-social-auth/social-core/tree/master/social_core/tests) directory and install the dependencies listed in the
[requirements.txt](https://github.com/python-social-auth/social-core/blob/master/social_core/tests/requirements.txt). Then run with `nosetests` command, or with the
`run_tests.sh` script.
## Tox
You can use [tox](http://tox.readthedocs.org/) to test compatibility against all supported Python versions:
```bash
$ pip install tox # if not present
$ tox
```
## Pending
At the moment only OAuth1, OAuth2 and OpenID backends are being tested, and
just login and partial pipeline features are covered by the test. There’s still
a lot to work on, like:
* Frameworks support
# thanks.html.md
# Thanks
[python-social-auth](https://github.com/python-social-auth) is the result of almost 3 years of development done on
[django-social-auth](https://github.com/omab/django-social-auth) which is the result of my initial work and the thousands
lines of code contributed by so many developers that took time to work on
improvements, report bugs and hunt them down to propose a fix. So, here is
a big list of users that helped to build this library (if somebody is missed
let me know and I’ll update the list):
* [kjoconnor](https://github.com/kjoconnor)
* [krvss](https://github.com/krvss)
* [estebistec](https://github.com/estebistec)
* [mrmch](https://github.com/mrmch)
* [uruz](https://github.com/uruz)
* [maraujop](https://github.com/maraujop)
* [bacher09](https://github.com/bacher09)
* [dokterbob](https://github.com/dokterbob)
* [hassek](https://github.com/hassek)
* [andrusha](https://github.com/andrusha)
* [vicalloy](https://github.com/vicalloy)
* [caioariede](https://github.com/caioariede)
* [danielgtaylor](https://github.com/danielgtaylor)
* [stephenmcd](https://github.com/stephenmcd)
* [gugu](https://github.com/gugu)
* [yrik](https://github.com/yrik)
* [dhendo](https://github.com/dhendo)
* [yekibud](https://github.com/yekibud)
* [tmackenzie](https://github.com/tmackenzie)
* [LuanP](https://github.com/LuanP)
* [jezdez](https://github.com/jezdez)
* [serdardalgic](https://github.com/serdardalgic)
* [Jolmberg](https://github.com/Jolmberg)
* [ChrisCooper](https://github.com/ChrisCooper)
* [marselester](https://github.com/marselester)
* [eshellman](https://github.com/eshellman)
* [micrypt](https://github.com/micrypt)
* [revolunet](https://github.com/revolunet)
* [dasevilla](https://github.com/dasevilla)
* [seansay](https://github.com/seansay)
* [hepochen](https://github.com/hepochen)
* [gibuloto](https://github.com/gibuloto)
* [crodjer](https://github.com/crodjer)
* [sidmitra](https://github.com/sidmitra)
* [ryr](https://github.com/ryr)
* [inve1](https://github.com/inve1)
* [mback2k](https://github.com/mback2k)
* [hannesstruss](https://github.com/hannesstruss)
* [NorthIsUp](https://github.com/NorthIsUp)
* [tonyxiao](https://github.com/tonyxiao)
* [dhepper](https://github.com/dhepper)
* [Troytft](https://github.com/Troytft)
* [gardaud](https://github.com/gardaud)
* [oinopion](https://github.com/oinopion)
* [gameguy43](https://github.com/gameguy43)
* [vinigracindo](https://github.com/vinigracindo)
* [syabro](https://github.com/syabro)
* [bashmish](https://github.com/bashmish)
* [ggreer](https://github.com/ggreer)
* [avillavi](https://github.com/avillavi)
* [r4vi](https://github.com/r4vi)
* [roderyc](https://github.com/roderyc)
* [daonb](https://github.com/daonb)
* [slon7](https://github.com/slon7)
* [JasonGiedymin](https://github.com/JasonGiedymin)
* [tymofij](https://github.com/tymofij)
* [Cassus](https://github.com/Cassus)
* [martey](https://github.com/martey)
* [t0m](https://github.com/t0m)
* [johnthedebs](https://github.com/johnthedebs)
* [jammons](https://github.com/jammons)
* [stefanw](https://github.com/stefanw)
* [maxgrosse](https://github.com/maxgrosse)
* [mattucf](https://github.com/mattucf)
* [tadeo](https://github.com/tadeo)
* [haxoza](https://github.com/haxoza)
* [bradbeattie](https://github.com/bradbeattie)
* [henward0](https://github.com/henward0)
* [bernardokyotoku](https://github.com/bernardokyotoku)
* [czpython](https://github.com/czpython)
* [glasscube42](https://github.com/glasscube42)
* [assiotis](https://github.com/assiotis)
* [dbaxa](https://github.com/dbaxa)
* [JasonSanford](https://github.com/JasonSanford)
* [originell](https://github.com/originell)
* [cihann](https://github.com/cihann)
* [niftynei](https://github.com/niftynei)
* [mikesun](https://github.com/mikesun)
* [1st](https://github.com/1st)
* [betonimig](https://github.com/betonimig)
* [ozexpert](https://github.com/ozexpert)
* [stephenLee](https://github.com/stephenLee)
* [julianvargasalvarez](https://github.com/julianvargasalvarez)
* [youngrok](https://github.com/youngrok)
* [garrypolley](https://github.com/garrypolley)
* [amirouche](https://github.com/amirouche)
* [fmoga](https://github.com/fmoga)
* [pydanny](https://github.com/pydanny)
* [pygeek](https://github.com/pygeek)
* [dgouldin](https://github.com/dgouldin)
* [kotslon](https://github.com/kotslon)
* [kirkchris](https://github.com/kirkchris)
* [barracel](https://github.com/barracel)
* [sayar](https://github.com/sayar)
* [kulbir](https://github.com/kulbir)
* [Morgul](https://github.com/Morgul)
* [spstpl](https://github.com/spstpl)
* [bluszcz](https://github.com/bluszcz)
* [vbsteven](https://github.com/vbsteven)
* [sbassi](https://github.com/sbassi)
* [aspcanada](https://github.com/aspcanada)
* [browniebroke](https://github.com/browniebroke)
* [eshaan7](https://github.com/eshaan7)
* [amitray007](https://github.com/amitray007)
# use_cases.html.md
# Use Cases
Some miscellaneous options and use cases for [python-social-auth](https://github.com/python-social-auth).
## Return the user to the original page
There’s a common scenario to return the user back to the original page from
where they requested to login. For that purpose, the usual `next` argument is
used. The value of this parameter will be stored in the session and later used
to redirect the user when login was successful.
In order to use it, just define it with your login request. For instance, when
using Django:
```default
```
## Pass custom GET/POST parameters and retrieve them on authentication
In some cases, you might need to send data with the login request, and retrieve
it while processing the after-effect. For example, for conditionally executing
code in custom pipelines.
In such cases, add it to `SOCIAL_AUTH_FIELDS_STORED_IN_SESSION`.
In your settings:
```default
SOCIAL_AUTH_FIELDS_STORED_IN_SESSION = ['key']
```
In a Django template:
```default
```
In your custom pipeline, retrieve it using:
```default
strategy.session_get('key')
```
## Associate users by email
Sometimes it’s desirable that social accounts are automatically associated if
the email already matches a user account.
For example, if a user signed up with their Facebook account, then logged out and
next time tries to use Google OAuth2 to login, it could be nice (if both social
sites have the same email address configured) that the user gets into their
initial account created by Facebook backend.
This scenario is possible by enabling the `associate_by_email` pipeline
function, like this:
```default
SOCIAL_AUTH_PIPELINE = (
'social_core.pipeline.social_auth.social_details',
'social_core.pipeline.social_auth.social_names',
'social_core.pipeline.social_auth.social_uid',
'social_core.pipeline.social_auth.auth_allowed',
'social_core.pipeline.social_auth.social_user',
'social_core.pipeline.user.get_username',
'social_core.pipeline.social_auth.associate_by_email', # <--- enable this one
'social_core.pipeline.user.create_user',
'social_core.pipeline.social_auth.associate_user',
'social_core.pipeline.social_auth.load_extra_data',
'social_core.pipeline.user.user_details',
)
```
This feature is disabled by default because it’s not 100% secure to automate
this process with all the backends. Not all the providers will validate your
email account and others users could take advantage of that.
Take for instance User A registered in your site with the email
`foo@bar.com`. Then a malicious user registers into another provider that
doesn’t validate their email with that same account. Finally this user will turn
to your site (which supports that provider) and sign up to it, since the email
is the same, the malicious user will take control over the User A account.
## Signup by OAuth access_token
It’s a common scenario that mobile applications will use an SDK to signup
a user within the app, but that signup won’t be reflected by
[python-social-auth](https://github.com/python-social-auth) unless the corresponding database entries are created. In
order to do so, it’s possible to create a view / route that creates those
entries by a given `access_token`. Take the following code for instance (the
code follows Django conventions, but versions for others frameworks can be
implemented easily):
```default
from django.contrib.auth import login
from social_django.utils import psa
# Define an URL entry to point to this view, call it passing the
# access_token parameter like ?access_token=. The URL entry must
# contain the backend, like this:
#
# path('register-by-token//',
# register_by_access_token,
# name='register_by_access_token')
@psa('social:complete')
def register_by_access_token(request, backend):
# This view expects an access_token GET parameter, if it's needed,
# request.backend and request.strategy will be loaded with the current
# backend and strategy.
token = request.GET.get('access_token')
user = request.backend.do_auth(token)
if user:
login(request, user)
return 'OK'
else:
return 'ERROR'
```
The snippet above is quite simple, it doesn’t return JSON and usually this call
will be done by AJAX. It doesn’t return the user information, but that’s
something that can be extended and filled to suit the project where it’s going
to be used.
**Note**: when dealing with `OAuth1`, the `access_token` is
: actually a query-string composed by `oauth_token` and
`oauth_token_secret`, [python-social-auth](https://github.com/python-social-auth) expects this to be a
`dict` with those keys, but if an string is detected, it will treat
it as a query string in the form `oauth_token=123&oauth_token_secret=456`.
## Multiple scopes per provider
At the moment [python-social-auth](https://github.com/python-social-auth) doesn’t provide a method to define multiple
scopes for single backend, this is usually desired since it’s recommended to
ask the user for the minimum scope possible and increase the access when it’s
really needed. It’s possible to add a new backend extending the original one to
accomplish that behavior. There are two ways to do it.
1. Overriding `get_scope()` method:
```default
from social_core.backends.facebook import FacebookOAuth2
class CustomFacebookOAuth2(FacebookOauth2):
def get_scope(self):
scope = super(CustomFacebookOAuth2, self).get_scope()
if self.data.get('extrascope'):
scope = scope + [('foo', 'bar')]
return scope
```
This method is quite simple, it overrides the method that returns the scope
value in a backend (`get_scope()`) and adds extra values to the list if it
was indicated by a parameter in the `GET` or `POST` data
(`self.data`).
Put this new backend in some place in your project and replace the original
`FacebookOAuth2` in `AUTHENTICATION_BACKENDS` with this new version.
When overriding this method, take into account that the default output the
base class for `get_scope()` is the raw value from the settings (whatever
they are defined), doing this will actually update the value in your
settings for all the users:
```default
scope = super(CustomFacebookOAuth2, self).get_scope()
scope += ['foo', 'bar']
```
Instead do it like this:
```default
scope = super(CustomFacebookOAuth2, self).get_scope()
scope = scope + ['foo', 'bar']
```
2. It’s possible to do the same by defining a second backend which extends from
the original but overrides the name, this will imply new URLs and also new
settings for the new backend (since the name is used to build the settings
names), it also implies a new application in the provider since not all
providers give you the option of defining multiple redirect URLs. To do it
just add a backend like:
```default
from social_core.backends.facebook import FacebookOAuth2
class CustomFacebookOAuth2(FacebookOauth2):
name = 'facebook-custom'
```
Put this new backend in some place in your project keeping the original
`FacebookOAuth2` in `AUTHENTICATION_BACKENDS`. Now a new set of URLs
will be functional:
```default
/login/facebook-custom
/complete/facebook-custom
/disconnect/facebook-custom
```
And also a new set of settings:
```default
SOCIAL_AUTH_FACEBOOK_CUSTOM_KEY = '...'
SOCIAL_AUTH_FACEBOOK_CUSTOM_SECRET = '...'
SOCIAL_AUTH_FACEBOOK_CUSTOM_SCOPE = [...]
```
When the extra permissions are needed, start authentication with the new
backend and then get the social auth entry for it with
`user.social_auth.get(provider='facebook-custom')` and use the
`access_token` in it. In Django templates, start the flow with a POST
form:
```default
```
## Enable a user to choose a username from their World of Warcraft characters
If you want to register new users on your site via battle.net, you can enable
these users to choose a username from their own World-of-Warcraft characters.
To do this, use the `battlenet-oauth2` backend along with a small form to
choose the username.
The form is rendered via a partial pipeline item like this:
```default
@partial
def pick_character_name(backend, details, response, is_new=False, *args, **kwargs):
if backend.name == 'battlenet-oauth2' and is_new:
data = backend.strategy.request_data()
if data.get('character_name') is None:
# New user and didn't pick a character name yet, so we render
# and send a form to pick one. The form must do a POST/GET
# request to the same URL (/complete/battlenet-oauth2/). In this
# example we expect the user option under the key:
# character_name
# you have to filter the result list according to your needs.
# In this example, only guild members are allowed to sign up.
char_list = [
c['name'] for c in backend.get_characters(response.get('access_token'))
if 'guild' in c and c['guild'] == ''
]
return render_to_response('pick_character_form.html', {'charlist': char_list, })
else:
# The user selected a character name
return {'username': data.get('character_name')}
```
Don’t forget to add the partial to the pipeline:
```default
SOCIAL_AUTH_PIPELINE = (
'social_core.pipeline.social_auth.social_details',
'social_core.pipeline.social_auth.social_names',
'social_core.pipeline.social_auth.social_uid',
'social_core.pipeline.social_auth.auth_allowed',
'social_core.pipeline.social_auth.social_user',
'social_core.pipeline.user.get_username',
'path.to.pick_character_name',
'social_core.pipeline.user.create_user',
'social_core.pipeline.social_auth.associate_user',
'social_core.pipeline.social_auth.load_extra_data',
'social_core.pipeline.user.user_details',
)
```
It needs to be somewhere before create_user because the partial will change the
username according to the users choice.
## Re-prompt Google OAuth2 users to refresh the `refresh_token`
A `refresh_token` also expire, a `refresh_token` can be lost, but they can
also be refreshed (or re-fetched) if you ask to Google the right way. In order
to do so, set this setting:
```default
SOCIAL_AUTH_GOOGLE_OAUTH2_AUTH_EXTRA_ARGUMENTS = {
'access_type': 'offline',
'approval_prompt': 'auto'
}
```
Then show users a form that starts Google OAuth2 with
`approval_prompt=force`. In Django templates, submit the value in the POST
body:
```default
```
If you want to refresh the `refresh_token` only on those users that don’t
have it, redirect them to a page that renders the form above with a pipeline
function:
```default
def redirect_if_no_refresh_token(backend, response, social, *args, **kwargs):
if backend.name == 'google-oauth2' and social and \
response.get('refresh_token') is None and \
social.extra_data.get('refresh_token') is None:
return redirect('/refresh-google-access/')
```
Set this pipeline after `social_user`:
```default
SOCIAL_AUTH_PIPELINE = (
'social_core.pipeline.social_auth.social_details',
'social_core.pipeline.social_auth.social_names',
'social_core.pipeline.social_auth.social_uid',
'social_core.pipeline.social_auth.auth_allowed',
'social_core.pipeline.social_auth.social_user',
'path.to.redirect_if_no_refresh_token',
'social_core.pipeline.user.get_username',
'social_core.pipeline.user.create_user',
'social_core.pipeline.social_auth.associate_user',
'social_core.pipeline.social_auth.load_extra_data',
'social_core.pipeline.user.user_details',
)
```
## Improve unicode cleanup from usernames
It’s possible to improve the username cleanup by using an external library like
[Unidecode](https://pypi.org/project/Unidecode/) or [Text-Unicode](https://github.com/kmike/text-unidecode/). You can integrate these by using the
SOCIAL_AUTH_CLEAN_USERNAME_FUNCTION documented at [Username Generation](configuration/settings.html#username-generation)
section. For instance, this will do the work:
```default
SOCIAL_AUTH_CLEAN_USERNAME_FUNCTION = 'unidecode.unidecode'
```