Webservice credentials - OpenID/Android AccountManager?

11,366

Solution 1

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.

Solution 2

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:

  1. 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;
    }
    
  2. Pass received UserToken to the server over secured channel.

  3. 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;
    }
    
  4. 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);
    }
    

Solution 3

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.

Share:
11,366
Tomer Weller
Author by

Tomer Weller

Updated on July 30, 2022

Comments

  • Tomer Weller
    Tomer Weller over 1 year

    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.

  • Peter Knego
    Peter Knego about 13 years
    The linked example does not use OpenID or Xauth, so it can not be used on GAE to authenticate against existing Google accounts. As I can see it uses it's own authentication scheme: simple POST with username/password as header parameters.
  • Tal Weiss
    Tal Weiss over 12 years
    +1 - very detailed! What does the custom URL text show? "Click this to return to the application"? Or something like that?
  • Peter Knego
    Peter Knego over 12 years
    It can show whatever you like. I used "Continue.." link that looks like a button. With some javascript you could possibly launch the app automatically, without requiring user to click on the link - didn't try this, it would need to be tested.
  • tony gil
    tony gil almost 12 years
    though @peterKnego has a point regarding the second recommendation, the first link is actually quite useful. but i do agree that more attention should go into formulating a proper answer, fmo. but be careful not to be downvoted in the future.
  • android developer
    android developer over 11 years
    is it possible to do the same (get the token to be used) for email messages on gmail and for facebook ? if so, can you please answer it on the next post : stackoverflow.com/questions/11532257/…
  • Karly
    Karly over 11 years
    But that doesn't solve the problem since he wants to authenticate the user on his own webservice with a database I guess, so if he wants to authenticate on his webservice he should send the token and his google ID on the webservice and the webservice should send the token on google server who send back a response with the google ID that you can compare with the google ID the user has sended, isn't it right ? how do you do that ?
  • thomas88wp
    thomas88wp over 11 years
    I tested this solution and it works great if you just need an authenticated email from your user. The upside is you can do everything in your app without a web browser. The downside is, you have to ask for permission to edit user's contacts, which some might be hesitant to do. Does anyone know of a way to just ask for authentication without asking for authorization?
  • Nilzor
    Nilzor about 11 years
    @thomas88wp you can follow this guide which uses the built in Google Account authorization of GAE: blog.notdot.net/2010/05/… . Note that this is only for Google Accounts, not OpenID
  • Nilzor
    Nilzor about 11 years
    Actually here's a better guide: android-developers.blogspot.no/2013/01/…