2

I have a django application that uses django-social-auth and allows for three types of SSO authentication, Google, Office 365, and SAML2.0. In the case of SAML2.0, the application allows the end user to specify their own IdP, and to enable that, we have a custom Database SAML Auth class, that allows us to store the users IdP information in the database, and log the user in. This works as expected, and users can sign in with SAML, Google, or Office 365, no problem.

The challenge is when I need to be redirected to a specific URL once the login has completed. This is working as expected for Google and Office 365, but not for SAML login.

As an example, I have a mobile application that authenticates via OAUTH to the django web application. When that mobile application starts its oauth flow, it goes to the authorization URL, and the gets forwarded to login. The path looks something like this:

  1. https://myapp.com/oauth/authorize/?client_id=something
  2. https://myapp.com/login/?next=/oauth/authorize/?client_id=something
  3. (after selecting sign in with SAML login method) https://myapp.com/login/subdomain/?next=/oauth/authorize/?client_id=something
  4. Redirect to IdP
  5. Redirect back to assertion consumer url https://myapp.com/login/sso/saml/complete/
  6. Redirect to account page https://myapp.com/manage/ (not https://myapp.com/oauth/authorize/?client_id=something as expected)

The frustration of course is that this breaks the oauth flow of my mobile application, as the user is logged into the web application, but never authorizes the mobile application.

According to python social auth documentation the value of ?next= should be used if present to redirect the user after a successful authentication.

I have done a lot of debugging, and I can see in the social-core do_auth method that the next parameter is indeed added to the session as expected.

But, when I debug and look at the session returned in the social-core do_complete method after the IdP posts the user back to my application, the session is empty and doesn't contain any data, including not having my next parameter.

At one point, all of this worked as expected. However, it has stopped working and I can not find any code change that seems to point to why this is the case. My question to the greater community is, have I missed something that might resolve my issue and set me back on the path where SAML login redirects as expected.

Some further details that may be helpful:

  • Django version 2.2.24
  • Python 3.6
  • social-auth-app-django versions 3.0.0 and 5.0.0 tested
  • social-auth-core versions 3.3.3 and 4.1.0 tested

My middlewares (note many of these are custom middlewares as part of our application):

MIDDLEWARE = [
    'instana.instrumentation.django.middleware.InstanaMiddleware',
    'eb.middleware.HealthCheckMiddleware',
    'eb.middleware.OriginalXForwardedProtoMiddleware',
    'eb.middleware.DebugMiddleware',
    'eb.middleware.ContentSecurityPolicy',
    'django_cookies_samesite.middleware.CookiesSameSite',
    'common.middleware.RequestIDMiddleware',
    'common.middleware.APIErrorHandler',
    'corsheaders.middleware.CorsMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.locale.LocaleMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'accounts.middleware.TwoFactorMiddleware',
    'accounts.middleware.PasswordChangeMiddleware',
    'accounts.middleware.VerifyActiveMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.gzip.GZipMiddleware',
    'billing.middleware.CartMiddleware',
    'accounts.middleware.SentryContextMiddleware',
    'accounts.middleware.SocialAuthExceptionMiddleware',
    'accounts.middleware.RealIPMethodMiddleware',
    'common.sqla.middleware.SQLAlchemyMiddleware',
    'django.middleware.security.SecurityMiddleware',
]

Already attempted troubleshooting steps:

  • Remove / rearrange middlewares
  • Change settings in the django application both for session cookies and SOCIAL_AUTH security settings
  • Run several PDB sessions to see the session setting and getting noted above

Thank you for any help in advance.

1 Answers1

1

I also stumbled upon this today and this might be an issue with SameSite cookies. As you rightly noted, the next parameter is correctly set within the user's session. To remember the session, the session id is stored within a cookie before the redirect to the SAML-IDP happens. Once the authentication happened on the SAML-Provider, the latter will redirect back to django but since the default setting for SameSite cookies is Lax, the cookie will not be sent along with the (redirect) request as the domain of the SAML provider is most likely not the "same site" as the django site.

In essence this means, the next parameter is correctly stored within a session, but once the user comes to django after authenticating via the IDP, django cannot relate back the session.

To mitigate this, we'd either need to set the SameSite cookie policy to be None, thus allowing cookies to be sent also with requests from other domains / sites than the one the cookie was issued for or find another way to keep the state (the path to redirect to after login) between the two requests. SAML itself offers functionality to do exactly that, called RelayState:

This RelayState parameter is meant to be an opaque identifier that is passed back without any modification or inspection

Sounds great in theory, but python-social-core / django-social-auth currently (ab-)use RelayState to only pass along the idp's name as they attempt to support multiple SAML-IDPs on one site. So the built-in SAML provider would need to be adapted to pass along also the next parameter alongside the name of the idp to make this work.

burnedikt
  • 962
  • 5
  • 17