Google Cloud Endpoints Tutorial – Part 7

Welcome to Part 7 of the Google Cloud Endpoints Tutorial.

The full series:

  • Part 1 : We looked at writing a Google Cloud Endpoints class manually by using the various annotations and Exception classes that are available. We create a Quotes API that provided a JSON + REST based interface to manage Quotes (add, modify, delete and retrieve quotes).
  • Part 2 : We generated the Google Cloud Endpoints class from a JDO Annotated Entity class by using the Code Generation tools provided in the library.
  • Part 3 : Generated the Cloud Endpoints Client Library for Android and wrote an Android application that invokes the Endpoints API.
  • Part 4: We wrote a JavaScript client for our Endpoints API.
  • Part 5: Securing our API
  • Part 6: Calling a Secured API from a JavaScript client.
  • Part 7 : Calling a Secured API from an Android client.

IMPORTANT

This series covers Google Endpoints with Eclipse.

If you are looking for using Google Cloud Endpoints with Android Studio, I recommend my series on Gradle, especially the following parts:

  • Part 8 : Gradle + App Engine + Cloud Endpoints + Android Studio
  • Part 9 : Gradle + App Engine + Cloud Endpoints (Persistence) + Android Studio
  • Part 10 : Consuming Endpoints in your Android application

 

I have also published a list of Cloud Endpoints Tips:

  1. Check Endpoint Deployment Status
  2. Throw the Right Exception Classes
  3. Understand Injected Types
  4. Understand @API and @APIMethod Annotations
  5. Using Cursor and Limit parameters
  6. The @APIResourceProperty Annotation

In this episode

So far in this series, we looked at writing a Quote API and in the last couple of episodes, we introduced how to secure your API methods so that only authorized clients can invoke the methods and we demonstrated how to make secure calls from our JavaScript client.

In this episode, we shall see how to make secure API calls from our Android client.

What do you need ?

  • You have a working development environment for Google App Engine. This includes the Google Eclipse plugin.
  • The API Project 2 (Quotes Endpoint Project) loaded in your Development Environment. This is the same as the previous episode.
  • You have a working development environment for Android. I have used Eclipse with the ADT Plug-in.
  • Basic understanding of Android Application Development. We will be using the same code that we had used in Part 3 : Writing an Android client.

My Development Environment

  • This remains the same, no changes at all. My development environment is given below:
    • Eclipse Juno
    • Google Eclipse plugin with App Engine SDK 1.8.8
    • Android ADT Plugin with latest Android SDK installed.
    • Mac + Windows machine (I kept switching from one to another, to keep everyone happy ;-))

Invoking the Secure API from Android client

In the last post, we had demonstrated how to make a secured call to the insertQuote method from a JavaScript client. In the Android version of that in this episode, we will be using the same code as we have used in Part 3, except that in the Add Quote Activity, we shall be modifying our code to make secured calls from the Android native application.

Let us first revisit the whole thing about Web Client Id and Audiences that we had setup for our Quotes API earlier.

Web Client Id

If you recollect, in the earlier episode, we had visit the Google Cloud Console for our project and create the OAuth Client Id. A screenshot of the Web Client Id is shown below.

Screen Shot 2014-02-14 at 8.45.01 PM

We created a Constants.java file that contained all these IDs as given below:

package com.mindstorm.famousquotes.entity;
public class Constants {
public static final String WEB_CLIENT_ID = "756161739003-0l5c7ptti2j42l28j5anijr5mukks835.apps.googleusercontent.com";
public static final String ANDROID_CLIENT_ID = "756161739003-9e1gefoeo2f9tsnrbjchtsn2shfq7q8k.apps.googleusercontent.com";
public static final String ANDROID_AUDIENCE = WEB_CLIENT_ID;
public static final String EMAIL_SCOPE = "https://www.googleapis.com/auth/userinfo.email";
}
view raw Constants.java hosted with ❤ by GitHub

Notice that the ANDROID_AUDIENCE is what we are going to use here and it has been set to the same value as the WEB_CLIENT_ID.

Additionally, we had updated the @APIMethod for insertQuote and provided the clientIds, that included the Web Client Id and then deployed the API.

@ApiMethod(name = "insertQuote", scopes = {Constants.EMAIL_SCOPE},
clientIds = {Constants.WEB_CLIENT_ID,
Constants.ANDROID_CLIENT_ID,
com.google.api.server.spi.Constant.API_EXPLORER_CLIENT_ID},
audiences = {Constants.ANDROID_AUDIENCE})
public Quote insertQuote(Quote quote, User user) throws UnauthorizedException {
if (user == null) throw new UnauthorizedException("User is Not Valid");
PersistenceManager mgr = getPersistenceManager();
try {
if (quote.getId() != null) {
if (containsQuote(quote)) {
throw new EntityExistsException("Object already exists");
}
}
mgr.makePersistent(quote);
} finally {
mgr.close();
}
return quote;
}

Again notice that we have specified the Client Ids that can access the insertQuote method and also mentioned the ANDROID_AUDIENCE in the audiences attribute.

Remember to regenerate your Client Libraries, just in case you have not done so and deploy it as needed. 

Famous Quotes Android Application Code

We are going to use the same code base as the original Famous Quotes Android application that was covered in Part 3I strongly suggest to go through that first to ensure that you have your Android application and all dependencies set up correctly.

If you want to download that source code, it is available here.

Play Services SDK

Since we are going to make an authenticated call from the Android client, the process will be that we will use the currently logged in Google Account on your Android device. Since this requires Account Picker and other classes on Android from the Play Services Library, it is required that you download the correct Play Services SDK and set that up as a library in the application.

Make sure you link up to the correct library depending on the version that you want to target. I tried out my code on a Android 2.3 device too and it bombed with the latest version of the Play Services SDK. As a result of which, I had to link up to a Froyo version of the library.

So, the first thing to do is to ensure that you have the Play Services SDK. For e.g. in the Android SDK Manager, you can see it over here:

cep7-1

Once you install (download) the above packages, they will be available in the ADT Folder\sdk\extras\google folder as given below:

cep7-2

Simply go to your Eclipse environment and Import any of the above projects as applicable. These are Android library projects and make sure you are able to import it successfully. 

Next, simply add the Play Services library project to your Famous Quotes Android application. The Properties page with the library linked up successfully is shown below:

cep7-3

This completes the development environment setup.

Let us look at the parts of the Add Quote Activity that we modified to ensure that the correct credentials are passed over behind the scenes while making the secure calls from the Android client.

AddQuote Activity Source Code

The entire source code for the AddQuote Activity is shown below:

package com.mindstorm.famousquotesandroid;
import android.accounts.AccountManager;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import com.google.api.client.extensions.android.http.AndroidHttp;
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential;
import com.google.api.client.json.gson.GsonFactory;
import com.mindstorm.famousquotes.entity.quoteendpoint.Quoteendpoint;
import com.mindstorm.famousquotes.entity.quoteendpoint.model.Quote;
import com.google.android.gms.common.AccountPicker;
import com.google.android.gms.common.GooglePlayServicesUtil;
public class AddQuoteActivity extends Activity {
private static final int REQUEST_ACCOUNT_PICKER = 2;
EditText editAuthorName;
EditText editMessage;
private SharedPreferences settings;
private String accountName;
private GoogleAccountCredential credential;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_addquote);
editAuthorName = (EditText)findViewById(R.id.editAuthorName);
editMessage = (EditText)findViewById(R.id.editMessage);
//Event Listener for About App button
Button btnAddQuote = (Button)findViewById(R.id.btnAddQuote);
btnAddQuote.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//Check if values are provided
String txtAuthorName = editAuthorName.getText().toString().trim();
String txtMessage = editMessage.getText().toString().trim();
if ((txtAuthorName.length() == 0) || (txtMessage.length() == 0)) {
Toast.makeText(AddQuoteActivity.this, "You need to provide values for Author and Message", Toast.LENGTH_SHORT).show();
return;
}
//Go ahead and perform the transaction
String[] params = {txtAuthorName,txtMessage};
new AddQuoteAsyncTask(AddQuoteActivity.this).execute(params);
}
});
//Account stuff
settings = getSharedPreferences("FamousQuotesAndroid", 0);
credential = GoogleAccountCredential.usingAudience(this,"server:client_id:756161739003-0l5c7ptti2j42l28j5anijr5mukks835.apps.googleusercontent.com");
setAccountName(settings.getString("ACCOUNT_NAME", null));
if (credential.getSelectedAccountName() != null) {
// Already signed in, begin app!
Toast.makeText(getBaseContext(), "Logged in with : " + credential.getSelectedAccountName(), Toast.LENGTH_SHORT).show();
//Toast.makeText(getBaseContext(), GooglePlayServicesUtil.isGooglePlayServicesAvailable(getBaseContext()),Toast.LENGTH_SHORT).show();
} else {
// Not signed in, show login window or request an account.
chooseAccount();
}
}
private class AddQuoteAsyncTask extends AsyncTask<String, Void, Quote>{
Context context;
private ProgressDialog pd;
public AddQuoteAsyncTask(Context context) {
this.context = context;
}
protected void onPreExecute(){
super.onPreExecute();
String accountName = settings.getString("ACCOUNT_NAME", null);
pd = new ProgressDialog(context);
pd.setMessage("[" + accountName + "]" + " Adding the Quote...");
pd.show();
}
protected Quote doInBackground(String... params) {
Quote response = null;
try {
Quoteendpoint.Builder builder = new Quoteendpoint.Builder(AndroidHttp.newCompatibleTransport(), new GsonFactory(), credential);
Quoteendpoint service = builder.build();
Quote quote = new Quote();
quote.setAuthor(params[0]);
quote.setMessage(params[1]);
response = service.insertQuote(quote).execute();
Log.d("Response from call", response.getMessage());
} catch (Exception e) {
Log.d("Could not Add Quote", e.getMessage(), e);
}
return response;
}
protected void onPostExecute(Quote quote) {
//Clear the progress dialog and the fields
pd.dismiss();
editMessage.setText("");
editAuthorName.setText("");
//Display success message to user
Toast.makeText(getBaseContext(), "Quote added succesfully", Toast.LENGTH_SHORT).show();
}
}
// setAccountName definition
private void setAccountName(String accountName) {
SharedPreferences.Editor editor = settings.edit();
editor.putString("ACCOUNT_NAME", accountName);
editor.commit();
credential.setSelectedAccountName(accountName);
this.accountName = accountName;
}
void chooseAccount() {
startActivityForResult(credential.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) {
setAccountName(accountName);
SharedPreferences.Editor editor = settings.edit();
editor.putString("ACCOUNT_NAME", accountName);
editor.commit();
// User is authorized.
}
}
break;
}
}
}

Let us focus on the important parts and how the Account Credentials are setup before invoking the Secure Cloud Endpoints API.

  • Pay attention first to the onCreate(…) method. The code is identical to the earlier one where we setup the onclick listener for the AddQuote button.
  • There is a section starting with the comment //Account Stuff and there we create an instance of the GoogleAccountCredential class. We create that using the usingAudience method. Notice that the Audience Id string has to be created as follows “server:client_id:<AudienceId>
  • Once the GoogleAccountCredential instance is setup, we invoke the setAccountName method. This method uses Android SharedPreferences , to determine if an ACCOUNT_NAME String preference value was set. If yes, we set that account name for the GoogleAccountCredential instance.
  • We check in the next line if the name has been selected, if not – we invoke the chooseAccount() method, that starts the standard Account Picker activity on your Android phone. This will allow you either login or choose from one or more of your accounts on the device. Once selected, we invoke the setAccountName() method again to set the account name in the GoogleAccountCredential instance and set the value in the SharedPreferences too.
  • Now that the the GoogleAccountCredential is all set, the other change you will notice is in the AddQuoteAsyncTask.
  • In the doInBackground method, notice the following line:
    Quoteendpoint.Builder builder = new Quoteendpoint.Builder(
                    AndroidHttp.newCompatibleTransport(),
                    new GsonFactory(),
                    credential);

    Here, we are no longer passing null in the 3rd parameter. We are using the GoogleAccountCredential instance.

AndroidManifest.xml Permissions

We need to add some additional permissions to the AndroidManifest.xml file as given below:

<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
view raw permissions.xml hosted with ❤ by GitHub

Project Source Code – Download

The source code for the MyAPIProject2 is available in Github. You can download the entire source code over here.

Additionally, the source code for the secure FamousQuotes Android application is also provided over here.

This brings to an end, the series on Cloud Endpoints as I had originally planned out. Hope you liked the series so far. Give me feedback to help me create more and better content.

I do hope to blog about comparing Google Cloud Endpoints with other methods of writing Web Services along with some stuff that I would ideally like the next release of Google Cloud Endpoints to address both in features, samples and documentation.

23 thoughts on “Google Cloud Endpoints Tutorial – Part 7

    1. Thats a very good suggestion. Is your use case, the ability to call secured endpoints from another app engine application , without the need to authenticate ?

  1. Thanks for this tutorial.

    Q1 – Is there anyway to hide my API definitions from the public? I have uploaded my app to appengine however looks like anyone on the internet can access .appspot.com/_ah/api/explorer and view and input data.

    Q2 – I’m building an Android client which does not require authentication from the user for some APIs. Basically the user does not need to login to view some screens.
    However I only want my Android app to access those APIs? How can I go about doing this. Do you recommend any other solution other than http://stackoverflow.com/questions/21825175/how-do-i-restrict-google-app-engine-endpoints-api-access-to-only-my-android-appl

    1. 1. I am not sure if that can be done via a simple configuration setting or something like that. I am not aware of that.
      2. I believe you can do that by trying to specify the Client Ids, Audiences, etc in the @APIMethod. The user object would be null. But still give that a try and see if it works.

  2. Thanks for this tutorial.
    but i have a problem, if you can help please.
    in line response = service.insertQuote(quote).execute();
    i have exception: com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAuthIOException

  3. Thanks again for this series. I was wondering whether you had more to come? I’d love to learn more about JDO relationships, queries and authentication using FB or twitter for example. I’ve been doing a bit of reading and experimenting but I find your series was the fastest way to get going 🙂

  4. hello thnk u for the tutorial. Why do I have to add scope/id, audiences in @ApiMethod (…), isnt it enough to set it in @API(..) at the beginning, thats how the google doc says. We can as best add the User parameter in api methods. am I wrong ?

  5. ^@Hossain, setting the OAuth dance stuffs, as explained by Romain, in @ApiMethod(…) will provide authentication measures only for that method. For example insertQuote() in the tutorial. Setting it in @API(…) will provide authentication measures for all the capabilities (methods) that the API provides.

    @Romin: I learnt a lot from your series, thank you! In addition I wanted to know whether we could add SSL for the HTTP calls that we make with the Endpoints API? Or is it the case where OAuth2 (authenticated by Google IDs, they might be using some Certificate Authority) for Endpoints API and SSL are cannot go together?

  6. Oh I forgot that the way we are accessing our appspot domain on the Internet is over HTTPS i.e. SSL is implicitly present. Was thinking all the way with respect to the HTTP access that we do on localhost.

  7. Hello Romin thanks a lot for this great serie of tutorials!!

    Here we are saving just messafe “text”, but I just have a doubt that now comes through my mind, and I hope that you could please helpme and it is if can we save any type of file on the cloud through Android by using Endpoints?

    Well the thing is that I am doing a project in which I have to do my own version of dropbox or cloud data storage something like this and I really dont have an idea how could be the way to do this.

    I hope you culd give me some advice!!

    Thanks in advance Romi!!

    And again really nice tutorial!!

  8. Hello Romin!

    First of all, thank you for an excellent series of tutorials. They have really helped me understanding more about GAE Endpoints.

    However, I do have a question about how to make my api secure to my applications without having to choose an google account as user. I feel that Googles documentation isn’t very thorough about that part.

    What would you say is the correct approach for that?

    best regards,
    Martin

    1. Martin – Thanks for the feedback. Your question is a good one and unfortunately it is not straightforward to implement the way you can use Google Accounts. I have never done it myself but there are enough threads on this subject at Stack Overflow, if they can help. Check this one out : http://stackoverflow.com/questions/24537865/custom-authenicationuser-model-for-cloud-endpoints-python?lq=1 and http://stackoverflow.com/questions/18716674/facebook-login-in-google-cloud-endpoints?lq=1

  9. Hi Romin,

    Till recently me and my team were working with endpoints without authentication to get our app up for a pilot. Then we decided to secure the endpoints and decided to move android project to a separate google console project and app engine to a separate instance.

    Following the instructions we have set the Client ID , Audience and all other prerequisites and generated the endpoints and its respective android client library.

    Using this library in our android project and calling the endpoint we are facing difficulties with authentication : “Caused by: com.google.android.gms.auth.GoogleAuthException: Unknown”.

    As per instructions we have built the GoogleAccountCredential object with useAudience method and pass in the proper WEBCLIENT ID with “server:client_id:” prepended to the WEBCLIENTID.

    Also if you have any pointers on using the secured endpoints via Retrofit, it would also help. We are able to use the endpoints via Retrofit without any authentication successfully.

    Any help in this matter would be very helpful. Thanks 🙂

    FYI:
    The Android Project and App Engine project are in two different developer console projects.
    I have added appropriate SHA1 related client IDs to the Endpoints code.
    The instance runs on 1.9.26 release of AppEngine SDK.

  10. Thanks a lot for this series! It was very clarify to me. I’m trying to use a Python version of endpoints but this series help me to understand more about the endpoints. If anyone here know a similar python series please warn me!

Leave a reply to rominirani Cancel reply