OAuth allows users to share their data or resources, hosted on a service provider, with websites or desktop or mobile apps.
Of course, if you gave your credentials (username and password) to those websites or apps, they could access your data on your behalf; but would you trust a third-party app or website to keep your credentials (your key to your digital life) safe? What if this app or website is malicious? Or simply unsecured? How many times have you read about a website getting its users credentials stolen, or keeping passwords in plain text in their databases?
OAuth is just about this; letting third-party apps or websites have a limited access (through a list of authorizations or scopes: access user email
, access user profile
, can post messages on user behalf
, and so on) to your data, hosted on a service provider (the famous OAuth service providers are Google, Facebook, Twitter, Yahoo!, GitHub, LinkedIn, and so on) without ever giving them your credentials.
An OAuth example using Google
We are going to build an Android app displaying information about a Google user: we are going to use Spring for Android Auth
of course, but more importantly Spring Social and Spring Social Google.
Let's have a look at the pom.xml
file of this project, in particular the inclusion of Spring Social Google:
You will notice here (in the repository
section), this module is not hosted by Spring Source, because it is actually a community project not endorsed by Spring Source.
Note
This pom.xml
file contains many exclusions; this is because most of the libraries used were developed with Java SE in mind, this is why they rely on Spring Core, Spring MVC, and so on. Spring for Android Core and RestTemplate
provide the necessary dependencies for those modules.
Now let's have a look at the AndroidManifest.xml
file:
For the first time in our examples, we are going to use an Application
class, named here MainApplication
.
Note
GoogleWebOAuthActivity
will embed a browser and will only be launched for authentication. We don't want this activity to be part of the app history or the user to be able to get back to it; that's why we added android:noHistory="true"
and android:excludeFromRecents="true"
. More info on this is available at http://developer.android.com/guide/topics/manifest/activity-element.html.
This class will be used to prepare the two most important factories of our app (they will be accessed in all the activities): ConnectionFactoryRegistry
and ConnectionRepository
:
As you can see, in the onCreate()
method we initialize:
ConnectionFactoryRegistry
: With the client ID and the client secret of the application from ConnectionFactoryRegistry
, we'll have access to GoogleConnectionFactory
which is the Google services extension of OAuth2ConnectionFactory
that gives access to all OAuth operations
ConnectionRepository
: This will be responsible for persisting ConnectionFactoryRegistry
, so that the OAuth token can be retrieved without needing to do the whole OAuth workflow every time
Note
You may have noticed the use of a salt and a password (encryption) during the initialization of the database.
This will prevent a malicious app from being able to access the device database to retrieve the user OAuth token. A brief reminder: the app will never have access to the user's Google password. The authentication to the service provider, Google in this example, is always performed from the device browser.
Let's have a look at the main activity of that project, GoogleActivity
that will be launched at startup:
This activity will display a list of entries related to the user profile if he/she is connected or just a Connect button if the user is not connected yet (since GoogleConnectionFactoryRegistry
is persisted in a database, just looking up a connection of type Google
in ConnectionRepository
is enough to know whether or not the access token is already fetched).
So, in the case that we are not connected, taping on Connect will call displayGoogleAuthorization()
which will launch GoogleWebOAuthActivity
.
GoogleWebOAuthActivity
is certainly the most important activity of this app. It is responsible for the OAuth 2.0 authentication and authorization.
When this activity is created, it configures the associated WebView (you will notice this activity extends AbstractWebViewActivity
that injects a Chrome Browser instance into a WebView) to accept JavaScript ( the service provider and Google OAuth 2.0 requires JavaScript to authenticate the user) and injects a custom WebViewClient
object that we will use to intercept OAuth flows (more on that in a moment).
Then, when the activity starts, we ask the WebView (the embedded Chrome browser) to request the authorization code for this app (see step 1 of The OAuth dance section).
This request is built using a callback URL, the scope of the authorizations needed for the app, and the client ID and secret (those two were already given to Spring OAuth when we created ConnectionFactoryRegistry
).
If the user has not yet authenticated to any Google web services from his device, he/she should see a dialog inviting him/her to authenticate:
In all cases though, the user will see this authorization dialog which lists all the scopes the app has requested:
Note
If the user denies the authorization, then, as expected, the authorization process will be terminated.
According to the service provider, this dialog may vary.
Once the user accepts the requested authorizations, GoogleWebOAuthActivity
will detect that the web client is being redirected to a localhost (the callback URI), with the authorization code:
exchangeAuthorizationCodeForAccessToken.execute(code)
will execute the following AsyncTask (we are going to send back the authorization code from our app, using RestTemplate
, relying on Java UrlConnection
, so we need to code this call from a background thread):
Once the exchangeForAccess
method is called, we retrieve the user token and we persist it in the ConnectionRepository
class.
Our app is finally authorized to access the user's Google profile!
If the user clicks on Profile, he will launch GoogleProfileActivity
, from which, as you may expect, we get the user profile.
To do so we are using an AsyncTask, named FetchProfileTask
, that will hit two Google web services: UserOperations
(to read the main profile of the user and his/her profile picture) and PersonOperations
(to read his/her Google+ profile, here we will just access the about me description):
This information is then injected into the view: