In my last post I showed how to send custom notifications for Android Wear devices to an Android Wear watch.
In order to demonstrate this better I used the events from our Teamgeist App as a case example and how notifications could look like.
I mocked the server side data as the focus was on the notification part. Today, I wanted to connect to our server side data via an Android app. Therefore I needed to authenticate the Android user at the server.
How the Google OAuth2 flow works for our web app
Our Teamgeist App uses OAuth2 and Google to authenticate a user and gain some information about the users profile picture or email address. The OAuth2 flow can be quite tricky the first time. The flow between our JavaScript client and the server part can be simplified to following the steps:
1. The first request from our JavaScript application to the server will be sent without token in the HTTP header. Therefore, we redirect the user to the login page where we show the Google+ login button:
2. Depending on the current user login status the next pages could be a login screen or selection page (e.g. if the user is using multiple Google accounts). Let’s assume the user logs in. The next page will be our consent screen:
As you can see, our App wants access to the users’ profile information. If the user grants the permission and accepts this screen, Google will generate an authorization code. During the OAuth2 setup Google asked for a callback URL. The generated authorization code will be sent to this URL.
In exchange of the authorization code the server retrieves an access token from Google. With this token the server can obtain information about the users’ profile for a certain amount of time. Furthermore, the server gets a refresh token which lasts longer and can be used to get a new access token.
The access and refresh tokens should be stored on the server. These tokens should never be given to the client app (Neither Android nor JavaScript). On the client side we store an application bearer token. We associate this token to the user and give it to the client. That’s the only token the client needs to communicate to our server.
Connect Android to the existing flow
Let’s assume that the user using an Android app already registered over the web. In order to get any information from the server e.g. events or kudos, we must initiate a request for a bearer token. We followed a blog post from Google, step-by-step about cross-client-identity which lead us to two working solutions. Both have their restrictions.
For both of them you need to register an Android app INSIDE of your application project in the Google Developer Console. Don’t create a new one, as these will be linked together and must be part of the same project. Please verify twice the SHA1 key for the Android app you enter AND the package name of your Android application. In the beginning we started with refactoring the current notification app by changing the package name from io.teamgeist.app to io.teamgeist.android. This lead to frustrating INVALID_CLIENT_ID and INVALID_AUDIENCE errors. As we changed back to .app and recreated the Android application in the developer console everything started to work. We haven’t tried to rename it back to .android so we can’t tell if this is a forbidden keyword in the package name or maybe we were too confident about our IDE renaming android package names. If you struggle with any of the errors check your keystore SHA1 key and your packagename. Also look into this blog post, which came quite handy.
If you did everything right you can obtain an authorization code or a GoogleIdToken from GoogleAuthUtil. For this, you will need the client id of the server or web application registered in your project.
Select the Google Account
Before you start you need to let the user select an Google Account. This is done by invoking a Choose Account Intent from the AccountPicker class:
github:b554a701a42b4deb4f20
When the user picks one Account the onActivityResult method of the initiating activity will be triggered. In order to get the authorization code or GoogleIdToken, you need the e-mail address of the user from the intent extras.
github:15e0cedf2a9f300a9264
Authorization Code
The idea using the same flow with the authorization code was tempting. Get the code from your Android application and send it to the server where the server can exchange it for a pair of refresh/access tokens. You can request an authorization code by calling:
github:0ca8982afbd7923a6048
This call is blocking and therefore has to be executed e.g. in an AsyncTask. Besides, the server_client_id of the scope parameter must be the client id of the server.
When you call this, you will not get an authorization code but an exception of type UserRecoverableAuthException, because you need to authorize your Android app for offline access. The exception itself contains already an intent to be triggered. It will launch a consent screen where the user has to grant the requested permissions of the app.
github:bb1d023190c79c4ad5ad
When you add more scopes to the scope string (see com.google.android.gms.common.Scopes for available permissions) the consent will contain more permission requests.
After the user accepts the consent, the onActivityResult of the initiating Activity will be called. From the extras you get the authorization code:
github:563392f2bac05495ef6c
The code has a very short time-to-live (TTL) and can only be used once. Once you send the token to the server you can get a refresh and access token in exchange. After that create a bearer token and return it to the Android app as you would do it with your JavaScript app. Add the bearer token to the header of all your REST calls.
The authorization code grants offline access to the app through the refresh token. That’s one of our reasons why we don’t like this solution:
1. If the user logs out from the Android app (remove the Bearer Token), you will need to go through all the steps again, including the consent screen.
2. In our example we also don’t need the offline access to user data (meaning the server can interact with Google without any user interaction). As we suppose the user is already registered via web and has granted the permission for offline access.
In our Android app we only want to fetch data contained in our server. Next, let’s take a look into the GoogleIdToken approach.
GoogleIdToken
The GoogleIdToken is a JSON Web Token (JWT). The JWT contains three parts: Header, payload and signature. The signature is encrypted and contains the header and the payload. With the public keys from Google (https://www.googleapis.com/oauth2/v1/certs) everybody can decrypt the signature and check if it matches the header and the payload.
The GoogleIdToken payload contains several information regarding the user and the application. If you send the token to https://www.googleapis.com/oauth2/v1/tokeninfo?id_token=[jwt] a payload could look like this:
github:145cd0e2cfe49c9910c7
On the server you would verify the signature and then look into the payload.
1. If the signature check is ok, you know the token has been created by Google.
2. You know Google has already verified your Android app (it checks the SHA-1 key and the package name of your Android app and compares them with the Android client registered in the same project as the web/server application) and thus provided your app with the JWT for the user with the e-mail address in the payload.
This is why you have to check the “audience” field. It must contain your web/server application client id. You could also check the “issued_to” field (also called “azp”). It contains the client id of your Android application. But this is not really needed as long as you have only one client communicating this way with your server. Google says this field could be faked from rooted devices although we don’t know how we would accomplish this.
So lets come back to our app. We want to get the GoogleIdToken. You can obtain it from Android with the same method call with which you obtained the authorization code:
Change the scope parameter in the call from
github:7542964182964020a3e4
to
github:267c137e5e987f3e318e
Unlike the Authorization Code request, we directly get the response back this time. There is no need for consent screen, as the user already granted permission to the server app. On the server side Google provides the verification of the token signature with a GoogleIdTokenVerifier. You should also provide your client id of the web/server application through the Builder of the GoogleIdTokenVerifier :
github:8a169e35386742805fdc
You verified the user and the application and could send the Bearer Token to the Android app as explained before with the authorization code. The benefits you gain additionally are:
1. You need no extra call to Google to verify the token.
2. You could even change your server application to accept not only the bearer token but also the GoogleIdToken. Therefore, you could spare the creation of the bearer Token and store it to the database.
The only thing you need to do, is to check if the user has already logged in from the web and search for the users’ data in your user database by using the social id or e-mail address from the JWT.
Drawbacks:
1. The user must have logged in into the app via the authorization code flow from the webapp. There the user has to accept the consent screen.
2. The user is never logged out. If the JWT expires (60 minutes) the Android app can get a new JWT without interaction with your server. Even if you invalidate all Bearer Tokens from the user the JWT would still be valid. Blocking the user is only possible by adding a flag to the user in your database.
3. You can’t access additional data on the server side from Google with the JWT.
4. Checking for the JWT in addition to the Bearer Token needs change on our server side.
Besides all the drawbacks we prefer the JWT approach. One suggestion is to create a mixture of these two possibilities. Use an authorization code for user registration and to get the access/refresh token. For User Identification use the GoogleIdToken only.
In our next episode we will use the login to gather periodically events from our server and push them as notifications to an Android Wear Smartwatch.
Feel free to share your thoughts and opinions in the comments section below.