7

I have a requirement to share a common presistent login (auth token) between 2 or more android apps. The trick is that either app need no be installed for the other to work. They are independent of each other.

So before an app logs in it asks the question "is there possibly another friendly app out there that can give me (or has stored somewhere) a token that I can use?"

Obviously there are various ways (and issues) that I can use to approach this:

  • using a shared service (requires secondary installation, which app installs it?)
  • using a shared content provider (requires secondary installation, which app installs it?)
  • using a file on the system (if the file exists / may not be accessible on every device)
  • using shared preferences (i believe that on some android version global shared prefs are not possible)
  • using an ordered broadcast (to wake up another possible app and ask it)

What do the stackoverflow folks think is the best approach that is simple but also robust?

kris larson
  • 30,387
  • 5
  • 62
  • 74
Rob McFeely
  • 2,823
  • 8
  • 33
  • 50
  • If you have full control over the Apps and they're strongly bound (signed with same key), using a sharedUserId value can help: http://stackoverflow.com/questions/9783765/what-is-shareduserid-in-android-and-how-is-it-used – adelphus Aug 07 '15 at 15:48

1 Answers1

6

You can start by writing an account authenticator. The canonical text on Android authenticator development is http://blog.udinic.com/2013/04/24/write-your-own-android-authenticator/

I wrote an authenticator for my app based on this article. However, what I haven't tried is having two apps with authenticators that register for the same account type. I think it should be possible to have the authenticator code in both apps. When the app asks for an authenticator for your account type with both apps installed, it shouldn't matter which authenticator it uses because they both do the same thing.

You could also have the authenticator in a separate library, but now you have three apps.


EDIT:

Here's how I integrated the authenticator into my app, within a LoginActivity:

  1. Check preferences for the last logged-in username.
  2. If there is no username, call AccountManager.newChooseAccountIntent() with my authenticator's account type.
  3. If there is a username, call AccountManager.getAccountsByType() with the account type, look through the accounts for that username, then call accountManager.getPassword(account) with that user's account.
  4. Now I have the username and password, so I can do the login. When the user is successfully logged in, the username can be stored in preferences for subsequent auto-login.

My authenticator activity has a UI flow for "Add Existing Account to Device". In this case, the user already has subscribed to our service. They enter a username and password, and if they are authenticated on the server, an account is added to the device for that username.

There is also a "Register For New Account" UI flow where the user enters all the registration information and creates a 30-day free trial account. In this process, the user is already authenticated, since the password is entered as part of this process.

This means that when the user chooses Add Account from the Account Chooser, the user is authenticated, while choosing an existing account just returns with the account from the device without authenticating. One of the drawbacks of the AccountManager Account Chooser is that there is no way to put a flag in the return intent to say what actually happened, so when the Account Chooser activity finishes, you have to go through some hopscotch to see if an authentication occurred or not. I chose a safe & conservative route by just doing the authentication every time, which means I am duplicating server authentication on account additions.

There are a bunch of corner cases you have to think about too, such as:

  • What if the user's login is valid, but their subscription has expired, which means they need to be sent to the e-commerce module to renew their service
  • What if the user has changed the password on the server from the web app, so now the password is out of sync with the device.
kris larson
  • 30,387
  • 5
  • 62
  • 74
  • Thanks for this. A good answer. What I cant figure out from the tutorials on this is there a way to have the account automatically created instead of having the user go into settings and create manually. I'm sure there is but havn't found it yet – Rob McFeely Aug 14 '15 at 09:44
  • @RobMcFeely I added some details to my answer; I hope that helps you with ideas for design. Feel free to ask any other questions you have in comments, and I will reply or update my answer accordingly... Cheers – kris larson Aug 14 '15 at 16:36
  • The short answer to your question is: Call `accountManager.addAccountExplicitly()`. – kris larson Aug 14 '15 at 16:43
  • 1
    For those trying to add the authenticator in all apps: this might work but you need to have the same `sharedUserId`, the same apk-signature and have set this up *before* publishing the apps (you cannot change those things afterwards). – hardysim Jul 03 '18 at 12:10
  • @hardysim What if signatures are different and apps already published to play store? – support_ms Dec 22 '20 at 03:32
  • @ShuhratAkramov as said, you cannot change the signature of an existing app. But in the meantime, google has added an option to change it at least *for new installations* (https://support.google.com/googleplay/android-developer/answer/9842756#upgrade). So your options are (a) you create a new app and all users need to install that (uninstalling the old) or (b) all existing users need to uninstall and install again (hopefully that works). I had to go with (a) creating all but one app as new apps and re-use its key (there's an option to re-use an existing apps key right in the play console). – hardysim Dec 23 '20 at 07:01
  • @hardysim Main purpose is sharing auth token between apps to access same server. If one app generated token others should able to access it. Where can I save that token? One friend advised to save it in android keystore storage by ciphering – support_ms Dec 23 '20 at 07:15
  • The "correct" way would be to use an authenticator(-app) as described in this answer. Using the keystore might work but I think apps can only share a keystore (so multiple apps can access the same keystore) if their signatures match. So you have the same problem there. – hardysim Dec 23 '20 at 07:49
  • @hardysim Are you sure that multiple apps can access the same keystore (access the same key stored in the keystore) if their signatures match? I wasn't able to find anything that verifies the same. The official documentation says the opposite, I think: Use the Android Keystore provider to let an individual app store its own credentials that only the app itself can access. Source - https://developer.android.com/training/articles/keystore#WhichShouldIUse – BATMAN May 24 '21 at 22:04
  • @KashishMalhotra I don't have a reference at hand but reviewing this discussion I think apps with the same `sharedUserId` (and matching signatures) are treated like "the same app" and therefore can access each others keystore?! But I'm not sure. – hardysim May 25 '21 at 12:35
  • @hardysim Got it, but `sharedUserId` has been deprecated, so I think the solution in the above comment is no longer valid, hence I raised the question. Thank you though. – BATMAN May 26 '21 at 13:08
  • @hardysim Regarding your comment - For those trying to add the authenticator in all apps: this might work but you need to have the same sharedUserId, the same apk-signature and have set this up before publishing the apps (you cannot change those things afterwards). Did you mean that if I already have an app live on the play store, I cannot make it work with AccountManager (like create a custom account through it)? – BATMAN May 26 '21 at 21:59