Part 3: User Management
When we last left our hero… uh, I mean, heroic application,
it was happily running in the cloud, greeting anyone and everyone that happens
to wander by, possibly even by name, if they happen to provide it.
Which brings up an interesting problem: user identity. Many,
if not most, applications running on the Internet want or need to be able to
identify the user behind the browser in some unique fashion, for a variety of
reasons. Sometimes the application needs to display personal user information
and shouldn’t do so to anyone other than that particular user, sometimes the
application needs to verify the user’s identity before allowing them in to use
any part of it, and sometimes the application just wants to provide a more
personalized experience to the user. Whatever the reason, user identity
management is usually somewhere near the top of the "must-have" feature list
for a web app.
If you’re like a lot of developers, then, the next
discussion becomes a list of features surrounding the concept: CAPTCHAs to
verify human users, login screens, password strength rules, username uniqueness
verification, I-forgot-my-password screens and verification, email validation,
salted and encrypted passwords, and federated security systems like OAuth or
OpenID (or both).
Or, you just let Google worry about it.
User Management
One of the big advantages to running on top of the Google
Cloud Platform is that Google has built a metric crap-ton (that’s an actual unit of
measurement, by the way) of libraries and APIs to support it, all of which can
be utilized pretty easily from your Java-based applications running in the
Google Cloud Platform. In this particular case, we can leverage the Google
authentication and user management system (the same one that you use to get to
your Gmail and, if you followed directions from the last article, to create the
app’s project in the cloud in Google App Engine) to identify users. Instead of
building the login screens and email verification and all that, you just import
the right APIs, make the right method calls, and let Google do the heavy
lifting.
Flow
There are several different ways to think about how the
users will interact with the application, depending on business requirements.
One approach, common in a lot of applications both internal and external, is
that users cannot access any aspect of the application without a successful
login—in other words, the application is entirely opaque to anyone who is not
authorized to use it already. A slight variant of that approach (common with
most external, consumer-facing applications) is to allow people to apply for
membership into the exclusive club, but otherwise the application behaves the
same: without a successful login, no aspect of the application can be accessed.
Other applications prefer less binary distinctions about
authorization, and may allow certain portions of the application to be used by
anyone, while reserving certain features and/or benefits to those who have a
recognized set of credentials. (Most e-commerce systems usually fall into this
category: browse all you want, but you only get free shipping if you’re a
recognized member of our site so we can spam you and sell your email to the
highest bidder.)
From a Google App Engine developer’s point of view, the
distinction really isn’t important, except to know where to challenge the user
for credentials and what actions to take if the user doesn’t provide acceptable
ones. For example, an application that wants to follow the binary model of "all
in or all out", it may choose to set up a servlet filter to check for
established login credentials in the current session, and redirect the user to
a page explaining why they’re being redirected when no credentials are present.
My application, however, doesn’t want to be so harsh—it still wants to be warm
and friendly and all that, so I’ll ask for Google credentials, but still offer
up a greeting to those that aren’t Google-identifiable users. Just maybe not
quite as personal.
Packages, packages
The user management API lives in the package
com.google.appengine.api.users, so the first order of business is to import
that into the servlet.
import com.google.appengine.api.users.*;
Then, like so many Java APIs, we need to have a reference to
an object that acts as a gateway to the remainder of the API, and in the Google
User Service case, that’s a UserService
object, obtained via a call to the
static UserServiceFactory.getUserService()
method. This UserService
object,
along with the User object it will get for us (more on that in a minute), forms
a majority of the surface area a developer uses. In fact, it’s more notable how
small the API really is, compared with all the functionality that it provides.
Let’s start with the basics.
Who are you?
The UserService
object’s principal method of returning an
authenticated user is the getCurrentUser()
method, which returns (not
surprisingly) a User object containing the authenticated user’s credential
information (more on that in a second), or else null, indicating that no such
user has logged in. So, principally, checking to see if a user has
authenticated with the web app is a single method call, checking for a null
result:
public class HelloServlet extends HttpServlet
{
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException
{
UserService userService = UserServiceFactory.getUserService();
User user = userService.getCurrentUser();
if (user != null)
{
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<body>");
out.println("<p>Hello, " + user.getNickname() + "</p>");
out.println("</body>");
out.println("</html>");
}
else
{
}
}
}
The User object has a number of methods on it that describe
what Google knows about this user: their email address (getEmail
), their human
identifier (getNickname
, which may be their "handle", their email address, or
something else, but usually something that they’ve chosen for themselves), their
Google identifier (getUserId
), and a few other tidbits that can be useful in
select situations, like the domain (authentication system) used to authenticate
this user (getAuthDomain
) or the user’s federated identity
(getFederatedIdentity
), which will likely only be of use in cases where
Google’s authentication is used in conjunction with systems like OAuth or
OpenID.
In the case where the user isn’t someone that’s already
logged in with Google, we need to create a URL to which they can be redirected,
in order to capture their credentials (username and password). This is where
UserService
fulfills the other half of its function, by crufting up a URL with
Google’s authentication system that will capture the user’s information and
redirect it back to us:
else
{
String host = request.getProtocol() + "://" +
request.getServerName() +
(request.getServerPort() != 80 ? request.getServerPort() : "");
String loginUrl = userService.createLoginURL(host + "/hello");
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<body>");
out.println("<p>Please <a href='" + loginUrl + "'>log in</a> " +
+ "for a nice, friendly greeting!");
out.println("</body>");
out.println("</html>");
}
Notice that the createLoginUrl
method takes a string
parameter, which is the page to which the user should be redirected after
successful authentication. In the case of this ridiculously simplified
application, that’s the same page we came from, but in other applications, that
would most often be the home page of the application, chock full of
menu-selection goodness.
Bear in mind, if this endpoint is a JSON-delivered data-only
endpoint (such as what a mobile app would be using), then the createURL method
won’t be of much use; instead, the servlet would probably return a 400-class
error code (probably a 401), indicating that the servlet cannot return data
without authentication, and the mobile application will then have to present a
native login screen to gather those credentials. More of this will come when we
cover the Google mobile endpoints API, a specific set of APIs geared towards
building these kinds of endpoints, in a later article.
Role-playing games
Although I personally dislike mixing user-facing and
admin-facing applications into the same web application (since it heightens the
possibility that an accidental information disclosure will occur), many systems
want to distinguish between "normal" users and "admin" users, in order to
display different links, pages, or information. The User object doesn’t support
a full role-based security system, but it does allow for distinguishing between
"admin" and "non-admin" users, via the isAdmin()
method call on the User
object. (Users who are identified as "admin" users are those who are granted
that status in the application’s project page in the administrative console.)
Testing, testing, testing
It’s important to note that this application now takes a
dependency on both the Google services and the Internet, because if the Google
services are down, or, more likely, your connection to the Internet is severed
or unstable, this application will never be able to serve authentication
requests. Granted, if the Internet connection is down, you won’t be able to
much of anything else, either, if it happens in Production, but during testing,
lots of developers are offline, or more importantly, don’t want to have to type
in username/password combinations all the time.
For this very reason, Google has defined the UserService as
an interface, so that any competent dependency-injection container or API can
inject a mocked UserService
object that returns a known constant, to enable
fully-automated testing.
But my boss doesn’t like Google…
Unfortunately, it is a fact of developer life that a lot of
elegant solutions aren’t workable because the boss (or the customers, or the
government, or whomever else is upstream on the "client" side of the
discussion) has some kind of problem with using said elegant solution. We can
moan and grumble and make comments like "idiot users" all we like, but the fact
remains, they pay the bills, they get to make those
decisions. (And in all honesty, the legal and liability implications of a third
party holding your users authentication data is still somewhat up in the air,
and for some businesses, that is too much risk to take on.)
Or, perhaps your users are currently authenticating using a
different system, other than Google’s, and the thought of trying to
mass-migrate them out of the current system and into Google’s is enough to make
your system administrator’s head spin a few times. Or, perhaps your users
aren’t actually a part of any single system at all, or are actually consumers,
and you want to enable easy authentication for them using Facebook or Twitter
or LinkedIn or….
Fortunately, Google has an OAuth user provider, and it works
almost identically to the UserService approach we showed above. An
OAuthServiceFactory uses the static getOAuthService()
method to return an
instance of an OAuthService
object, which in turn can be used to check to see
the current user’s OAuth credentials, in the form of a User object from the
UserService API above. It makes for a pretty plug-and-play replacement. (Note that
a developer still needs to set up a link to the OAuth provider that will do the
authentication, as described by the OAuth provider’s documentation. So, for
example, if the application wants to allow Facebook to do the authentication,
the developer must redirect the user to the Facebook OAuth endpoint, according
to Facebook’s documentation, and set up a redirect back to this application.
This is all standard OAuth fare, available from any OAuth tutorial, and outside
the scope of this discussion.)
It’s highly unlikely that one would use both systems for
authentication, since then there would be two entirely separate "master user
lists", but I’m pretty sure that as soon as I say that, somebody will have a
legitimately good reason to do it, so just be careful if you do use both
simultaneously.
Summary
Google Cloud Platform makes a number of APIs available that
reduce the overall work that a developer has to perform to create a
"ready-for-production" application. The user and OAuth services described above
are just the tip of the iceberg; a complete Javadoc is available at https://developers.google.com/appengine/docs/java/javadoc/.
In the next piece, we’re going to look at the next thing that
almost all applications have to do—store data—and the various mechanisms that
Google Cloud Platform provides for doing so. But for now, happy coding!