<a id="pipeline"></a>

# Pipeline

[python-social-auth](https://github.com/python-social-auth) uses an extendible pipeline mechanism where developers can
introduce their functions during the authentication, association and
disconnection flows.

<a id="pipeline-overview"></a>

## 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** `**kwargs` to handle unexpected arguments gracefully

<a id="understanding-return-values"></a>

### Understanding Return Values

Pipeline functions can return three types of values, each with different behavior:

1. **Return** `None` **or nothing**: The pipeline continues to the next function. This is
   equivalent to returning an empty dict `{}`.
2. **Return a** `dict`: The values in the dictionary are merged into the `kwargs` for
   all subsequent pipeline functions. This is how you pass data forward in the pipeline.

   Example:
   ```default
   def my_pipeline_function(backend, user, **kwargs):
       # Calculate something
       custom_value = "some data"
       # Pass it to next functions
       return {'custom_value': custom_value}
   ```
3. **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:
   ```default
   def my_pipeline_function(backend, user, **kwargs):
       if some_condition:
           # Interrupt pipeline and redirect user
           return redirect('/some-form/')
   ```

<a id="common-function-parameters"></a>

<a id="id1"></a>

### 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 (`None` if not yet created or retrieved)
* `social` - The `UserSocialAuth` instance (`None` until created)
* `uid` - The unique user ID from the provider
* `response` - The raw response from the authentication provider
* `details` - Processed user details (username, email, etc.)
* `groups` - Normalized external memberships, or `None` when extraction is disabled; see [External groups](groups.html.md)
* `is_new` - Boolean indicating if a user was just created
* Any values returned as dicts by previous pipeline functions

Read effective request parameters through `strategy.request_data()`. The native
framework request is available as `strategy.request` when the integration
provides one. Neither is automatically passed as a pipeline `request` argument.

**Important:** Always include `**kwargs` in your function signature to handle additional
arguments that may be passed from other pipeline functions or future versions:

```default
def my_custom_pipeline(strategy, backend, user, **kwargs):
    # Your code here
    pass
```

<a id="authentication-pipeline"></a>

## 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.

<a id="understanding-the-default-pipeline"></a>

### 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:

```default
(
    # 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',
    'social_core.pipeline.social_auth.social_names',

    # 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',
)
```

<a id="name-normalization"></a>

<a id="id2"></a>

### Name normalization

The default pipeline runs `social_core.pipeline.social_auth.social_names`
after `social_details`. Backends return the names supplied by the provider;
`social_names` fills missing representations in `details` before user
creation, extra-data storage, and user updates.

When both `first_name` and `last_name` are empty, a nonempty `fullname` is
split at the first space. For example, `Mary Jane Watson` becomes `Mary` and
`Jane Watson`. A single name becomes `first_name`, with an empty
`last_name`. If either component is supplied, it is preserved and the other
component is not inferred.

A missing `fullname` is generated from either or both components. When
`first_name` already appears as a complete name component or sequence of
components in `last_name` (matched case-sensitively at whitespace boundaries),
the full name uses `last_name` alone. For example, `Jane` and `Jane Watson`
produce `Jane Watson`, while `Ann` and `Anniston` produce `Ann Anniston`. Otherwise, the components are joined with a
space. Surrounding whitespace is removed. Supplied nonempty names take
precedence, even when the representations differ. Missing or `None` names
are preserved when no nonempty name is available.

Set `SOCIAL_AUTH_FIRSTLAST_FROM_FULL` or `SOCIAL_AUTH_FULL_FROM_FIRSTLAST`
to `False` to disable one direction. Both default to `True` and support
backend-specific overrides, such as
`SOCIAL_AUTH_SAML_FIRSTLAST_FROM_FULL = False`. These settings are read only
by `social_names`.

#### IMPORTANT
Existing custom pipelines must add
`social_core.pipeline.social_auth.social_names` after `social_details`
to retain automatic name conversion. Built-in backends no longer perform
this conversion in `get_user_details()`. Remove or replace `social_names`
to customize normalization, including in backend-specific pipelines.

`BaseAuth.get_user_names()` is deprecated and emits `DeprecationWarning`.
Third-party backends should return provider-supplied names and let the pipeline
normalize them. The compatibility helper still converts names before the
pipeline, so the pipeline settings cannot undo conversion performed by callers
of that helper.

**What Data is Available When?**

Understanding which data is available at each stage is crucial for placing your
custom functions correctly:

* **After** `social_details`: `details` dict is populated with user info
* **After** `social_names`: missing name representations are populated
* **After** `social_uid`: `uid` contains the provider’s user ID
* **After** `social_user`: `social` may contain the UserSocialAuth instance (if user was previously authenticated)
* **After** `get_username`: `username` is available
* **After** `create_user`: `user` contains the User instance (new or existing)
* **After** `associate_user`: `social` contains the UserSocialAuth instance
* **After** `load_extra_data`: `social.extra_data` contains access tokens and additional provider data

<a id="customizing-the-pipeline"></a>

### 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:

```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.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:

```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',
    '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:

```default
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}
```

<a id="per-backend-pipelines"></a>

### 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.

<a id="disconnection-pipeline"></a>

## 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:

```default
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`.

<a id="partial-pipeline"></a>

## 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.

<a id="how-partial-pipelines-work"></a>

### How Partial Pipelines Work

1. **Pause the pipeline**: Use the `@partial` decorator on your function
2. **Return an HTTP response**: Redirect the user to a form or page
3. **User completes the action**: User fills out a form and submits it
4. **Resume the pipeline**: User is redirected back to `/complete/<backend>/` from
   the same browser session and the pipeline resumes from the same function

<a id="basic-example"></a>

### Basic Example

Here’s a simple example of collecting additional user information:

```default
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**:

```default
<form method="post" action="{% url 'social:complete' backend %}">
    {% csrf_token %}
    <input type="email" name="email" required>
    <button type="submit">Continue</button>
</form>
```

<a id="how-partial-data-is-stored"></a>

### How Partial Data is Stored

`@partial` stores the pipeline state in a database table named
`social_auth_partial` and stores the current partial token in the browser
session. By default, the stored pipeline can resume only from the browser
session that created it. This prevents a `partial_token` copied into another
browser from acting as a bearer login credential.

The partial token is passed via the `partial_token` parameter (by default).
The library automatically picks this value from the request, but it resumes the
process only when the token belongs to the current session.

<a id="accessing-partial-data"></a>

### Accessing Partial Data

Pipeline functions receive a `current_partial` instance containing:

* `current_partial.token` - The unique token for this partial process
* `current_partial.backend` - The backend name
* `current_partial.pipeline_type` - `authentication` or `disconnect`;
  the saved step can resume only in the matching pipeline
* Other saved data from the pipeline

Example of using the partial token in a redirect:

```default
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)
```

<a id="configuration"></a>

### Configuration

To override the default parameter name:

```default
SOCIAL_AUTH_PARTIAL_PIPELINE_TOKEN_NAME = 'my_token_name'
```

<a id="external-resume-links"></a>

### External resume links

Some partial flows intentionally send the partial token out of band, for
example by e-mail or SMS. Use `partial_step` with `allow_external_resume`
only for those flows:

```default
from social_core.pipeline.partial import partial_step

@partial_step(save_to_session=True, allow_external_resume=True)
def my_external_validation(strategy, backend, current_partial=None, **kwargs):
    ...
```

Externally resumed partials do not resume immediately when a request supplies
the partial token, even in the browser session that created the partial. The
first request stores pending resume state in the current browser session and
asks the Strategy to render a local confirmation response. A later confirmation
request from the same browser resumes the pipeline and replays the original
link request data through `strategy.request_data()`.

The confirmation request must include the parameter configured by
`SOCIAL_AUTH_PARTIAL_PIPELINE_EXTERNAL_RESUME_CONFIRMATION_PARAMETER`. The
default parameter name is `partial_pipeline_confirm`. A hidden field such as
`partial_pipeline_confirm=1` is enough for core to select the pending external
resume; your Strategy should still verify any local confirmation state it
created, such as a nonce stored in the browser session.

If your pipeline step needs the original link parameters after confirmation,
read them like this:

```default
data = strategy.request_data()
```

Confirmation state is checked against the current framework request before
replay is activated. Effective data combines the saved link parameters with
the confirmation request’s parameters, with current values taking precedence
when a key appears in both. Ordinary session-owned partial resumes use current
request parameters, so newly submitted form values remain available.

For Django, `social-auth-app-django` provides the default confirmation page.
Override the `social_django/partial_pipeline_external_resume.html` template to
customize it. The confirmation form must require an explicit user action and
must not send `partial_token` or validation codes again. Custom templates must
still include the confirmation parameter described above. They must also include
the nonce field from the template context, using `confirmation_nonce_parameter`
as the field name and `confirmation_nonce` as the value. The default nonce
field name is `partial_pipeline_confirm_nonce`.

Other integrations should implement both Strategy hooks:
`partial_pipeline_external_resume_confirmation()` to render the local
confirmation response and `partial_pipeline_external_resume_confirmed()` to
verify the follow-up request.

Check the [example applications](https://github.com/python-social-auth/social-examples) for more detailed usage examples.

<a id="email-validation"></a>

## 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](https://github.com/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.

The built-in mail validation step is an externally resumable partial. Opening
the validation link will first show the local confirmation response provided by
the active Strategy. After confirmation, the pipeline receives the original
`verification_code` and `partial_token` through `strategy.request_data()`.

`code` is a model instance used to validate the email address, it
contains four 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 / False`
: Flag marking if the email was verified or not.

`timestamp`
: Creation time used to enforce the code’s lifetime.

Email validation codes are single-use and expire after seven days by default.
Set their lifetime in seconds with:

```default
SOCIAL_AUTH_EMAIL_VALIDATION_EXPIRED_THRESHOLD = 604800
```

Set this to `None` or `0` to disable expiry. Codes are expired once their
creation time plus this lifetime is reached. Undated codes are rejected when
expiry is enabled, and existing codes older than the configured lifetime can no
longer be used. See [Storage](storage.html.md) for timestamp persistence requirements.

Expiry is checked when the code is validated, independently of scheduled
database cleanup. It does not delete expired codes or limit the lifetime of
other partial pipeline steps. Django’s `clearsocial` command handles retention
of old unused codes and partials separately.

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:

```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`.

<a id="extending-the-pipeline"></a>

# 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

<a id="steps-to-add-a-custom-pipeline-function"></a>

## 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`.

<a id="writing-custom-pipeline-functions"></a>

## Writing Custom Pipeline Functions

<a id="function-signature"></a>

### 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](#common-function-parameters) for details on the parameters available to pipeline functions.

<a id="practical-example-saving-user-profile-data"></a>

## 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
```

<a id="common-patterns-and-tips"></a>

## 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
```
