Sunday, September 13, 2015

Google App Engine and Google Authentication

One of the things that I love about the year 2015 is "cloud computing"; in particular, I love that I can hand Google App Engine a Java project and it will host it, handle redundancy, auto-scale it, provide me a database, and do just about everything else that I could ever want.

However, security is still a major concern, and there are some applications that I work on where "leaking" some private data onto the public Internet is bad news.

User authentication

There are lots of ways to authenticate users at this point.  Years ago, we did everything ourselves (remember those days?).  Each application had its own user database with varying degrees of security, and passwords were being stolen and sold all the time.  Now we have things like OAuth, where we can pass off user authentication to other systems (which we have to trust), so we don't have to store anything more than an e-mail address.  If the OAuth server says that that e-mail address is legit and logged in, then it's legit and logged in.  This saves us time and money, since who wants to build and maintain a user authentication layer, anyway?

Google App Engine provides a pretty easy and awesome way to lock down your application to "signed in" users.  Here, "signed in" users can be one of two things:
  1. Any user on Earth with a Google account; or
  2. Any user in your Google Apps domain.
If you have a Google Apps domain, then with a couple of tweaks to both the domain and the app, then Google will make sure that only those people in the domain can log in to the app.  Otherwise, if you'll have to check for a particular domain from your logged-in users in your REST API calls (your app is RESTful, right?).  No biggie, either way.

(In this article, I'll be talking about the Java version of the Google App Engine SDK, so any files or calls will be related to that.)

To set up your app to force everything to require a logged-in user, you just need to update "web.xml" and add a "security-constraint" (obviously, you can play a lot with this):
<security-constraint>
<web-resource-collection>
<web-resource-name>site</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>*</role-name>
</auth-constraint>
</security-constraint>

The "url-pattern" of "/*" means "MATCH ALL THE URLS!", and the "role-name" of "*" means "all users signed in with a Google account".

I can't log out!

I recently put together an application for a group where I don't own the Google Apps domain, so I couldn't set up the sweet domain-level restriction at the app level.  Like I said, it's no big deal, so at the top of my API class was a check for "@myspecialdomain.org" in the user's e-mail address (remember, Google already verified that it was a Google account, so I just needed to make sure that it was in the right domain).

I asked the group to try out the application, and half loved it; the other half couldn't see any data.  It turned out those people logged in with their personal accounts (not their organization-specific accounts), and now were stuck in an application where they had no access to any data.

What I needed was a "switch accounts" button (like all of the other Google applications), or at least a way to invalidate the user's session so that they could log in again (and use their organization account, this time).  Google App Engine's built-in authentication system does some magic, so the usual Google authentication guides don't apply.  I tried all of the stuff that people online were talking about (invalidating sessions, deleting cookies, etc.), but none of it worked, and it seemed like a hack anyway.

It turns out that Google App Engine provides a "UserService" class help deal with this kind of problem.  In particular, you may request a login URL and a logout URL.  Since my whole app requires the user to be logged in, all I needed to do was log the user out, and he would be immediately redirected to the Google login/pick-account screen again.

I added a simple API call to return some information about the current user (so I could show the "Logged in as ..." message), and that call returned an additional property for the logout URL (remember, this URL is generated by Google App Engine and isn't trivial).  When my UI loads, it makes an AJAX call for the user information, grabs the logout URL, and provides a "Logout" link for any users that need to log out.

Problem solved.

Here's how to get the logout URL:
UserService userService = UserServiceFactory.getUserService();
String logoutUrl = userService.createLogoutURL("/");

The "/" argument to "createLogoutUrl" is where the user should be redirected once he logs out.  In this case, I'm sending him right back to the main screen of the app, where he will be asked to log in again.

Additional resources

  1. For more information on "web.xml" within Google App Engine, see this document.
  2. To learn how to set up authentication with Google App Engine, see this article.

No comments:

Post a Comment