I'm building a webservice and would like to use the user's google account credentials.
The service runs on GAE and will have a web client and an Android native client.
This is my first attempt of something like this and I've been reading about OpenID and the Android AccountManager library.
I'm still not sure what are my options in terms of storing the users in my Datastore. What Identifier should I use ? Is it possible to use OpenID on a native Android application ?
Any help and/or pointers would be appreciated. Thanks.
We had a similar requirements on the last project: GAE backend with GWT frontend and Android/iPhone clients. Also, we did not want to store user credentials.
So we choose to use OpenID, which is unfortunately a Web standard and does not play well with mobile devices, but is doable.
On the GAE side we simply enabled federated login which gave us OpenID.
On mobile devices, when user needs to login we present to them a list op OpenID authenticators (Google, Yahoo, etc..). Then we open a native browser (not embedded browser) and direct user to chosen OpenID authentication site. The upside is that user's browser usually already has username/pass remembered, so this step just requires user to press one button.
This is all pretty straightforward. Now here is the tricky part: After user confirms login, OpenID redirects back to our GAE return url (you need to provide this url when request is made). On this url we create a custom URL, for example:
yourappname://usrname#XXXYYYZZZ
where XXXYYYZZZZ is auth token. We get this token from the return page where it's stored as an ACSID cookie: we used some JSP to read this cookie and wrap it into above custom URL.
Then we register our Android and iPhone apps to handle the yourappname://
URLs, so that when user cliskc this link, our app is invoked and the link is passed to it. We extract user name and token from this link and we use it in REST requests to the GAE backend.
If you have any more questions I'd gladly update this post.
Update:
The user session cookie on production AppEngine is named ACSID
, while on development AppEngine server it's named dev_appserver_login
.
I spent about a week to find a suitable and modern looking way for this - without web browser and by using android account manager.
If you would like to use Google account and AccountManager to identify the user you can:
Get his token to Google Contacts (auth token type is "cp") through AccountManager on background thread:
public String getUserToken(Activity activity)
{
AccountManager accountManager = AccountManager.get(activity);
AccountManagerFuture<Bundle> amf = accountManager.getAuthTokenByFeatures("com.google", "cp", null, activity, Bundle.EMPTY, Bundle.EMPTY, null, null );
Bundle bundle = null;
try {
bundle = amf.getResult();
String name = (String) bundle.get(AccountManager.KEY_ACCOUNT_NAME);
String type = (String) bundle.get(AccountManager.KEY_ACCOUNT_TYPE);
String token = bundle.getString(AccountManager.KEY_AUTHTOKEN);
return token;
} catch (OperationCanceledException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (AuthenticatorException e) {
e.printStackTrace();
}
return null;
}
Pass received UserToken to the server over secured channel.
Validate the token at the server by google using gdata library (Google Data API library):
public String getUserId(String token)
{
ContactsService contactsService = new ContactsService("Taxi");
contactsService.setUserToken(token);
IFeed feed = null;
try {
feed = contactsService.getFeed(new URL("https://www.google.com/m8/feeds/contacts/default/full?max-results=10000"), ContactFeed.class);
} catch (IOException e) {
e.printStackTrace();
} catch (ServiceException e) {
e.printStackTrace();
} catch (NullPointerException e) {
e.printStackTrace();
}
if (feed == null)
return null;
String externalId = feed.getId();
IPerson person = feed.getAuthors().get(0);
String email = person.getEmail();
String name = person.getName();
String nameLang = person.getNameLang();
return externalId;
}
Google token can expire (usually after an hour), so if you failed to validate the token at the server, you must send response back to client, invalidate the token and get a new one. Use account manager to invalidate the token:
public void invalidateUserToken(Context context, String token)
{
AccountManager accountManager = AccountManager.get(context);
accountManager.invalidateAuthToken("com.google", token);
}
I think this blog post does exactly what you want. It worked for me. Both of the solutions posted here are viable and clever, but I think this does it exactly how the asker was asking.
Essentially, you're just getting an authToken using the "ah" scope, and passing it to the right webpage to get the ACSID cookie that will let you access any AppEngine page that uses UserService for authentication.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With