Skip to content

cparola/android-google-plus-omniauth-tutorial

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 

Repository files navigation

#How to authenticate an Android application to a Rails server with Omniauth

Lately, I have been in charge of the Android application development at elCurator. One of the new features we wanted was to be able to sign in with a Google account. At first I thought it would be easy to implement it with the Google+ SDK, and theoritically, it was. But in practice I ran into several issues which made me think that it is not trivial at all!

I also noticed that the documentations and tutorials I could find on the web could be a little confusing, and I couldn't find a tutorial for my specific need.

That's why I decided to write a tutorial to explain step by step, how to make an Android application authenticate to a Rails server, using the gem Omniauth.

##Run the sample

I made a sample Android application and a sample Rails server to illustrate what I am talking about in this article.

Here are the instructions to run it on your machine.

First of all:

$ git clone https://github.com/elcurator/android-google-plus-omniauth-tutorial
$ cd android-google-plus-omniauth-tutorial

For the rails server:

$ cd web
$ echo "GOOGLE_KEY=your_google_key_here" >> .env
$ echo "GOOGLE_SECRET=your_google_secret_here" >> .env
$ bundle
$ foreman start

For the Android application:

In the app.gradle file:

  • replace the BASE_URL config field by your server url.
  • replace the GOOGLE_SERVER_CLIENT_ID config field by your web application client id.

The project use gradle. You can either build it with gradle and run it on your android device manually, or open it with AndroidStudio and run it directly from there.

##The flow

Let's specify a little the flow we need to implement.

Server side code flow

On the above diagram, the Client is our Android application, and the Server is our Rails server.

From now on, we are going to follow this diagram step by step.

###User clicks the sign-in button

Obviously, this first part needs to be implemented in our Android application. Let's use the Google+ SDK to do so.

Notes:

  • To register the installed application in the Google console, you need to specify a SHA-1 fingerprint. In the documentation they tell us to use the default debug keystore. Don't use it for a signed APK, it won't work! To generate a signed APK, you must use your own keystore. To work properly with Google+ OAuth, the SHA-1 fingerprint must be generated from the keystore you used to sign your APK.

  • If you are using Gradle with plural flavors in your build.gradle file, be carefull to put the adequate flavor's applicationId in the package name field when creating the installed application in the Google console.

  • If you are using Android Studio with Gradle, just add the play-services dependency in your build.gradle file:

	compile 'com.google.android.gms:play-services:7.0.0'
[Note you can now use some specific parts of the play-services if you don't use every google service.](http://developer.android.com/google/play-services/setup.html?hl=en)
  • Remember that we want to request a one-time code, which will then be used by our Rails server. To do so, we will need to specify which server we want to grant our server an access to the user's Google+ informations. That means we need to register a web application client in the Google console under the same project than our installed application.

  • You can now add the sign-in button by following this documentation. Be carefull, the first part of the documentation tells you how to connect your client directly to Google+, but what we want is to enable our Rails server to access to our client Google+ informations. You will need to follow what's said in the last part of the documentation (Enable server-side API access for your app)

Note: To request the one-time code, you need to use a scope. In this scope, you need to specify a server client id. This id is actually the one you got from the Google console in the previous step.

If you correctly followed the documentation linked above, you should be able to successfully request a one-time code with the Google+ SDK, meaning we are now at the step 4 of the flow diagram.

###Client sends code to server

To implement this step, we need a route we can call from our client to post a one-time code and a redirect uri.

Let's see what we need on the server side to implement this route.

  • First, add this line in your Gemfile:

      gem 'omniauth-google-oauth2', github: 'zquestz/omniauth-google-oauth2', branch: 'master'
    

    I made a pull request to make omniauth-google-oauth2 respond to our needs. It has not been integrated in the last version yet, that's why we need to directly fetch the repository.

  • Create a SessionsController and add an action like this:

     def create_from_google_oauth2
     	# Retrieve the user informations that omniauth fetched for us 
     	user_google_data = request.env['omniauth.auth']['info'].to_hash
    
     	# You should use the session handler you prefer here to create a session
     	@user = {
     		email: user_google_data['email'],
     		authentication_token: SecureRandom.hex
     	}
     		
     	# Render a json success template
     	render 'create_success'
     end
  • Create the file config/initializers/omniauth.rb with this code:

     # Setup the google oauth2 provider
     Rails.application.config.middleware.use OmniAuth::Builder do
     	provider :google_oauth2, 'your web client id', 'your web client secret', provider_ignores_state: true
     end

Notes:

  • You can find the web client id and secret in the Google console.

  • The web client id must be the same than the one used in our android application.

  • Add this to the routes.rb file:
     post '/auth/:provider/callback', to: 'sessions#create_from_google_oauth2'	

At this point, when we post on the route /auth/google_oauth/callback with a code and a redirect_uri in set in the body, omniauth fetches the user informations then calls our create_from_google_oauth2 action.

  • If the one-time code is already used, or if it is expired, Google responds with an invalid_grant error. Since we don't want to have to parse a big html error page in our client, we need to catch this error, and respond with a json formatted error.

    To do so, let's add an action in our SessionsController

     def oauth_failure
     	# Retrieve the error
         	@error = request.env['omniauth.error']
         	
     	# Render a json error template
     		render 'create_fail', status: 401
     end

    Then, add this code to our config/initializers/omniauth.rb file so omniauth knows which action to call when an error occurs:

      OmniAuth.config.on_failure = SessionsController.action(:oauth_failure)
    
  • We are ready to call the authentication route from our client. Using the library Ion to perform the request, here is the code we can add to our login activity, right after having fetched the one-time code from Google:

     Ion.with(this)
     	.load(BuildConfig.BASE_URL + "/auth/google_oauth2/callback")
     	.setBodyParameter("code", code)
     	.setBodyParameter("redirect_uri", BuildConfig.GOOGLE_REDIRECT_URI);

Server exchange one-time code for access and id tokens

It's actually what is omniauth-google-oauth2 doing for us. Don't bother with this step.

Server should confirm "fully logged in" to client

In the android application, replace the authentication request code you already use by this:

Ion.with(this)
	.load(BuildConfig.BASE_URL + "/auth/google_oauth2/callback")
	.setBodyParameter("code", code)
	.setBodyParameter("redirect_uri", BuildConfig.GOOGLE_REDIRECT_URI)
	.asJsonObject()
	.setCallback(new FutureCallback<JsonObject>() {
		@Override
		public void onCompleted(Exception e, JsonObject result) {
			// Invalidate the code as soon as the server consumed it.
			GoogleAuthUtil.invalidateToken(getApplicationContext(), code);				
			Toast.makeText(
				LoginActivity.this,
				result.get("error") != null ?
					"error : " + result.get("description").getAsString() :
					"connected as : " + result.get("authentication_email").getAsString(),
				Toast.LENGTH_LONG
			).show();
		}
	});

Note : we invalidate the one-time code as soon as it has been consumed by the server. Otherwise, the next time the user will try to authenticate, he will get the same code from Google, try to use it, and get an invalid_grant error.

I am open to your feedbacks

Don't hesitate to make a pull request if you want to add or clarify some informations.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published