F. Aydemir
F. Aydemir

Reputation: 2735

Java OAuth Authentication with Retrofit2 in Android (7.1.1 API 25)

In Android (Android 7.1.1 API 25), I am trying to do an OAuth2 authentication by using Retrofit2 library in the class shown in code snippet 1 below. In onCreate method, I am starting an intent in order to send the authorization request and in onResume method I am trying to capture the authorization code that should be returned by the OAuth server if things go allright. In order to capture the authorization code, I have specified an intent-filter in the android manifest file as shown in the snippet 2 below.

In the onResume method in snippet 1, the uri object that is being returned from getIntent().getData() invocation always return null. Therefore, the execution never enters the following if block and I cannot retrieve the authorization code. Could you please tell me what I am missing here considering what's provided in snippet 1 and 2? Thanks.

SNIPPET 1

package com.xyz.sdk;

import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;


public class MainActivity extends AppCompatActivity {
private final static String CLIENT_ID = "client";
private final static String CLIENT_SECRET = "secret";

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

    FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
    fab.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                    .setAction("Action", null).show();
        }
    });

    StringBuilder authURLBuilder = new StringBuilder();

    authURLBuilder.append("https://id.xyz.com/api/connect/authorize").append("?").
            append("grant_type=").append("password").
            append("&username=").append("86110").
            append("&client_id=").append(CLIENT_ID).
            append("&scope=").append("transfers public accounts offline_access").
            append("&response_type=code&redirect_uri=").
            append("myauth://mycallback");

    /*BEGIN: THIS IS WHERE I AM SENDING AUTHORIZATION REQUEST */
    Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(authURLBuilder.toString()));
    intent.setPackage(this.getClass().getPackage().getName());
    startService(intent);
    /*END: THIS IS WHERE I AM SENDING AUTHORIZATION REQUEST */
}

@Override
protected void onResume(){
    super.onResume();
    Uri uri = getIntent().getData(); //THIS ALWAYS RETURN NULL
    if(uri != null && uri.toString().startsWith(("myauth://mycallback"))) {
        String code = uri.getQueryParameter("code");
        //this.onAuthCodeResponseReceived(code);
    }
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.menu_main, menu);
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Handle action bar item clicks here. The action bar will
    // automatically handle clicks on the Home/Up button, so long
    // as you specify a parent activity in AndroidManifest.xml.
    int id = item.getItemId();

    //noinspection SimplifiableIfStatement
    if (id == R.id.action_settings) {
        return true;
    }

    return super.onOptionsItemSelected(item);
}

}

SNIPPET 2

<uses-permission android:name="android.permission.INTERNET" />

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity
        android:name=".MainActivity"
        android:label="@string/app_name"
        android:theme="@style/AppTheme.NoActionBar">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
        <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:host="mycallback"
                android:scheme="myauth"/>
        </intent-filter>
    </activity>
</application>

P.S.: I have benefited from the retrofit video tutorial in below link and tried to do almost the same as what's explained in the tutorial but thing did not work as explained in the tutorial. Probabaly, I missing some setting but googling did not help much. Video Tutorial Link: https://www.youtube.com/watch?time_continue=726&v=TnQUb-ACqWs

Upvotes: 0

Views: 245

Answers (1)

wax911
wax911

Reputation: 438

you are pretty much on track so far but you could make your code handling better by adding a seperate activity, lets call it LoginActivity and set the android:launchMode="singleTop" (I will explain why shortly) as shown below:

<activity android:name=".view.LoginActivity"
        android:theme="@style/AppTheme.NoActionBar"
        android:exported="true"
        android:launchMode="singleTop" >
        <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:host="mycallback"
                android:scheme="myauth" />
        </intent-filter>
    </activity>

Next in your Login Activity make the following changes Hint pay attention to onNewIntent(Intent):

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(this, R.layout.activity_login); 
    // R.layout is the name of your layout, what ever that will be, you may also want to check if the user is logged in or not before running the checkNewIntent carelessly, the logic of how to do this is up to you
    checkNewIntent(getIntent());        
}


@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    checkNewIntent(intent);
}


private void checkNewIntent(Intent intent) {
    if (intent != null && intent.getData() != null) {
        Uri intentUri = intent.getData();
        if(uri != null && uri.toString().startsWith(BuildConfig.REDIRECT_URI)) {
            String code = uri.getQueryParameter("code"); // do something with your code
        } else {
            // show an error toast
        }
    }
}

You maybe wondering why I suggested to use onNewIntent instead of onResume, well I had a similar issue when I started working with oauth callbacks where some browsers besides Chrome would fail call my activity that responds to my callback without delivering any data in the intent, due to how the android system calls the existing activity with the default launch mode.

Using singleTop will assure that the existing activity is not recreated with a new intent but rather brought to front with the new intent, thus onNewIntent() I hope I've explained this well and look forward to your response.

Also added checkNewIntent in the onCreate for special cases such as if your application gets killed in the background for whatever reason, if the user grants permission to your application the onNewIntent may not be called if the activity is destroyed, well at least from my experience, I'm sure someone might have a better explanation for this :)

Upvotes: 1

Related Questions