Wesley Chun
July 2010
SDK: 1.3.4
Federated authentication (OpenID) was introduced to App Engine in SDK 1.3.4 (May 2010), integrated as part of the Users service ( Python | Java ). The content here is intended to complement the existing documentation. The features outlined here cannot be used in older versions of the SDK. Note that the support for OpenID is experimental.
- Background
- Federated Authentication and OpenID
- Federated Authentication in App Engine
- Setup
- Federated identity providers
- UI Presentation
- Federated login and logout
- Examples
- Authorization
- Unsupported Features
- Conclusion
Background
Authentication and authorization are two topics of current interest to the web application community at-large. Users and developers often confuse the two, and even worse, many can't distinguish between them. To understand them better requires one to know more keenly what their differences are. When you hear authentication , think "user identities," e.g., logins and passwords; and when you see the term authorization , think "APIs" and "data access." In some cases, the two will overlap, such as when you go to a Twitter photo hosting and sharing website: you will authenticate with Twitter which also authorizes the site (via OAuth [more on OAuth in a bit]) to give it the ability to add a pic and status message to your Twitter stream.
Well-known and established Internet brands such as Google, Yahoo!, and MSN have been using single sign-on (SSO) solutions across their respective properties for years, and more recently, have started to act as identity providers for third-party sites. In addition, the huge growth in popularity of social networks such as Twitter, MySpace, and Facebook have pushed them to become identity platforms as well. It is increasingly common for new websites and services to leverage these existing platforms to authenticate users who have grown wary of registering for new services. Leveraging such services allows users to login with a familiar name. However, readers should be aware that there are two different flavors of authentication, delegated and federated .
Twitter's system, as described above, is known as delegated authentication . Many websites, opting out of building their own authentication system altogether, are effectively outsourcing, or "delegating," it to more well-known entities such as Twitter. Logging into a web app with Facebook Connect is another delegation example, although it is a closed system as opposed to "Sign in with Twitter" which uses OAuth. (OAuth is an open authorization system, a concept outside of the scope of this article but which is used regularly in conjunction with delegated authentication.)
Delegated authentication is one effort to bring the concept of Internet SSO to reality. The fewer accounts users have to worry about, the better the web scales. However, you have another problem now: developers who use this type of authentication are requiring their users have Twitter or Facebook accounts. This is not the "create one identity somewhere and use it any anywhere" type of solution that has been envisioned. In comes federated authentication , a solution that's much closer to true Internet SSO.
Federated Authentication and OpenID
Federated authentication is a decentralized security mechanism not tied to any one particular provider, and OpenID is an open standard which implements federated authentication. With OpenID, users can create an identity (username and password [or alternatives such as biometrics, keyfob, etc.]) at any OpenID provider and be able to use that same identity at any online app which accepts OpenID logins. Google has supported the OpenID 2.0 protocol since 2008 in a variety of products such as Blogger and Google Apps.
In the land of OpenID, there are two types of players:
- Identity provider or Server – service which hosts OpenID identities and can authenticate users whose credentials were created there
- Relying party or Consumer – any application that has delegated its authentication to an OpenID provider
Google is both an identity provider – letting users login to a variety of Google and third-party websites with their Google Account information – as well as a relying party: Blogger and Google Apps let users sign in to those services using their OpenID identity. Note that sites that require users to sign in with a specific authority, such as Google or Yahoo! with OpenIDs created at those sites, are examples of delegated authentication even though OpenID is used. (Often in documentation, you will find that the term "OpenID" is also used interchangeably [like we just did] to refer to an OpenID identity as well as the protocol itself.) A truly federated system allows you to login using an identity created at any OpenID provider.
Federated Authentication in App Engine
As of release 1.3.4, federated login via OpenID has been made available to App Engine users, meaning that developers can create apps that use OpenID as their authentication mechanism. This is a breakthrough and highly-desired feature for application developers who want to offer more flexible authentication to their users rather than having to roll their own or simply using App Engine's Users service which requires their users to have a Google Account. The addition of OpenID means that apps written for App Engine can now support three forms of authentication:
- Google Accounts (identity provider)
- Google Apps (identity provider)
- OpenID (relying party)
Setup
Applications can be configured to use "Google Accounts" (the default) or "Federated Login" at creation time. Existing applications can migrate to federated authentication via a configuration change in the Admin Console Dashboard.
Figure 1. Google Accounts authentication is the default.
Go to the "Authentication Options" pulldown and select "(Experimental) Federated Login" to switch to OpenID. Your settings will now look like this:
Figure 2. Selecting "Federated Login" as the authentication option.
For more precise steps on how to do this as well as a general introduction to this feature in App Engine, check out the Users service overview documentation ( Python | Java ). There is also a blog post where you can find additional information.
If your app already exists, switching from using Google Accounts to OpenID as your authentication mechanism does not affect your app's existing User objects as Google will now serve as those users' OpenID provider. However, you should avoid doing the reverse as OpenID comprises a superset of the entire space of user identification ( more on this below ).
One thing that hasn't changed is
requiring
users to login to your app. If you wish to allow unauthenticated users, you can do that too. If not, you require users login "at runtime" by doing a redirect to a link created by
create_login_url()
(Python) or
createLoginURL()
(Java). Or, if there are multiple entry points to your app, you can make it a configuration setting in your
app.yaml
(Python) or
web.xml
(Java) file. You can review how to do this in the docs (
Python
|
Java
).
WARNING
: Be aware that if you change your authentication setting to "Federated Login" and add require logins in your config file
without
changing any application code, there will be automatic redirection to
/_ah/login_required
, so if you're not handling it, you'll get infinite recursion, as
discovered recently by early adopters
.
Below are sample configurations for Python and Java to handle this redirection. Remember, you only need to do this if you do not change your application code. (In the code example further along in this article, we did not require users be logged in when accessing our app so these alterations were not made.)
In this Python
app.yaml
file, we have set user login to be required for the
main.py
controller, thus we only need to add a section to handle the
/_ah/login_required
URL:
handlers: - url: /_ah/login_required script: do_openid_login.py - url: .* script: main.py login: required
The changes to
web.xml
for Java users are similar. We start by specifying that logins are required for some URL pattern of your app:
<security-constraint> <web-resource-collection> <web-resource-name>all</web-resource-name> <url-pattern>/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>*</role-name> </auth-constraint> </security-constraint>
With standard Google authentication, you wouldn't need a specific handler. App Engine automatically brings up a Google Accounts login page for users. With OpenID, if you wish to use a different authentication screen, and presumably to accept non-Google OpenIDs you would, add a handler for
/_ah/login_required
:
<servlet> <servlet-name>LoginRequiredServlet</servlet-name> <servlet-class>mysite.server.LoginRequiredServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>LoginRequiredServlet</servlet-name> <url-pattern>/_ah/login_required</url-pattern> </servlet-mapping>
Whether you choose
do_openid_login.py
or
LoginRequiredServlet.java
, the corresponding snippet of code will likely contain HTML and display a nice login prompt to your users letting them enter their OpenID to authenticate with.
WARNING : at the time of this writing, OpenID is not supported if your app runs in secure mode using HTTPS.
Federated identity providers
The magic of OpenID in App Engine happens in the
create_login_url()
or
createLoginURL()
calls to generate the link that users click on or that your app redirects to appropriate provider for authentication. There are currently a number of
OpenID providers
your users can choose from.
Provider types
There are two flavors of provider types: direct providers like Google and Yahoo! require only a generic federated identity that has no username associated with it. These are called unbound discoverable URLs because they're not tied to any particular user – in other words, there is no information contained within the OpenID URL string to uniquely identify users.
In contrast, Flickr, WordPress, Blogger, and LiveJournal are known as username providers because all require you to provide a URL which does. Not surprisingly, those are called bound discoverable URLs . Each of these URLs contains a username in one form or another to directly associate it with a user registered at that provider.
Some providers do both, meaning they will accept both bound as well as unbound discoverable URLs. AOL, MyOpenID, and MySpace are examples of providers that accept both types. Regardless of which method you choose, be sure that your interface is aware of the requirements and prompt if necessary from your users.
In any case, that "URL" is passed as the
federated_identity
parameter to the
users.create_login_url(federated_identity=
URL
)
call to generate the HTML link that the user can select. Below are examples of both types of providers you can use as
URL
in your call (domain names are case-insensitive). These lists aren't exhaustive, however.
Direct provider federated identities
-
https://www.google.com/accounts/o8/id
-
yahoo.com
-
myspace.com
-
aol.com
-
myopenid.com
Username provider federated identities
-
flickr.com/ USERNAME
-
USERNAME .wordpress.com
-
USERNAME .blogspot.com
-
USERNAME .livejournal.com
-
openid.aol.com/ USERNAME
-
USERNAME .myopenid.com
-
www.myspace.com/ USERNAME
UI presentation
There are a variety of ways you can let your users select their federated provider and login:
- Present links for users to click
- Let users select from a pulldown
- Create a text field for users to enter an OpenID
- Have a login page with logos
For an example of how to design and structure your login page, check out Google's federated login user experience summary document . You can also take a look at this flexible and stylish jQuery plugin – at this page, you will also find other direct and username providers that may not have been listed above.
Federated login and logout
As far as integrating OpenID into your app, the API call to get a link for users gets one or two new optional parameters – there are no other changes. This was done intentionally to simplify the process for developers and to preserve the existing API. The new
federated_identity
parameter is passed to
create_login_url()
; likewise for
federatedIdentity
and
createLoginURL()
method in Java. You would use this same value to instantiate new
User
objects with.
There are no changes to existing
User
objects that you may have stored when using only Google Accounts or Google Apps domain, even when you switch to federated authentication. All user data in your datastore and user IDs are preserved and still valid. Earlier, we advised you that once you move to using OpenID, you should not revert back to using only Google Accounts. The reason for this is because once you start storing new user information created from OpenIDs in your datastore, there is no way to corroberate the non-Google generated user IDs in your datastore nor any way to convert them to any valid type of Google user.
If you look at the docs, you'll see that both
create_login_url()
(Python) or
createLoginURL()
(Java) have an "authentication domain" parameter (
auth_domain
for Python or
authDomain
for Java) in their signatures, but it is not used so just pass an empty string (Java) or leave it out (Python).
dest_url
and
destinationURL
work as before. Java has an additional
attributesRequest
parameter for additional attributes to send as part of the request.
Logging out is more interesting. As before, you will use
create_logout_url()
(Python) or
createLogoutURL()
(Java) for your app. When the user clicks on it, they will log out of the App Engine app but
not
logged out of the federated provider. In other words, if your user then logs into your app again, they will not be prompted to enter their credentials. Instead, they will likely get a choice to just continue (yes/no) to your app, and upon clicking it, go directly to whatever destination URL you had set. There is no equivalent of a "single sign-out" to log users out of both your App Engine app as well as with their OpenID provider.
WARNING : You should strongly urge users that if using a computer that is not their's, say a kiosk in a public venue, they should clear all cookies in addition to logging out. (A less attractive alternative is to tell users to logout of your app then go to their OpenID provider's website and sign-out from there too.)
Examples
Let's look at some code! As described above, login screens can be as simple or as complex as you like. The most generic way sends users directly to an OpenID provider where they enter their OpenID and password. In the example app below, all we do is take the "Hello World" example and add the ability for the user to do a federated login:
Python
main.py
from google.appengine.api import users from google.appengine.ext import webapp from google.appengine.ext.webapp.util import run_wsgi_app providers = { 'Google' : 'https://www.google.com/accounts/o8/id', 'Yahoo' : 'yahoo.com', 'MySpace' : 'myspace.com', 'AOL' : 'aol.com', 'MyOpenID' : 'myopenid.com' # add more here } class MainHandler(webapp.RequestHandler): def get(self): user = users.get_current_user() if user: # signed in already self.response.out.write('Hello <em>%s</em>! [<a href="%s">sign out</a>]' % ( user.nickname(), users.create_logout_url(self.request.uri))) else: # let user choose authenticator self.response.out.write('Hello world! Sign in at: ') for name, uri in providers.items(): self.response.out.write('[<a href="%s">%s</a>]' % ( users.create_login_url(federated_identity=uri), name)) application = webapp.WSGIApplication([ ('/', MainHandler), ], debug=True) def main(): run_wsgi_app(application) if __name__ == '__main__': main()
Java
main.java
import java.io.IOException; import java.io.PrintWriter; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.google.appengine.api.users.User; import com.google.appengine.api.users.UserService; import com.google.appengine.api.users.UserServiceFactory; @SuppressWarnings("serial") public class OpenIdDemoServlet extends HttpServlet { private static final Map<String, String> openIdProviders; static { openIdProviders = new HashMap<String, String>(); openIdProviders.put("Google", "https://www.google.com/accounts/o8/id"); openIdProviders.put("Yahoo", "yahoo.com"); openIdProviders.put("MySpace", "myspace.com"); openIdProviders.put("AOL", "aol.com"); openIdProviders.put("MyOpenId.com", "myopenid.com"); } @Override public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { UserService userService = UserServiceFactory.getUserService(); User user = userService.getCurrentUser(); // or req.getUserPrincipal() Set<String> attributes = new HashSet(); resp.setContentType("text/html"); PrintWriter out = resp.getWriter(); if (user != null) { out.println("Hello <i>" + user.getNickname() + "</i>!"); out.println("[<a href=\"" + userService.createLogoutURL(req.getRequestURI()) + "\">sign out</a>]"); } else { out.println("Hello world! Sign in at: "); for (String providerName : openIdProviders.keySet()) { String providerUrl = openIdProviders.get(providerName); String loginUrl = userService.createLoginURL(req .getRequestURI(), null, providerUrl, attributes); out.println("[<a href=\"" + loginUrl + "\">" + providerName + "</a>] "); } } } }
The way this simple app works in either language is that you get a simple login screen like the below:
Figure 3. Sample app offering multiple login links.
Users will then select a provider. Let's randomly choose a few so you can see what each login screen looks like:
Figure 4. Login experience with: a) Google, b), Yahoo!, c) MySpace
After logging in, you can verify the user's OpenID username as presented in our sample app above via the
user.getNickname()
(Java) and
user.nickname()
(Python).
Figure 5. Various output from app after logging in with different OpenID providers.
Remember that these are just ultra-simplistic login screens. We linked you to more complex ones in the UI Presentation section above, but here is an example of a simple login selector box with pre-made links for users:
Figure 6. Graphically-oriented OpenID selector
It is important to note that App Engine's SDK does not provide a login screen. Building a login screen is your responsibility as the developer. The sample code above is sufficient for a basic authentication mechanism, but be aware that this only logs users into your application via their identity provider of choice and nothing more.
Authorization
As mentioned at the beginning of this article, authentication and authorization are often confused. The topic of our discussion here is purely authentication – the concept of user identities – usually controlled or distinguished by a username and password. Although authorization is out of the scope of this article, we must mention that it is also possible to roll-in authorization with authentication, such as with services like Twitter or open APIs using OAuth , which App Engine also supports ( Python | Java ). You can also find out more about the general hybrid approach using OpenID for authentication combined with OAuth for data access through Google APIs.
Unsupported features
Although we're happy to introduce federated login, we would also like to emphasize that this is just the beginning: at the time of this writing, App Engine does not support other OpenID features such as Attribute Exchange (AX), AX Trust, Provider Auth Policy Extension (PAPE), OpenID User Interface Extension (UIEX), Remote API, nor secure/HTTPS service. Keep an eye on our public roadmap as well as our frequent release cycle for new features.
Conclusion
Federated authentication gives developers a fourth option of providing authentication services to their applications. Now, in addition to either requiring your users to have Google Accounts or be part of a Google Apps domain, you can offer them the ability to authenticate through a third-party OpenID provider such as Yahoo!, MySpace, and of course, Google. A federated login service brings universal single-sign on closer to reality and ultimately offers developers more choices and a more flexible and convenient user experience.