Search code examples
androidspring-bootoauth-2.0social-authentication

Best practice for combining android client, spring-boot backend and Oauth2 social login


I am attempting to create a project that combines an Android client with a spring-boot microservices architecture in the backend. The client application will communicate with the backend via a series of Rest APIs, but will require login/authentication when doing so. For authentication/authorisation, this will be done via 3rd party/social login - i.e. google/facebook etc using Oauth2.

There seem to be plenty of docs which explain how the Oauth2 protocol works, in terms of resource owners granting access via authorisation servers, code & token exchange, redirection to the client, and accessing resources from a resource server using the provided token. I've followed the guide at:

https://developers.google.com/identity/protocols/oauth2/native-app

I have used this to guide configure a basic app with sign-in buttons which can be used to retrieve a token. My problem now is that I need to be able to do is extend the 3rd party sign-in to cover the authentication of the rest API communications between the app and the spring-boot backend.

There are a number of guides and tutorials (e.g. https://spring.io/guides/tutorials/spring-boot-oauth2/) which explain how to configure a spring-boot web application with social login, but none of these seems relevant to this project given the client is a mobile application which is performing the social login, so I'm not 100% sure how to proceed.

As far as I am aware, these 2 authentications have to be treated separately.

My current idea is that once the client has authenticated and received the Oauth2 token, this token will need to be sent directly to a backend "login" microservice as a form parameter in a post request body. The “login” microservice will then perform its own validation of the token it receives, as referenced here:

https://developers.google.com/identity/sign-in/web/backend-auth

If the token is validated by the backend server, it will then have access to requested resources. My intention will be to use the google resource ident field as a database primary key. This can then be compared to records held in my server-side database to check if it corresponds to a valid user. If the received token is valid and the extracted ident matches that for an existing database record, the login request is valid. This is similar to checking a username and password hash.

As the guide suggests, to prevent the need to revalidate the token each time an API call is made, the backend server should create a session or generate its own token to send back to the client as per a traditional web server authorisation. In this case, it will be done in the backend via spring-boot rest controllers and using volley https requests sent from the android client.

If the login is invalid, no token/cookie session id is returned and the API response will indicate either "invalid token" or "no existing user" found.

Below is a diagram which summarizes what is described above:

Authentication flow

Is this approach sensible in regards to Oauth2, or have I missed the point of social login with android + backend services here? Is there an easier/better method of implementation? This feels like something that should be commonly encountered, but most docs are quite confusing when describing how it should work.

Update

Ok, the above approach is reasonable and at present, I have a spring-boot oauth2 application combining social login providers with an oauth2 authorisation server which can:

a) redirect web-clients to the relevant social login provider, with redirecft_uri set to the authorisation server to perform code/token exchange b) after creating a db entry using the social login details obtained, generates its own oauth2 token to send back to the client.

This works great for web clients - after the initial authorisation, all API interactions are done using my authorisation server's token, generated independently of the provider.

I want to make use the same framewwork/APIs etc for mobile clients next. For web clients, all I have to do is add buttons to the UI that send requests via REST-API for the appropriate provider (e.g. http://mybackend.com/oauth2/login/google) and the rest is handled by browser redirects. For e.g. android, my current proposal is:

  1. Add equivalent buttons to app and use volley to send equivalent requests.
  2. Don't follow redirect, but instead pass response location header value in an intent to start a mobile browser session, with redirect uri set to an appropriate activity in my app
  3. When my authorisation server has finished generating a token, it will then redirect to the activity specified above, which will read the token and set in any future REST-API requests sent via volley

Update 2

Based on the feedback received so far, I have implemented a call to chrome custom tab which will be redirected by my backend to the social login provider with the appropriate settings (as configured server-side):

Button gLoginButton = findViewById(R.id.gButton) ;
gLoginButton.setOnClickListener(view -> {
    CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
    CustomTabsIntent customTabsIntent = builder.build();
    customTabsIntent.launchUrl(this, "http://example.com/oauth2/authorize/google?redirect_uri=com.example.custom.scheme.redirect://oauth2/redirect"));
});

The Oauth2 authorization flow is then completely handled by my backend identically as it would for a web-client, with my AS sending the generated token to the custom scheme in the redirect uri above. I have then declared in AndroidManifest.xml an activity RedirectUriReceiverActivity which will handle the custom scheme:

<activity
    android:name=".RedirectUriReceiverActivity"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <category android:name="android.intent.category.BROWSABLE"/>
        <data android:scheme="com.example.custom.scheme.redirect"/>
    </intent-filter>
</activity>

The activity can retrieve the token using getIntent().getData()


Solution

  • You're running into a common issue when using foreign access tokens (such as those issued by Facebook etc):

    • You want multiple authentication methods
    • You want to be in control of tokens that microservices receive

    But tokens from Facebook are not designed to be used in your own APIs.

    OAuth is designed so that each software provider uses their own Authorization. Server (AS) - which manages connections to Google / Facebook (and potentially many other providers) - then issues tokens for APIs identically in each case. This will mean your apps and APIs have far less to deal with.

    Avoid integrating social login packages directly into your Android app and just use standard OpenID Connect instead - via AppAuth Libraries.

    My blog post explains the pattern and its benefits. There are many free or low cost AS implementations that you use, which will make your architecture simpler and provide the best future design options, with the simplest code.