AWS Cognito in combination with Android Account Manager for multiple accounts

I would like to show the user of my application the choice of accounts (including a common device, for example, for one department, but still the ability to identify users). User authentication and authorization work through AWS Cognito User Pool β†’ federated identifiers β†’ STS.

My problem is that I cannot get the AWS SDK to integrate well with the Android Account Manager (which I would also like to use with SyncManager).

So here is my current approach:

I tried to separate user authentication from api calls. User authentication had to happen through the AccountManager, and after it provides AuthToken, the Provider account in the CloudService (bound to MainActivity) is configured, and api calls become possible. In an attempt to make greater use of the AWS Cognito SDK, I created a singleton to store UserPool, the current user and session, but with no real effect.

So far, what is the solution, I can log in and use the application for a while. After 1 hour, the access token that I saved in the AccountManager has expired, and I don’t have a direct way to update it. The only way I was able to update the tokens was to not use the AccountManager, since AWS CachedCredentialsProvider takes care of updating the token. However, this is based on the use of sharedPreferences, and I don’t see who the user is currently in caching, so this is not possible.

In short, I tried to put all the significant code below. Hope you can point me in the right direction. I’m probably far from architecture ... Thanks in advance!

AccountSelectionActivity

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

    List<Account> availableAccounts = new ArrayList<>();
    mAdapter = new AccountsViewAdapter(availableAccounts, this);
    RecyclerView recyclerView = (RecyclerView) findViewById(R.id.rvAccountsList);
    if (null != recyclerView) {
        recyclerView.setLayoutManager(
            new LinearLayoutManager(getApplicationContext()));
        recyclerView.setAdapter(mAdapter);
    } else {
        Log.e(TAG, "no RecyclerView found");
    }

    accountManager = AccountManager.get(getApplicationContext());
    loadAvailableAccounts();
}

void loadAvailableAccounts(){
    Account[] accounts = accountManager.getAccountsByType(
        getString(R.string.account_type));
    if (0 == accounts.length) {
        addAccount(null);
    } else {
        mAdapter.setAccountsList(accounts);
    }
}

// called upon account selection
public void onClick(Account account) {
    selectedAccount = account;
    Intent intent = new Intent(this, MainActivity.class);
    Bundle bundle = new Bundle();
    bundle.putParcelable(AccountManager.KEY_ACCOUNT_NAME, selectedAccount);
    intent.putExtras(bundle);
    startActivity(intent);
    finish();
}

Mainactivity

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    Bundle bundle = getIntent().getExtras();
    if (bundle != null) {
        mAccount = bundle.getParcelable(AccountManager.KEY_ACCOUNT_NAME);
        if (mAccount == null) {
            finishDueToMissingAccount();
        }
    } else {
        finishDueToMissingAccount();
    }

    setContentView(R.layout.activity_buddy);
    [...]
}

// called from base class after binding the CloudService
void onCloudServiceConnected() {
    if (mAccount != null ) {
        cloudService.logIn(mAccount, this);
    } else {
        Log.e(TAG, "mAccount = " + mAccount.toString());
    }
}

CloudService

@Override
public void onCreate() {
    accountManager = AccountManager.get(getApplicationContext());
    credentialsProvider = new CognitoCachingCredentialsProvider(
        this, // context
        IDENTITY_POOL_ID,
        IDENTITY_POOL_REGION);
    syncManager = new CognitoSyncManager(
        this, // context
        IDENTITY_POOL_REGION,
        credentialsProvider);
}

[...]

public void logIn(Account account, Activity activity) {
    this.account = account;
    accountManager.getAuthToken(account,
        "n/a", // token type
        null, // options
        activity,
        new OnTokenAcquired(), // callback
        null // handler
    );
}

private class OnTokenAcquired implements AccountManagerCallback<Bundle> {
    @Override
    public void run(AccountManagerFuture<Bundle> result) {
        try {
            Map<String, String> logins = new HashMap<>();
            String token = result.getResult().getString(
                AccountManager.KEY_AUTHTOKEN);
            logins.put(USER_POOL_ARN, token);
            credentialsProvider.setLogins(logins);
        } catch (IOException e) {
            // TODO: Handle Exception
        } catch (OperationCanceledException e) {
            // TODO: Handle Operation Canceled
        } catch (AuthenticatorException e) {
            // TODO: Hanlde Authenticator exception
        }
    }
}

Authenticator

@Override
public Bundle addAccount(AccountAuthenticatorResponse response,
             String accountType, String authTokenType,
             String[] requiredFeatures,
             Bundle options) throws NetworkErrorException {
    return promptToLogin(response, options);
}

private Bundle promptToLogin(AccountAuthenticatorResponse response, Bundle options) {
    options = (options == null) ? new Bundle() : options;
    final Intent intent = new Intent(context, AuthenticatorActivity.class);
    intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
    intent.putExtras(options);

    final Bundle result = new Bundle();
    result.putParcelable(AccountManager.KEY_INTENT, intent);
    return result;
}

public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account,
    String authTokenType, Bundle options) throws NetworkErrorException {
    Bundle result = new Bundle();
    if (accountExists(account.type)) {
        String token = accountManager.peekAuthToken(account, authTokenType);
        if ( token != null ) {
            return passBackCurrentAuthToken(account.name, authTokenType,
                result, token);
        }
    }

    return promptToLogin(response, options);
}

private Bundle passBackCurrentAuthToken(String accountName, String authTokenType, Bundle bundle, String token) {
    bundle.putString(AccountManager.KEY_ACCOUNT_NAME, accountName);
    bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, authTokenType);
    bundle.putString(AccountManager.KEY_AUTHTOKEN, token);
    return bundle;
}

AuthenticatorActivity

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

    curSession = SessionSingleton.getInstance(getApplicationContext());
    etMail = (EditText) findViewById(R.id.etMail);
    etPass = (EditText) findViewById(R.id.etPass);

}

public void attemptLogin(View v) {
    showWaitDialog("Signing in...");
    curSession.getSessionInBackground(etMail.getText().toString(), authHandler);
}

AuthenticationHandler authHandler = new AuthenticationHandler() {
        @Override
        public void onSuccess(CognitoUserSession cognitoUserSession,
                              CognitoDevice cognitoDevice) {
            Bundle result = new Bundle();

            closeWaitDialog();
            try {
                Log.d(TAG, "> authHandler > onSuccess");
                curSession.setSession(cognitoUserSession);

                String token = curSession.getIdToken();
                result.putString(AccountManager.KEY_AUTHTOKEN, token);

                AccountManager accountManager = AccountManager.get(getApplicationContext());
                Account account = new Account(etMail.getText().toString(),
                                  getString(R.string.account_type));
                accountManager.addAccountExplicitly(account, token, result);
                accountManager.setAuthToken(account, "n/a", token);

                setAccountAuthenticatorResult(result);
                finish();
            } catch (Exception e) {
                showDialogMessage("ERROR", e.getLocalizedMessage());
            }
        }

        @Override
        public void getAuthenticationDetails(AuthenticationContinuation authenticationContinuation, String username) {
            String password = etPass.getText().toString();
            AuthenticationDetails authenticationDetails = new AuthenticationDetails(username, password, null);
            authenticationContinuation.setAuthenticationDetails(authenticationDetails);
            authenticationContinuation.continueTask();
        }

        @Override
        public void getMFACode(MultiFactorAuthenticationContinuation multiFactorAuthenticationContinuation) {
            closeWaitDialog();
            showDialogMessage("MFA is not supported", "");
        }

        @Override
        public void authenticationChallenge(final ChallengeContinuation continuation) {
            continuation.continueTask();
        }

        @Override
        public void onFailure(Exception e) {
            closeWaitDialog();
            showDialogMessage("Sign-in failed", formatException(e));
        }
    };

SessionSingleton

private SessionSingleton(Context context) {
    userPool = new CognitoUserPool(
        context,
        USER_POOL_ID,
        CLIENT_ID,
        CLIENT_SECRET,
        new ClientConfiguration(),
        USER_POOL_REGION);
}

void getSessionInBackground(String username, AuthenticationHandler authHandler) {
    user = userPool.getUser(username);
    user.getSessionInBackground(authHandler);
}

void setSession(CognitoUserSession session) {
    this.session = session;
}

CognitoUserSession getSession() {
    return session;
}

String getIdToken() {
    return session.getIdToken().getJWTToken();
}
+4

Source: https://habr.com/ru/post/1678957/


All Articles