I have a simple Google App Engine backend and a simple Android Application and I want to do authenticated requests from the Android App to the server. I read about Google Cloud Endpoints and even if it is a really good API, I feel that it's a bit overkill for what I want to do. I just want to do an authenticated HTTP request and get the response text.
GET myappid.appspot.com/api/user
Should answer:
Hello john.doe
If the user [email protected] does the request.
I created a new App Engine project:
WEB_CLIENT_ID=123456789012.apps.googleusercontent.com
and registered an Android App ("Accessing APIs directly from Android"):
package name : com.myappid
debug SHA1 fingerprint: 3a:e1:05:17:15:54:c6:c7:9b:ef:19:74:ae:5b:f7:0f:c3:d5:45:9d
And this created
ANDROID_CLIENT_ID=123456789012-9f4sd525df3254s3d5s40s441df705sd.apps.googleusercontent.com
application: myappid
version: 1
runtime: python27
api_version: 1
threadsafe: true
handlers:
- url: /api/.*
secure: always
script: api.APP
libraries:
- name: webapp2
version: latest
- name: pycrypto
version: latest
import webapp2
from google.appengine.api import users
from google.appengine.api import oauth
class GetUser(webapp2.RequestHandler):
def get(self):
user = users.get_current_user()
self.response.headers['Content-Type'] = 'text/plain'
self.response.out.write('Hello, {}\n'.format('None' if user is None else user.nickname()))
try:
user = oauth.get_current_user()
self.response.out.write('Hello OAuth, {}\n'.format('None' if user is None else user.nickname()))
except Exception as e:
self.response.out.write(str(e)+'\n')
class SignIn(webapp2.RequestHandler):
def get(self):
if users.get_current_user() is None:
self.redirect(users.create_login_url(self.request.uri))
APP = webapp2.WSGIApplication([
('/api/user', GetUser),
('/api/signin', SignIn),
], debug = True)
public class MainActivity extends Activity
{
private static final String CLIENT_ID = "123456789012.apps.googleusercontent.com";
private static final String SCOPE = "audience:server:client_id:" + CLIENT_ID;
private static final int AUTH_REQUEST_CODE = 1;
private Account mAccount;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mAccount = AccountManager.get(mActivity).getAccountsByType(GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE)[0];
new GetAuthToken().execute(mAccount.name);
}
protected void log(String msg) {
TextView tv = (TextView) mActivity.findViewById(R.id.textView);
tv.setText(tv.getText() + "\n" + msg);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == AUTH_REQUEST_CODE) {
if (resultCode == RESULT_OK) {
new GetAuthToken().execute(mAccount.name);
}
}
}
private class GetAuthToken extends AsyncTask<String, Void, String> {
@Override
protected String doInBackground(String... params) {
try {
// Retrieve a token for the given account and scope. It will always return either
// a non-empty String or throw an exception.
String email = params[0];
String token = GoogleAuthUtil.getToken(mActivity, email, SCOPE);
return token;
} catch (GooglePlayServicesAvailabilityException playEx) {
Dialog alert = GooglePlayServicesUtil.getErrorDialog(playEx.getConnectionStatusCode(), mActivity, AUTH_REQUEST_CODE);
return "error - Play Services needed " + playEx;
} catch (UserRecoverableAuthException userAuthEx) {
// Start the user recoverable action using the intent returned by
// getIntent()
mActivity.startActivityForResult(userAuthEx.getIntent(), AUTH_REQUEST_CODE);
return "error - Autorization needed " + userAuthEx;
} catch (IOException transientEx) {
// network or server error, the call is expected to succeed if you try again later.
// Don't attempt to call again immediately - the request is likely to
// fail, you'll hit quotas or back-off.
return "error - Network error " + transientEx;
} catch (GoogleAuthException authEx) {
// Failure. The call is not expected to ever succeed so it should not be
// retried.
return "error - Other auth error " + authEx;
}
}
@Override
protected void onPostExecute(String result) {
if (result.startsWith("error -")) {
log(result);
} else {
log("Obtained token : " + result);
new GetAuthedUserName().execute(result);
}
}
}
private class GetAuthedUserName extends AsyncTask<String, Void, String> {
@Override
protected String doInBackground(String... params) {
try {
String token = params[0];
URL url = new URL("https://myappid.appspot.com/api/user");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//conn.setRequestProperty("Authorization", "Bearer " + token);
conn.addRequestProperty("Authorization", "OAuth " + token);
InputStream istream = conn.getInputStream();
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(istream));
String line;
StringBuilder sb = new StringBuilder();
while ((line = reader.readLine()) != null) {
sb.append(line);
}
return sb.toString();
} catch (IOException e) {
return "error - Unable to read from the connection";
}
} catch (MalformedURLException e) {
return "error - Malformed URL " + e;
} catch (IOException e) {
return "error - IO error " + e;
}
}
@Override
protected void onPostExecute(String result) {
if (result.startsWith("error -")) {
log(result);
} else {
log("Request result : " + result);
}
}
}
}
I can use my browser, to to
https://myappid.appspot.com/api/signin
login as John Doe and then
https://myappid.appspot.com/api/user
And I get
Hello, john.doe
Fantastic it's exactly what I expect.
With Android, I all my tries resulted in
Hello, None
As you can see in the Android code, I use GoogleAuthUtil to retrieve a token but I don't really understand what I'm supposed to do with it.
String token = GoogleAuthUtil.getToken(mActivity, email, SCOPE);
Then I build the request:
URL url = new URL("https://myappid.appspot.com/api/user");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
And add the "Authorization" header:
conn.setRequestProperty("Authorization", "Bearer " + token);
I also tried:
conn.addRequestProperty("Authorization", "OAuth " + token);
There is probably something missing on Android or on the App Engine backend but I really don't get what. Is there a piece of API that simplifies this ?
It seems so simple with a browser...
TIA
It is possible to send the access token to your Google App Engine app (or any other web application) (as a bearer token, it's all that is needed to forward credentials) however Google App Engine won't automatically recognize the "Authorization" header and set the user object for you (that's something Endpoints can help you with).
You could choose to find the access token yourself via the request headers object:
access_token = self.request.headers['Authorization']
Then send that to a Google API to verify it's valid and get information about that user (I think this includes email so long as email is a scope that you originally asked for an access token for).
See Get user info via Google API for details on how to do this.
You should also check that the access token has been issued to your application (https://www.googleapis.com/oauth2/v1/tokeninfo?access_token={access_token} - verify client ID in the response) - if you don't that makes it very easy for another application that has permission from the user to get an access token to make calls against your private API.
That all said, another mechanism is to get an IDToken from Android, and send that to your web application- more details can be found here: http://googledevelopers.blogspot.com/2013/05/cross-platform-sso-technology.html and https://developers.google.com/accounts/docs/CrossClientAuth
Sample showing using Google API Python Client to get information about an issued token:
from apiclient.discovery import build
print build('oauth2', 'v1').tokeninfo(access_token=access_token).execute()
# Result
{
'issued_to': 'xxxxxx.apps.googleusercontent.com',
'user_id': 'yyyyyy',
'expires_in': 3457,
'access_type': 'online',
'audience': 'xxxxxx.apps.googleusercontent.com',
'scope': 'https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile',
'email': '[email protected]',
'verified_email': True
}
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