Android – GoogleAuthUtil.getToken Exception error: Unknown

There are a lot of guides out there to do OAuth2 authentication through Google on an Android application. I’ve been reading several of them, javadocs, Stackoverflow questions and so on, trust me. Finally, after got connected to Google I wanted to retrieve the token for the user connected. For that I used the GoogleAuthUtil class. Here is a snippet of the code I’ve written to get the token:


    /**
     * Request code for auto Google Play Services error resolution.
     */
    private static final int REQUEST_CODE_RESOLUTION = 89898;

    /**
     * User connected token.
     */
    private String mToken;

    /**
     * Task for retrieving the token of the connected user
     */
    private class GetTokenTask extends AsyncTask<String, Integer, String> {
        @Override
        protected String doInBackground(String... userAccount) {
            String token = null;
            try {
                Log.d(TAG, "Retrieving token for [" + userAccount[0] + "]");
                token = GoogleAuthUtil.getToken(getActivity(), userAccount[0], Scopes.PROFILE, new Bundle());
            } catch (UserRecoverableAuthException e) {
                Log.w(TAG, "Error retrieving the token: " + e.getMessage());
                Log.d(TAG, "Trying to solve the problem...");
                startActivityForResult(e.getIntent(), REQUEST_CODE_RESOLUTION);
            } catch (IOException e) {
                Log.e(TAG, "Unrecoverable I/O exception: " + e.getMessage(), e);
            } catch (GoogleAuthException e) {
                Log.e(TAG, "Unrecoverable authentication exception: " + e.getMessage(), e);
            }
            return token;
        }

        @Override
        protected void onPostExecute(String token) {
            if (token != null) {
                mToken = token;
                Log.d(TAG, "We have a token!: " + token);
            } else {
                Log.d(TAG, "Can't retrieve any token");
            }
        }
    }

But always got the same error:

09-05 13:07:19.109  23577-25973/com.xxxxx E/Login﹕ Unrecoverable authentication exception: Unknown
    com.google.android.gms.auth.GoogleAuthException: Unknown
            at com.google.android.gms.auth.GoogleAuthUtil.getToken(Unknown Source)
            at com.xxxxx.views.LoginFragment$GetTokenTask.doInBackground(LoginFragment.java:232)
            at com.xxxxx.views.LoginFragment$GetTokenTask.doInBackground(LoginFragment.java:225)
            at android.os.AsyncTask$2.call(AsyncTask.java:288)
            at java.util.concurrent.FutureTask.run(FutureTask.java:237)
            at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231)
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
            at java.lang.Thread.run(Thread.java:841)

The error is all but descriptive. After browsing lot of pages I discovered the trick, the scope was wrong!. Well, the scope seems right, but it lacks a string header with the value ‘oauth2:’:

final String scope = "oauth2:" + Scopes.PROFILE;
token = GoogleAuthUtil.getToken(getActivity(), userAccount[0], scope, new Bundle());

After that, everything went right. Hope this helps!

Advertisements

Redirect after AJAX request / Control authentication failure after AJAX request

Last two days I’ve been dealing with a problem I’ve had with some of the projects I work on. For our Java web apps we use an authentication/authorization filter that redirects to a login page after an authentication/authorization error. This error can be a login error, a session timeout or other things.

Redirect calls are managed by browsers without problems, they do the request for the new location and the login page is presented to you. But if your app does AJAX requests, as every app nowadays does, you’ll get into troubles, the browser won’t do any redirection, so your app will stay in the same page as before the request, and probably the user will be annoyed because your app doesn’t respond or does strange things. The login page won’t be shown until the user clicks on a non AJAX component (menu, link or so), or does a refresh.

Usually the authentication filter does a sendRedirect to the login page when there is an authentication problem:

response.sendRedirect(response.encodeRedirectURL(url));
response.flushBuffer();

So after some research I found that doing a manual redirect should solve the problem with AJAX calls. Really I didn’t know why doing this would solve the problem, but I tried it:

...
if (isAjax(request)) {
    StringBuilder sb = new StringBuilder();
    sb.append("<?xml version='1.0' encoding='UTF-8'?>");
    sb.append("<partial-response><redirect url=\"").append(response.encodeRedirectURL(url.toString())).append("\"/></partial-response>");
    response.getWriter().print(sb.toString());
    response.flushBuffer();
}

private boolean isAjax(HttpServletRequest request) {
    return "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
}

Even I tried to send a redirect HTTP’s 302 code or adding a Location header… without any succeed:

if (isAjax(request)) {
    response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
    response.setHeader("Location", response.encodeRedirectURL(url.toString()));
    StringBuilder sb = new StringBuilder();
    sb.append("<?xml version='1.0' encoding='UTF-8'?>");
    sb.append("<partial-response><redirect url=\"").append(response.encodeRedirectURL(url.toString())).append("\"/></partial-response>");
    response.getWriter().print(sb.toString());
    response.flushBuffer();
}

private boolean isAjax(HttpServletRequest request) {
    return "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
}

Then I tried to solve it from the other side, thus is, the client. I didn’t like it, because it is easier to distribute a JAR file with the authentication filter solution than telling people what they must do in every app they are developing. Although we have projects with JSP/JQuery or Seam 2, the last apps we’re working on use Primefaces, so I tried to find a solution related to this framework. First I tried to catch onComplete events within ajaxStatus Primefaces tag, but this component doesn’t pass the XMLHttpRequest argument to the callbacks, also I tried to bind a callback to onComplete events at body or document level, but any result was good.

Finally I went for a more drastic and I think the most secure solution: Setting up every AJAX call that could be made within our apps to do what I want when an authentication error raised. For this, JQuery has a solution, the $.ajaxSetup function. With it you can define at a global level how AJAX calls behave, and when I say at a global level I mean that every AJAX call from your code, or from whatever library or framework you are using will do what you define with this function. So first of all I changed the HTTP code the filter was sending, instead of a 302 now it sends a 401 (unauthorized), then I defined the behaviour of AJAX calls after receiving this code:

First the changes in the Java code:

if (isAjax(request)) {
    response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
    response.setHeader("Location", response.encodeRedirectURL(url.toString()));
    response.flushBuffer();
}
private boolean isAjax(HttpServletRequest request) {
    return "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
}

Now the Javascript code:

function requestUnauthorized(xhr) {
    window.location.href = xhr.getResponseHeader("Location");
}
 
(function($) {
    $.ajaxSetup({
        statusCode: {
            401: requestUnauthorized,
        }
    });
})(window.jQuery);

The syntax…

(function($){.....})(window.jQuery) 

…means that you’re defining an anonymous function with an parameter called $, that will be self-executed with window.jQuery as value. It’s better to use window.jQuery because some libraries or frameworks rename jQuery to $, $$ or whatever they want.

And that’s all, you have to put the Javascript code somewhere that is visible for the whole app.