I noticed that adding authentication to an existing AppEngine endpoint is very simple - you just need to add the com.google.appengine.api.users.User parameter to your method. And finally, I discovered what is happening under the hood, and how to authenticate an arbitrary servlet in the same way. Therefore, for authentication on the Android side you will need: 1) select an account:
private void signIn() { startActivityForResult(GoogleAccountCredential.usingAudience(this, "server:client_id:{id}.apps.googleusercontent.com").newChooseAccountIntent(), REQUEST_ACCOUNT_PICKER); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case REQUEST_ACCOUNT_PICKER: if (data != null && data.getExtras() != null) { String accountName = data.getExtras().getString(AccountManager.KEY_ACCOUNT_NAME); if (accountName != null) {
2) get a Credential object:
GoogleAccountCredential credential = GoogleAccountCredential.usingAudience(this, "server:client_id:{id}.apps.googleusercontent.com"); credential.setSelectedAccountName(accountName);
3) create a Google HttpRequest object and execute the request:
HttpTransport transport = new NetHttpTransport(); HttpRequestFactory requestFactory = transport.createRequestFactory(credential); GenericUrl url = new GenericUrl(UPLOAD_SERVICE_URL); HttpRequest request = requestFactory.buildGetRequest(url); HttpResponse resp = request.execute();
To authenticate a request on the AppEngine side, you can use the WebApisUserService inner class declared in the appengine-endpoints.jar library. This is just the class used by AppEngine inside endpoints. Unfortunately, this class constructor and other necessary methods are protected from external use, so we need to use reflection to access it. The full helper class is as follows:
public class WebApisUserServiceHelper { public static WebApisUserService createInstance(boolean isClientIdWhitelistEnabled) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { Constructor<WebApisUserService> constructor; constructor = WebApisUserService.class.getDeclaredConstructor(Boolean.TYPE); constructor.setAccessible(true); WebApisUserService ret = constructor.newInstance(isClientIdWhitelistEnabled); return ret; } public static User getCurrentUser(WebApisUserService service, HttpServletRequest req, String appName, List<String> audiences, List<String> clientIds) throws NoSuchMethodException, SecurityException, ClassNotFoundException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { String token = getAuthToken(service, req); if (token != null) { List<String> allowedScopes = new ArrayList<String>(); allowedScopes.add("https://www.googleapis.com/auth/userinfo.email"); return getCurrentUser(service, token, allowedScopes, audiences, clientIds); } return null; } private static User getCurrentUser(WebApisUserService service, String token, List<String> allowedScopes, List<String> allowedAudiences, List<String> allowedClientIds) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { Method method = WebApisUserService.class.getDeclaredMethod("getCurrentUser", String.class, List.class, List.class, List.class); method.setAccessible(true); Object ret = method.invoke(service, token, allowedScopes, allowedAudiences, allowedClientIds); if (ret instanceof User) return (User) ret; return null; } private static String getAuthToken(WebApisUserService service, HttpServletRequest request) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { Method method = WebApisUserService.class.getDeclaredMethod("getAuthToken", HttpServletRequest.class); method.setAccessible(true); Object ret = method.invoke(service, request); if (ret instanceof String) return (String) ret; return null; } }
and here is how to use this helper:
public class MyServlet extends HttpServlet { private final WebApisUserService auth = createAuthService(); private static WebApisUserService createAuthService() { try { return WebApisUserServiceHelper.createInstance(false); } catch (Exception e) { log.log(Level.WARNING, "Failed to create WebApisUserServiceFactory instance. Exception: %s", e.toString()); } return null; } @Override public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { try { User user = authenticateUserSafe(req); if (user == null) { resp.sendError(401, "auth required"); return; } String str = String.format("User id: %s, nick: %s, email: %s", user.getUserId(), user.getNickname(), user.getEmail()); resp.getWriter().write(str); } catch (Throwable e) { resp.getWriter().write("Exception: " + e); } } private User authenticateUserSafe(HttpServletRequest req) { try { return authenticateUser(req); } catch (Exception e) { log.log(Level.WARNING, "Failed to authenticate user. Exception: %s", e.toString()); } return null; } private User authenticateUser(HttpServletRequest req) throws NoSuchMethodException, SecurityException, ClassNotFoundException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { List<String> audiences = new ArrayList<String>(); audiences.add(Ids.ANDROID_AUDIENCE); List<String> clientIds = new ArrayList<String>(); clientIds.add(Ids.WEB_CLIENT_ID); clientIds.add(Ids.ANDROID_CLIENT_ID); return WebApisUserServiceHelper.getCurrentUser(auth, req, "{id}", audiences, clientIds); } }
This approach has been tested with AppEngine 1.8.6. I hope that Google will open the WebApisUserService class publicly, so no response is needed.