Unified login in Django and Drupal

I’m working on a project that has two hearts: a Django for a custom CRM with thousands of imported users, and Drupal, where those users should login. This howto apply on:

There are many single/centralized sign-on solutions around, like OpenID, OAuth CAS and external services like SecurePass to do the job. I’m looking for a more basic solution, and I asked to Django-it Google group for a help.

Django-it Google group

Here my constraints and requisites:

During the chat in the Django-it group, I come to this solution for the login and logout processes:

Login: Django login -\> (auto) Drupal login

Logout: Drupal logout -\> (auto) Django logout

Log in to Django

Step 1: The first step is to implement a standard authentication in Django, providing a path for login/logout and two templates for these pages.

Here my template directory structure (template directory is assigned in settings.py):

assigned in settings.py

You can copy base.html and login.html from the Django core. Here an handy command to find these on command line:

If you have different version of Python and Django installed, check these files are from the right version of Python or you’ll use outdated templates, mine comes in a pretty explicit “/usr/lib/python6” directory. Copy those one from the admin directory and make your changes: I suppressed the breadcrumb in the base.html to remove a link to admin backend.

Step 2: In the main urls.py file, I added two login/logout views for the /login and /logout paths using default django views:

Step 3: In settings.py, I add login and logout default urls:

login and logout default urls

You can use everything you want for login and logout paths just changing step 2 and step 3 assignments.

Now http://users.example.com/login/ page should look like this.

http://users.example.com/login/

=> https://chirale.wordpress.com/wp-content/uploads/2013/03/djlogin.png djlogin

Automatic Login to Drupal

First, I create a view in my application containing all the logic for the automatic login on Drupal on myapp/views.py.

Note: if you’re using Python \<= 6, you should implement yourself the check_output method:

should implement yourself the check_output method

If you’re using Python 7, check_output is already implemented and you can skip this step.

check_output is already implemented

On Django 5, an handy settings.py we can redirect a user after login using LOGIN_REDIRECT_URL. Instead of specifying an url, I use the view name I just added:

LOGIN_REDIRECT_URL

What’s going on?

After the successfully login by a valid Django user from http://www.example.com/login/, the user will be redirected to /sso/. The login_required decorator check if a user is logged in and if it’s not the LOGIN_URL page is displayed, with the sso page in the next parameter. So when sso view is displayed, the user is logged in and we can redirect her/him to a Drupal one-time login link we just get from drush on command line:

http://www.example.com/login/

login_required

drush on command line

The output is something like this:

“2” is drupal_id for the user just logged in.

The whole check_output part is to get this value, clean it and then redirect the user to the right page. The substitution on line 22 is necessary in my case for a drush issue that can display the wrong website name in my Drupal development installation. You can skip it if the url is valid, I suggest you to run drush on command line.

The result is like this:

drupal_loggedin

=> https://chirale.wordpress.com/wp-content/uploads/2013/03/drupal_loggedin.png?w=300 drupal_loggedin

Logout

The last step is to logout the user on Drupal and then logout from Django. Now this step has to be implemented in Drupal.

So all we have to do is to redirect to this page after a successful logout on Drupal. We can do it via UI using Rules and Rules UI module:

using Rules and Rules UI module

Visit: admin/config/workflow/rules

Add new rule Reach on event \> User \> User has logged out Actions \> Add action \> System \> Page redirect URL: http://users.example.com/logout/

From now on, after a Drupal user will log out on Drupal via users/logout, her/he will be redirected to the logout page on Django and then logged out.

Side effects

Since we are actually using a one-time login URL, a confusing message is displayed to the unaware user. This message can be changed via String overrides module.

String overrides module

Enable and install module via drush:

And then visit admin/config/regional/stringoverrides, paste the message you get into the original text field and then add the replacement message.

=> https://chirale.wordpress.com/wp-content/uploads/2013/03/logged_in_message.png?w=300 logged_in_message

Additional steps

Here some additional steps on Drupal, valid for the project I’m working on:

These steps may seem trivial but they are important, since if we cannot allow that two different users are logged in in Django and Drupal. A lock to prevent users to log in on Drupal and to register a new account on Drupal should be implemented.

Hope it helps both Drupal and Django users. Happy coding!

https://web.archive.org/web/20130315000000*/https://groups.google.com/forum/?fromgroups=#!topic/django-it/Wyqfb_oyGHM

https://web.archive.org/web/20130315000000*/http://www.example.com

and users.example.com</li> <li>On a Django user creation, a user with the same e-mail is created on Drupal</li> <li>Drupal user id (from now on drupal_id) is stored in the Django user model (a <a href=

https://web.archive.org/web/20130315000000*/https://docs.djangoproject.com/en/dev/topics/auth/customizing/#auth-custom-user

https://web.archive.org/web/20130315000000*/https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TEMPLATE_DIRS

https://web.archive.org/web/20130315000000*/https://docs.djangoproject.com/en/1.5/ref/settings/#login-url

https://web.archive.org/web/20130315000000*/http://users.example.com/login/

page should look like this.</p> <p><a href=

https://web.archive.org/web/20130315000000*/https://chirale.wordpress.com/2013/03/15/unified-login-in-django-and-drupal/djlogin/

&quot;http://%s/&quot; % settings.DRUPAL_SITE_URL).strip() # set Drupal login destination using DRUPAL_LOGIN_DESTINATION destination = &quot;%s?destination=%s&quot; % (drupal_login_url, settings.DRUPAL_LOGIN_DESTINATION) return redirect(destination) else: # no output from the drush command return HttpResponse('Wrong request') except AssertionError: # Drupal id is not assigned return HttpResponse('Not registered user') </pre> <p>Note: if you&#8217;re using Python &lt;= 2.6, you <a title=

https://web.archive.org/web/20130315000000*/http://stackoverflow.com/a/2924457/892951

def check_output(*popenargs, **kwargs): if 'stdout' in kwargs: raise ValueError('stdout argument not allowed, it will be overridden.') process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs) output, unused_err = process.communicate() retcode = process.poll() if retcode: cmd = kwargs.get(&quot;args&quot;) if cmd is None: cmd = popenargs[0] raise subprocess.CalledProcessError(retcode, cmd, output=output) return output class CalledProcessError(Exception): def __init__(self, returncode, cmd, output=None): self.returncode = returncode self.cmd = cmd self.output = output def __str__(self): return &quot;Command '%s' returned non-zero exit status %d&quot; % ( self.cmd, self.returncode) # overwrite CalledProcessError due to `output` keyword might be not available subprocess.CalledProcessError = CalledProcessError </pre> <p>If you&#8217;re using Python 2.7, <a href=

https://web.archive.org/web/20130315000000*/http://docs.python.org/2/library/subprocess.html#subprocess.check_output

https://web.archive.org/web/20130315000000*/https://docs.djangoproject.com/en/1.5/ref/settings/#login-redirect-url

https://web.archive.org/web/20130315000000*/http://www.example.com/login/

the user will be redirected to /sso/. The <a href=

https://web.archive.org/web/20130315000000*/https://docs.djangoproject.com/en/dev/topics/auth/default/#django.contrib.auth.decorators.login_required

https://web.archive.org/web/20130315000000*/http://drush.ws/#user-login

</pre> <p>&#8220;2&#8221; is drupal_id for the user just logged in.</p> <p>The whole check_output part is to get this value, clean it and then redirect the user to the right page. The substitution on line 22 is necessary in my case for a drush issue that can display the wrong website name in my Drupal development installation. You can skip it if the url is valid, I suggest you to run drush on command line.</p> <p>The result is like this:<br /> <a href=

https://web.archive.org/web/20130315000000*/https://chirale.wordpress.com/2013/03/15/unified-login-in-django-and-drupal/drupal_loggedin/

https://web.archive.org/web/20130315000000*/http://users.example.com/logout/

</ul> <p>So all we have to do is to redirect to this page after a successful logout on Drupal. We can do it via UI <a href=

https://web.archive.org/web/20130315000000*/http://drupal.org/project/rules

</ol> <p>From now on, after a Drupal user will log out on Drupal via users/logout, her/he will be redirected to the logout page on Django and then logged out.</p> <h3>Side effects</h3> <p>Since we are actually using a one-time login URL, a confusing message is displayed to the unaware user. This message can be changed via <a href=

https://web.archive.org/web/20130315000000*/http://drupal.org/project/stringoverrides

https://web.archive.org/web/20130315000000*/https://chirale.wordpress.com/2013/03/15/unified-login-in-django-and-drupal/logged_in_message/

https://web.archive.org/web/20130315000000*/http://users.example.com/sso/

instead of /login/ because I can change the myapp.views.sso it later to not log in users already logged in and because the login_required decorator already serve the login page if the user is not logged in.</li> </ul> <p>These steps may seem trivial but they are important, since if we cannot allow that two different users are logged in in Django and Drupal. A lock to prevent users to log in on Drupal and to register a new account on Drupal should be implemented.</p> <p>Hope it helps both Drupal and Django users. Happy coding!</p>

Proxied content from gemini://chirale.org/2013-03-15_1009.gmi (external content)

Gemini request details:

Original URL
gemini://chirale.org/2013-03-15_1009.gmi
Status code
Success
Meta
text/gemini; charset=utf-8
Proxied by
kineto

Be advised that no attempt was made to verify the remote SSL certificate.