Pipeline¶
python-social-auth uses an extendible pipeline mechanism where developers can introduce their functions during the authentication, association and disconnection flows.
Pipeline Overview¶
The pipeline is a sequence of functions that are executed in order during the authentication process. Each function receives data from the previous steps and can pass data to the next steps.
Key Concepts:
Pipeline functions are called sequentially in the order they are defined
Each function receives arguments from the authentication process and previous pipeline steps
Functions can pass data forward by returning a dictionary
Functions can interrupt the flow by returning an HTTP response (e.g., redirect)
All functions should accept
**kwargsto handle unexpected arguments gracefully
Understanding Return Values¶
Pipeline functions can return three types of values, each with different behavior:
Return
Noneor nothing: The pipeline continues to the next function. This is equivalent to returning an empty dict{}.Return a
dict: The values in the dictionary are merged into thekwargsfor all subsequent pipeline functions. This is how you pass data forward in the pipeline.Example:
def my_pipeline_function(backend, user, **kwargs): # Calculate something custom_value = "some data" # Pass it to next functions return {'custom_value': custom_value}
Return any other value (HTTP response, redirect, etc.): The pipeline is interrupted and the value is returned directly to the client. This is useful for partial pipelines where you need user input.
Example:
def my_pipeline_function(backend, user, **kwargs): if some_condition: # Interrupt pipeline and redirect user return redirect('/some-form/')
Common Function Parameters¶
The functions will receive a variable set of arguments related to the current process. Common arguments include:
strategy- The current strategy instance (provides access to storage, settings, and request)backend- The current backend instance (the social authentication provider)user- The user instance (Noneif not yet created or retrieved)request- The current HTTP request objectsocial- TheUserSocialAuthinstance (Noneuntil created)uid- The unique user ID from the providerresponse- The raw response from the authentication providerdetails- Processed user details (username, email, etc.)is_new- Boolean indicating if a user was just createdAny values returned as dicts by previous pipeline functions
Important: Always include **kwargs in your function signature to handle additional
arguments that may be passed from other pipeline functions or future versions:
def my_custom_pipeline(strategy, backend, user, **kwargs):
# Your code here
pass
Authentication Pipeline¶
The authentication workflow is handled by a pipeline where custom functions can be added or default items can be removed to provide custom behavior. The default pipeline creates user instances and gathers basic data from providers.
Understanding the Default Pipeline¶
The pipeline executes in order, with each step depending on data from previous steps. Here’s what happens at each stage:
The default pipeline is composed by:
(
# Get the information we can about the user and return it in a simple
# format to create the user instance later. In some cases the details are
# already part of the auth response from the provider, but sometimes this
# could hit a provider API.
'social_core.pipeline.social_auth.social_details',
# Get the social uid from whichever service we're authing thru. The uid is
# the unique identifier of the given user in the provider.
'social_core.pipeline.social_auth.social_uid',
# Verifies that the current auth process is valid within the current
# project, this is where emails and domains whitelists are applied (if
# defined).
'social_core.pipeline.social_auth.auth_allowed',
# Checks if the current social-account is already associated in the site.
'social_core.pipeline.social_auth.social_user',
# Make up a username for this person, appends a random string at the end if
# there's any collision.
'social_core.pipeline.user.get_username',
# Send a validation email to the user to verify its email address.
# Disabled by default.
# 'social_core.pipeline.mail.mail_validation',
# Associates the current social details with another user account with
# a similar email address. Disabled by default.
# 'social_core.pipeline.social_auth.associate_by_email',
# Create a user account if we haven't found one yet.
'social_core.pipeline.user.create_user',
# Create the record that associates the social account with the user.
'social_core.pipeline.social_auth.associate_user',
# Populate the extra_data field in the social record with the values
# specified by settings (and the default ones like access_token, etc).
'social_core.pipeline.social_auth.load_extra_data',
# Update the user record with any changed info from the auth service.
'social_core.pipeline.user.user_details',
)
What Data is Available When?
Understanding which data is available at each stage is crucial for placing your custom functions correctly:
After
social_details:detailsdict is populated with user infoAfter
social_uid:uidcontains the provider’s user IDAfter
social_user:socialmay contain the UserSocialAuth instance (if user was previously authenticated)After
get_username:usernameis availableAfter
create_user:usercontains the User instance (new or existing)After
associate_user:socialcontains the UserSocialAuth instanceAfter
load_extra_data:social.extra_datacontains access tokens and additional provider data
Customizing the Pipeline¶
You can override the default pipeline by defining the setting SOCIAL_AUTH_PIPELINE.
Example 1: Preventing New User Creation
A pipeline that won’t create users, just accepts already registered ones would look like this:
SOCIAL_AUTH_PIPELINE = (
'social_core.pipeline.social_auth.social_details',
'social_core.pipeline.social_auth.social_uid',
'social_core.pipeline.social_auth.auth_allowed',
'social_core.pipeline.social_auth.social_user',
'social_core.pipeline.social_auth.associate_user',
'social_core.pipeline.social_auth.load_extra_data',
'social_core.pipeline.user.user_details',
)
Note
This example removes get_username and create_user steps, so only
users who have previously authenticated can log in.
Example 2: Custom User Loading
When authentication is purely external, you need a custom pipeline function that
populates the user key. This function should load or identify the user before
the social_user step:
SOCIAL_AUTH_PIPELINE = (
'social_core.pipeline.social_auth.social_details',
'social_core.pipeline.social_auth.social_uid',
'social_core.pipeline.social_auth.auth_allowed',
'myapp.pipeline.load_user', # Custom function to load the user
'social_core.pipeline.social_auth.social_user',
'social_core.pipeline.social_auth.associate_user',
'social_core.pipeline.social_auth.load_extra_data',
'social_core.pipeline.user.user_details',
)
Your load_user function might look like:
def load_user(strategy, backend, uid, user=None, **kwargs):
if user:
return {'user': user}
# Load user from your custom authentication system
user = MyUserModel.get_by_external_id(uid)
return {'user': user}
Per-Backend Pipelines¶
It is also possible to define pipelines on a per backend basis by defining a setting
such as SOCIAL_AUTH_TWITTER_PIPELINE. Backend-specific pipelines will override
the default and SOCIAL_AUTH_PIPELINE settings.
Disconnection Pipeline¶
Like the authentication pipeline, it’s possible to define a disconnection pipeline if needed.
For example, this can be useful on sites where a user that disconnects all the
related social account is required to fill a password to ensure the
authentication process in the future. This can be accomplished by overriding
the default disconnection pipeline and setup a function that checks if the user
has a password, in case it doesn’t a redirect to a fill-your-password form can
be returned and later continue the disconnection process, take into account
that disconnection ensures the POST method by default, a simple method to
ensure this, is to make your form POST to /disconnect/ and set the needed
password in your pipeline function. Check Partial Pipeline below.
In order to override the disconnection pipeline, just define the setting:
SOCIAL_AUTH_DISCONNECT_PIPELINE = (
# Verifies that the social association can be disconnected from the current
# user (ensure that the user login mechanism is not compromised by this
# disconnection).
'social_core.pipeline.disconnect.allowed_to_disconnect',
# Collects the social associations to disconnect.
'social_core.pipeline.disconnect.get_entries',
# Revoke any access_token when possible.
'social_core.pipeline.disconnect.revoke_tokens',
# Removes the social associations.
'social_core.pipeline.disconnect.disconnect',
)
Backend specific disconnection pipelines can also be defined with a setting such as
SOCIAL_AUTH_TWITTER_DISCONNECT_PIPELINE.
Partial Pipeline¶
The partial pipeline feature allows you to pause the authentication process to request additional information from the user, then resume where you left off.
How Partial Pipelines Work¶
Pause the pipeline: Use the
@partialdecorator on your functionReturn an HTTP response: Redirect the user to a form or page
User completes the action: User fills out a form and submits it
Resume the pipeline: User is redirected back to
/complete/<backend>/and the pipeline resumes from the same function
Basic Example¶
Here’s a simple example of collecting additional user information:
from social_core.pipeline.partial import partial
@partial
def require_email(strategy, backend, details, user=None, **kwargs):
if user and user.email:
# User already has email, continue
return
# Check if email was submitted in this request
email = strategy.request_data().get('email')
if email:
# Email was provided, pass it forward
return {'details': {'email': email}}
# No email yet - interrupt pipeline and show form
return strategy.render_html('email_form.html')
In your template, the form should POST to /complete/<backend>/.
Django example:
<form method="post" action="{% url 'social:complete' backend %}">
{% csrf_token %}
<input type="email" name="email" required>
<button type="submit">Continue</button>
</form>
How Partial Data is Stored¶
@partial stores the pipeline state in a database table named social_auth_partial.
This allows:
Cross-browser support: The process can resume from any browser
No session dependency: More reliable than session-based approaches
UUID tokens: Each partial process is identified by a unique token
The partial token is passed via the partial_token parameter (by default). The library
automatically picks this value from the request to resume the process.
Accessing Partial Data¶
Pipeline functions receive a current_partial instance containing:
current_partial.token- The unique token for this partial processcurrent_partial.backend- The backend nameOther saved data from the pipeline
Example of using the partial token in a redirect:
from urllib.parse import urlencode
@partial
def my_partial_function(strategy, backend, current_partial=None, **kwargs):
# Check if user needs to provide additional information
if not kwargs.get('phone_number'):
# Include partial_token in the URL
params = urlencode({'partial_token': current_partial.token})
url = f'/my-form/?{params}'
return redirect(url)
Configuration¶
To override the default parameter name:
SOCIAL_AUTH_PARTIAL_PIPELINE_TOKEN_NAME = 'my_token_name'
Check the example applications for more detailed usage examples.
Email validation¶
There’s a pipeline to validate email addresses, but it relies a lot on your project.
The pipeline is at social_core.pipeline.mail.mail_validation and it’s a partial
pipeline, it will return a redirect to the URL defined by the
EMAIL_VALIDATION_URL setting. For Django you can use a view name as the value
for this setting. You can use this redirect to tell the users that an email
validation was sent to them. If you want to mention the email address you can
get it from the session under the key email_validation_address.
In order to send the validation python-social-auth needs a function that will
take care of it, this function is defined by the developer with the setting
SOCIAL_AUTH_EMAIL_VALIDATION_FUNCTION. It should be an import path. This
function should take four arguments strategy, backend, code and
partial_token.
partial_token is the same token used on other partials functions
that can be used to restart a halted flow.
code is a model instance used to validate the email address, it
contains three fields:
code = '...'Holds an
uuid.uuid4()value and it’s the code used to identify the validation process.email = '...'Email address trying to be validate.
verified = True / FalseFlag marking if the email was verified or not.
You should use the code in this instance to build the link for email
validation which should go to /complete/email?verification_code=<code here>&partial_token=<token here>.
If you are using Django, you can do it with:
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:
from flask import url_for
url = url_for('social.complete', backend=strategy.backend_name,
_external=True) + '?verification_code=' + code.code + '&partial_token=' + partial_token
This pipeline can be used globally with any backend if this setting is defined:
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¶
Write your function with the appropriate signature
Place it in an importable location in your project
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:
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 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:
{
'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:
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:
SOCIAL_AUTH_PIPELINE = (
'social_core.pipeline.social_auth.social_details',
'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:
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:
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:
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:
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:
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