Thursday, January 12, 2012

OAuth2 with Google C2DM (push)

I had to implement the server prototype for Google C2DM (push) the other day.
The easiest option to get the auth token is to use ClientLogin API: http://code.google.com/apis/accounts/docs/AuthForInstalledApps.html  that returns the auth token in one call. One of the problems with this approach was that I either had to store the credentials on the server in order to refresh the auth token if it expires or notify the system administrator to go ahead and re-authenticate through the dedicated web page on the server. 

The better approach is to use OAuth2 API instead: http://code.google.com/apis/accounts/docs/OAuth2WebServer.html
There are couple of things are not well documented:
* When you register the app on the https://code.google.com/apis/console/ there is no C2DM service API to choose. That's fine for now, though it might change later probably.
* One of the parameters "scope" for the https://accounts.google.com/o/oauth2/auth call needs to be set to 
scope=https%3A%2F%2Fandroid.apis.google.com%2Fc2dm (https://android.apis.google.com/c2dm url-encoded)
* Set parameter access_type=offline.This will provide you with the refresh_token in the authentication response, that you can use to renew the auth token when it expires. 

Hope this will save somebody few minutes. 

UPDATE (January 19, 2012):
If you stumble upon the MismatchSenderID error, make sure that when you start authentication (https://accounts.google.com/o/oauth2/auth) that you are not already signed in under your personal google account (hint: the browser cookies) . You should be signed under the google account that you created and that got approved for C2DM.



UPDATE (July 2, 2012):
As of the Google IO 2012 the C2DM API is officially deprecated and this article is not really that relevant anymore.
The new sign ups and the quota increase requests are not going to be processed anymore and Google highly recommends to migrate over to the Google Cloud Messaging (GCM), which is still free and has lots of improvements and additional features. I personally think GCM is awesome. 
Please refer to the official GCM page for more details:
http://developer.android.com/guide/google/gcm/index.html
I'm looking forward to change my code to work with GCM pretty soon and will try to write another post on the transition process. Enjoy!

11 comments:

Anonymous said...

Great article. Do you mind showing how the server sends the message to C2DM service? I used curl for testing:

curl -k --header "Authorization: Bearer access_token" "https://android.apis.google.com/c2dm/send" -d registration_id=id_from_client -d "data.message=Example message to send" -d collapse_key=0

I always get MismatchSenderID error. ClientLogin authentication works with no problems though.

Thanks!

Alex said...

Ahh, you probably got the auth token for your personal account that you use with the browser instead of the account that got approved for C2DM.

Make sure when you start authentication (https://accounts.google.com/o/oauth2/auth), that you are not already signed in under your personal google account (hint: the browser cookies) . You should be signed under the google account that you created and that got approved for C2DM. If you start with no authenticated session, then you will be redirected to the standard google sign-in page first.

Charles Hudak said...

Are you providing a user interface for the original authentication that returns the initial access token?

I have a middleware web application that has no UI. It seems like a pain in the butt to have to have an administrator login to the application (on every server no less in the cluster) every time the application is started(restarted) in order to get the token.

I typically write B2B applications where the applications run without user intervention. This seems like a horrible security api on google's part that doesn't lend itself to M2M services.

Alex said...

Charles,
After you get the renewal token it never expires and it allows you to get actual auth tokens with it. So you keep the renewal token around and you can revoke it if the token gets compromised.

The idea behind OAuth type of authentications is that the user doesn't have to trust his credentials to anybody but the actual authenticating authority in this case it is Google. The tokens that you get don't give the full access to the account, just to the particular service that you are requesting. Also you don't have to store the credentials anywhere on your servers at any time.

In case of the web farms (multiple servers) you don't need to have all the servers to authenticate, they can barely just ask for the auth token from your central authenticating server for example and ask to refresh it when the token expires.

Btw, you can always go back and use simple ClientLogin type of authentication, where you make the auth credentials always be available to your authenticating code, but then you would have to worry about how to keep your credentials secure and if they get stolen you effectively could loose the control over your account.

Just my 2c :)

Alex said...

Sorry Charles, pushed the wrong button and accidentally removed your comment and could not undo this.

Here it is:
Charles Hudak has left a new comment on your post "OAuth2 with Google C2DM (push)":

Yeah, I get the concept but this requirement that you have user intervention in a M2M service interface like push is annoying. That means an administrator somewhere has to go through the process of authorizing and configuring our application with the authorization code so we can fetch the tokens.

When you have an application that is deployed in close to 2 dozen different environments (integration, multiple separate QA environments, staging, and trial deployments with different business partners) having to deal with this is a pain. All of the other B2B service integrations I've done have been through either just credentials or a combination of credentials and certificates.

In any event, how did you handle the auth header? In the sample code I have they user the tokesn to configure a GoogleAccessProtectedResource object and this is used to initialize the connection. It automatically sets the authentication header and automatically does the token refresh if a 401 is returned.

It looks like, however, that it sets the header as "OAuth {token}" but that would appear not to jive with what the c2dm documentation says that the authorization header should look like. Are you just manually handling setting the header and dealing with the token refresh? I wonder if it would be worthwhile to sublass the above so it writes the header as expectected by c2dm.

Alex said...

I understand what you are trying to say,
I've been there done that as well :)
It's convenient but has it's own problems.
Having to deploy the credentials to the same google account on 2 dozen of
different environment just further increases the risk of your account being
compromised.
As I said, you don't have to use OAuth2, you always can use the "old way" of
doing things with ClientLogin authentication API.

As for the header question, here is an the sample test curl call that that I used to
test the API for example:
curl -k --header "Authorization: Bearer [your token here, no brackets]"
"https://android.apis.google.com/c2dm/send" -d registration_id=[your
registration id here, no brackets] -d "data.message=[your message data]" -d collapse_key=0

Hope this helps.

hansamann said...

Thx for this article, it's great and convinced me to use OAuth2 also for C2DM. One thing I am really confused about now is this:

- i've setup my app and completed the oauth2 dance, ended up with an access token - great.... now that is the access_token I can use to post to C2DM for all registrationIDs for all users?

Maybe I am lost here, but I thought an oauth2 access token should be user specific. that would mean I need to move each user of my android app through the oauth2 flow to get a user-specific access token.

Or do I really only need to do this with the sender ID account once and refresh the token each hour? e.g. use the same access token for all messages to all registrationIDs that I send out?

thx!!!

Sven

Alex said...
This comment has been removed by the author.
Alex said...

Yeah, you need the auth token to communicate with the C2DM cloud only for the account that you registered for C2DM, the "sender ID" account.

Sergey L said...

Hello, Alex, thank you for article.

Some people (https://groups.google.com/forum/#!topic/android-c2dm/FxR2i_9nHmA) said that the scope should be:
https://android.apis.google.com/c2dm/send
Do you know something about this?

Also did you ever received "401 Unauthorized" response from https://android.apis.google.com/c2dm/send ?

Alex said...

Sergey,
Everything worked fine for me as described in the post.
Although as of Google IO 2012, the Google announced the Google Cloud Messaging (GCM) which is replacing the C2DM and C2DM is deprecated from now on. The will be no signup or quota requests processed for C2DM going forward.
For more information, see http://developer.android.com/guide/google/gcm/index.html

I'm planning to convert my code soon and will try to write a post on that.

Thanks,
Alex