For Acheev.it, I’m currently building a Django application that integrates with GMail. We plan to distribute this via the Google Apps Marketplace so it will be very easy for organizations that are already using Google Apps to install.
One of the requirements of the Apps Marketplace is that apps support single-sign on (SSO). Google Apps domains use OpenId to support SSO, but with a few differences that mean standard OpenID packages don’t work directly.
Problems
First, because Google Apps users are authenticated against google.com, their claimed id (e.g. from their custom domain) doesn’t match the authenticating domain (google.com). This means their identity cannot be verified by OpenId frameworks that don’t treat Google Apps specially.
Second, most users of Google Apps don’t know what their OpenId endpoint should be. Therefore a better experience is to calculate it from their domain name. The Google Apps endpoint is always at the URL below:
https://www.google.com/accounts/o8/site-xrds?ns=2&hd={DOMAIN}
This is not only useful for improving the UX for users who come to your site directly, but also for enabling a navigation link within Google Apps that links directly to your application. For that, you need to provide a URL in your appmanifest.xml that includes the Apps domain. That URL should immediately start the authentication process, thus enabling apps users to have the experience of clicking a link and immediately being logged in to your application.
Solution
Because I’m working in Django, I had to address these problems in python. The first one involved changing python-openid to support Google Apps domains and I now have a pull request open with the project. If you’d like to use my fork immediately, though, it’s at https://github.com/mpdaugherty/python-openid-with-google-apps.
For the second issue, I decided to extend django-socialregistration to support Google Apps as a separate authentication method. The socialregistration project already provides the ability to create users and profiles based on information from a 3rd-party authentication system and supports OpenId. Because GoogleApps is just a slightly-different way of processing OpenId, this seemed like a good way to go. My branch with google apps support is at https://github.com/mpdaugherty/django-socialregistration-with-google-apps. There is no pull request yet because it depends on the open pull request to python-openid.
Moreover, because GoogleApps can provide information about a user (first name, last name, email address, language, and country), my addition to django-socialregistration automatically populates those properties on users and profiles as long as the information is in the user’s Google profile.
Example
Now, adding Google Apps SSO support to a Django project is simple. First, include my two repositories in your requirements.txt file and install them with pip:
-e git://github.com/mpdaugherty/python-openid-with-google-apps@master#egg=openid
-e git://github.com/mpdaugherty/django-socialregistration-with-google-apps@master#egg=socialregistration
$ pip install -r requirements.txt
Next, update your settings to include socialregistration and socialregistration.contrib.googleapps in INSTALLED_APPS
INSTALLED_APPS = (
…
'socialregistration',
'socialregistration.contrib.googleapps',
…
)
Add ‘socialregistration.contrib.openid.auth.OpenIDAuth’ to AUTHENTICATION_BACKENDS
AUTHENTICATION_BACKENDS = (
…
'socialregistration.contrib.openid.auth.OpenIDAuth',
)
Finally, add the socialregistration urls to urls.py
urlpatterns = patterns(
…
url(r'/social/', include('socialregistration.urls',
namespace = 'socialregistration')),
…
)
After all that setup, in any of your templates, load the googleapps template tags and include a googleappsform. This will ask the user for a domain and authenticate them against google apps.
{% load googleapps %}
<ol>
<li>{% googleapps_form %}</li>
</ol>
To enable SSO within a marketplace app, add a navLink extension to your appmanifest.xml that goes directly to the google apps redirect page. Replace ‘yourapp.com’ with your actual application URL, but leave “${DOMAINNAME}” exactly as it is here. That will be replaced by Google with the domain name from which the user is logging in.
<!– Show this link in Google's universal navigation for all users –>
<Extension id="navLink" type="link">
<Name>Your App Name</Name>
<Url>http://yourapp.com/social/googleapps/redirect/?domain=${DOMAIN_NAME}</Url>
</Extension>
Conclusion
I will keep these repositories updated from their respective upstream repositories as long as the pull requests haven’t been merged in yet. Let me know if you find them useful or if you have any questions/suggestions in the comments below. Thanks!