I am attempting to login to appengine to app engine and access the User service API in app engine. Basically I want to be able to see who is logged in to my servlets. I am using the authentication flow of getting authtoken from android and then gettting a ASID(or SACID) cookie from app engine. The cookie is then sent with the http request to the appengine servlet. This seems to work all fine, however when I attempt to get the user with this code:
UserService userService = UserServiceFactory.getUserService();
User user= userService.getCurrentUser();
user is always null. My question is am I missing something here? Why is the user service returning a null user? Below is my appengine and android code. Any help would be greatly appreciated!
App engine:
public class MyServlet extends HttpServlet {
public void process(HttpServletRequest req, HttpServletResponse resp)
throws IOException, ServletException {
resp.setContentType("text/plain");
UserService userService = UserServiceFactory.getUserService();
User user= userService.getCurrentUser();
}
public void doPost(HttpServletRequest req, HttpServletResponse resp)
throws IOException, ServletException {
process(req, resp);
}
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException, ServletException {
process(req, resp);
}
}
Android code:
public class AppEngineClient {
static final String BASE_URL = Util.getBaseUrl(this);
private static final String AUTH_URL = BASE_URL + "/_ah/login";
private static final String AUTH_TOKEN_TYPE = "ah";
private final Context mContext;
private final String mAccountName;
private static final String TAG = "AppEngineClient";
public AppEngineClient(Context context, String accountName) {
this.mContext = context;
this.mAccountName = accountName;
}
public HttpResponse makeRequest(String urlPath, List<NameValuePair> params) throws Exception {
HttpResponse res = makeRequestNoRetry(urlPath, params, false);
if (res.getStatusLine().getStatusCode() == 500) {
res = makeRequestNoRetry(urlPath, params, true);
}
return res;
}
private HttpResponse makeRequestNoRetry(String urlPath, List<NameValuePair> params, boolean newToken)
throws Exception {
// Get auth token for account
Account account = new Account(mAccountName, "com.google");
String authToken = getAuthToken(mContext, account);
if (newToken) { // invalidate the cached token
AccountManager accountManager = AccountManager.get(mContext);
accountManager.invalidateAuthToken(account.type, authToken);
authToken = getAuthToken(mContext, account);
}
// Get SACSID cookie
DefaultHttpClient client = new DefaultHttpClient();
String continueURL = BASE_URL;
URI uri = new URI(AUTH_URL + "?continue=" +
URLEncoder.encode(continueURL, "UTF-8") +
"&auth=" + authToken);
HttpGet method = new HttpGet(uri);
final HttpParams getParams = new BasicHttpParams();
HttpClientParams.setRedirecting(getParams, false); // continue is not used
method.setParams(getParams);
HttpResponse res = client.execute(method);
Header[] headers = res.getHeaders("Set-Cookie");
if (res.getStatusLine().getStatusCode() != 302 ||
headers.length == 0) {
return res;
}
String sascidCookie = null;
for (Header header: headers) {
if (header.getValue().indexOf("SACSID=") >=0) {
// let's parse it
String value = header.getValue();
String[] pairs = value.split(";");
ascidCookie = pairs[0];
}
}
// Make POST request
uri = new URI(BASE_URL + urlPath);
HttpPost post = new HttpPost(uri);
UrlEncodedFormEntity entity =
new UrlEncodedFormEntity(params, "UTF-8");
post.setEntity(entity);
post.setHeader("Cookie", ascidCookie);
post.setHeader("X-Same-Domain", "1"); // XSRF
res = client.execute(post);
return res;
}
private String getAuthToken(Context context, Account account) throws PendingAuthException {
String authToken = null;
AccountManager accountManager = AccountManager.get(context);
try {
AccountManagerFuture<Bundle> future =
accountManager.getAuthToken (account, AUTH_TOKEN_TYPE, false, null, null);
Bundle bundle = future.getResult();
authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN);
if (authToken == null) {
throw new PendingAuthException(bundle);
}
} catch (OperationCanceledException e) {
Log.w(TAG, e.getMessage());
} catch (AuthenticatorException e) {
Log.w(TAG, e.getMessage());
} catch (IOException e) {
Log.w(TAG, e.getMessage());
}
return authToken;
}
public class PendingAuthException extends Exception {
private static final long serialVersionUID = 1L;
private final Bundle mAccountManagerBundle;
public PendingAuthException(Bundle accountManagerBundle) {
super();
mAccountManagerBundle = accountManagerBundle;
}
public Bundle getAccountManagerBundle() {
return mAccountManagerBundle;
}
}
}
The Android code above is getting a ClientLogin token from the Google Accounts API. For login and getting the current user via
UserService
, the GAE app must be using Google Accounts API for authentication as well ('Application settings'->'Authentication options').I noticed that it is very easy to add authentication to existing AppEngine endpoint - you just need to add com.google.appengine.api.users.User parameter to your method. And finally I found what is going under the hood and how to authenticate in the same way arbitrary servlet. So to authenticate on Android side you need: 1) choose account:
2) get Credential object:
3) create Google HttpRequest object and make the request:
To authenticate the request on AppEngine side you can use internal class WebApisUserService declared in "appengine-endpoints.jar" library. This is just the class used by AppEngine internally in endpoints. Unfortunately this class constructor and other required methods are protected from external usage so we need to use the reflection to access it. Complete helper class is the following:
and here is how to use this helper:
This approach checked with AppEngine 1.8.6. I hope Google will open WebApisUserService class publicly so reflection won't be required.