ZOFTINO.COM android and web dev tutorials

How to Get Auth Token from AccountManager in Android

If your app functionality needs to access protected online services or resources of users in order to provide certain features in your app, your app can do so by getting auth token of user account from account manager and using it to access protected online services.

The online services or resources can be owned by you or third party. But the question is how do you get auth token?

One way of getting auth token is every time your app needs to access online service, user is asked to enter credentials and get auth token from the server. If it is a third party service, your app needs to use third party login component or OAuth 2.0 authenitcation.

But prompting for credentials every time users try to access those features in your app which require authentication is not user friendly and efficient way of doing. To address this problem android provided account manager. Account manager stores credentials and maintains user accounts. To utilize account manager, app needs to provide components related to account type. In this post, I am going show how to get auth token from account manager. If you want more information on account type, you can read my post, how to create account type for account manager.

Now let’s see all the scenarios in getting auth token. One pre requirement is that account type that your app uses is available on the user device meaning when user goes to settings / accounts screen and clicks add account, your target account type is listed in the account type screen.

To show how to get auth token from account manager, I used coupons app. In order to download latest coupons to device, the app needs to use user’s online account so that it can fetch types of coupons that user is interested in.

Getting Auth Token - Account not Created Scenario

Account manager’s getAuthToken() method is used to get auth token. As getAuthToken() method takes account as parameter. So first we need to get account by calling getAccountsByType() method passing account type parameter.

If user hasn’t created account in account manager for the target account type, getAccountsByType() method returns null.

In this scenario, user needs to be prompted to create account by firing an intent that starts account type activity. Once user goes thru login flow and creates account in account manager, your app’s onActivityResult() method is called with status. From this method, your app can continue to do the work using auth token, in this example, app will fetch latest coupons.

        Account accounts[] = accountManager.getAccountsByType("com.zoftino");
        if (accounts == null || accounts.length < 1) {

            Intent intent = new Intent(this, ZoftinoAccountActivity.class);
            intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, "com.zoftino");
            intent.putExtra("addAccount", true);

            startActivityForResult(intent, 22);
            Toast.makeText(this, "You'll get notification once latest coupon data is downloaded", Toast.LENGTH_LONG).show();
            return;
        }

Getting Auth Token – Auth Token Expired Scenario

If method getAccountsByType() returns account, meaning user created account for the target account type, next step will be getting auth token and validating the token.

Auth token can be obtained using getAuthToken or blockingGetAuthToken. To just get the auth token, you can use blockingGetAuthToken method. Then validate auth token to see if the token is expired or not by sending a request to your authentication server.

If token is valid, auth token can be used and process can continue. If token is invalid, the auth token needs to be removed from the account manager by calling invalidateAuthToken() method. After removing the token from account manager, you need to call getAuthToken method passing account, account type, and account manager callback to get new token.

Method getAuthToken should be called from separate thread not from main thread. Since auth token doesn’t exist in account manager, account manager calls authenticator getAuthToken method to get fresh token.

Once auth token is store in account manager, AccountManagerCallback which was passed to getAuthToken method gets called. In AccountManagerCallback, you can continue the process using the token.

          String authToken= accountManager.blockingGetAuthToken(account, "user", true);
            boolean isAuthTokenValid = ZoftinoAccountRegLoginHelper.isAuthTokenValid(authToken);
            if(isAuthTokenValid){
                return authToken;
            }
            accountManager.invalidateAuthToken("com.zoftino", authToken);
            Toast.makeText(this, "You'll get notification once latest coupon data is downloaded", Toast.LENGTH_LONG).show();
            new AsyncTask<Void, Void, Void>() {
                protected Void doInBackground(Void... params) {
                    AccountManagerFuture accountManagerFuture = accountManager.getAuthToken(accountF, "user", null, GetAuthTokenActivity.this,
                            new TokenRefreshCallback(), null);
                    return null;
                }
            }.execute(); 

Permissions to Get Auth Token

Below are the required permissions to get auth token and use it in apps.

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

Get Auth Token Deadlock Calling from Main Thread

Method getAuthToken throws below dead lock exception if the method is called from main thread and auth token doesn’t exist in account manager. Account manager expects the call to getAuthToken to be made in non main thread as in this scenario auth token is fetched from server and it takes time.

That’s why in the example, I used thread.sleep in the call back and onActivityResult methods to delay the call to getAuthToken to make sure that new auth token is stored in account manager after refresh or creating new account.

 calling this from your main thread can lead to deadlock and/or ANRs
                                                                     java.lang.IllegalStateException: calling this from your main thread can lead to deadlock
                                                                         at android.accounts.AccountManager.ensureNotOnMainThread(AccountManager.java:1886)

Get Auth Token Code

Activity

package com.zoftino.content;


import android.Manifest;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.Settings;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.NotificationCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Toast;

public class GetAuthTokenActivity extends AppCompatActivity {

    private AccountManager accountManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_authtoken);

        accountManager = AccountManager.get(getBaseContext());
    }

    public void getLatestCoupons(View view) {

        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.GET_ACCOUNTS) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.GET_ACCOUNTS}, 33);
        }

        Account accounts[] = accountManager.getAccountsByType("com.zoftino");
        if (accounts == null || accounts.length < 1) {
            //startAccountActivity();
            startAccountActivityDirectly();
            Toast.makeText(this, "You'll get notification once latest coupon data is downloaded", Toast.LENGTH_LONG).show();
            return;
        }
        Account account = accountManager.getAccountsByType("com.zoftino")[0];
        processCoupons(account);
    }

    private void startAccountActivity() {
        Intent intent = new Intent(Settings.ACTION_ADD_ACCOUNT);
        intent.putExtra(Settings.EXTRA_ACCOUNT_TYPES, new String[]{"com.zoftino"});
        intent.putExtra("addAccount", true);

        startActivityForResult(intent, 22);
    }
    private void startAccountActivityDirectly() {
        Intent intent = new Intent(this, ZoftinoAccountActivity.class);
        intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, "com.zoftino");
        intent.putExtra("addAccount", true);

        startActivityForResult(intent, 22);
    }
    private void processCoupons(Account account) {

        if (account == null) {
            if (ActivityCompat.checkSelfPermission(this, Manifest.permission.GET_ACCOUNTS) != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.GET_ACCOUNTS}, 33);
            }
            Account accounts[] = accountManager.getAccountsByType("com.zoftino");
            account = accountManager.getAccountsByType("com.zoftino")[0];
        }
        String token  = getAuthToken(account);
        if(token != null){
            downloadCoupons(token);
        }
    }

    private String getAuthToken(Account account) {
        try {

            final Account accountF = account;

            String authToken= accountManager.blockingGetAuthToken(account, "user", true);

            boolean isAuthTokenValid = ZoftinoAccountRegLoginHelper.isAuthTokenValid(authToken);
            Log.d("auth token existing", authToken);

            if(isAuthTokenValid){
                return authToken;
            }
            accountManager.invalidateAuthToken("com.zoftino", authToken);
            Toast.makeText(this, "You'll get notification once latest coupon data is downloaded", Toast.LENGTH_LONG).show();


            new AsyncTask<Void, Void, Void>() {
                protected Void doInBackground(Void... params) {
                    AccountManagerFuture accountManagerFuture = accountManager.getAuthToken(accountF, "user", null, GetAuthTokenActivity.this,
                            new TokenRefreshCallback(), null);

                    return null;
                }

            }.execute();

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private class TokenRefreshCallback implements AccountManagerCallback<Bundle> {
        @Override
        public void run(AccountManagerFuture<Bundle> result) {
            Bundle bundle = null;
            Intent authIntent = null;
            try {
                bundle = result.getResult();
                authIntent = (Intent) bundle.get(AccountManager.KEY_INTENT);
            } catch (Exception e) {
                e.printStackTrace();
            }
            if (authIntent != null) {
                startActivityForResult(authIntent, 11);
                return;
            }

            String token = bundle.getString(AccountManager.KEY_AUTHTOKEN);
            downloadCoupons(token);
            Log.d("auth token callback ", token);
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode == RESULT_OK && (requestCode == 22 || requestCode == 11)) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
            processCoupons(null);
        }else
            super.onActivityResult(requestCode, resultCode, data);
    }
    private void downloadCoupons(String token){
        downCouponsFromServer(token);
        NotificationCompat.Builder mBuilder =
                new NotificationCompat.Builder(this)
                        .setSmallIcon(R.drawable.zoftino)
                        .setContentTitle("Latest Coupons")
                        .setContentText("Download Complete.");
        Intent resultIntent = new Intent(this, GetAuthTokenActivity.class);

        PendingIntent notificationPendingIntent = PendingIntent.getActivity(this, 0, resultIntent, 0);

        mBuilder.setContentIntent(notificationPendingIntent);
        NotificationManager mNotificationManager =
                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        mNotificationManager.notify(1, mBuilder.build());
    }
}