Recently I had to implement an online sync facility for my habit tracker app. As it goes with every online service, you needed an account to take it’s advantage. Enter the Android Account Manager.
Now, if you are an advanced user of Android phones (and since you’re reading this, you most certainly are), you must have seen an “Accounts” section in phone Settings.
Upon clicking that, you’ll find your accounts related to apps on your phone.
I wanted to utilize this built-in AccountManager
functionality to store and authenticate user’s credentials because it works like a charm with the built-in sync mechanism (using the SyncAdapter
).
But we are getting ahead of ourselves here. Let’s start with the authentication system for server.
Authentication system
Our system authenticates users based on email and password combo. If the combo is correct, server returns an auth_token
which are valid until the user logs out. This is your normal token based authentication where the token lives forever and has all the permissions as the user itself.
Every device creates a new auth_token
for itself because it makes it easy to log the device, limit the number of clients for an account.
This was fairly easy to implement on the server side by adding fields for auth_token
in Device
model. Simply create a new Device
whenever user logs in or registers, populating that auth_token
field to a random value.
Creating account on the phone via the Android Account Manager
Till now, we had discussed the how to create account on server, and how it interacts with the client. Let’s move on to the meat of this post, creating account using AccountManager
.
Wait, what’s AccountManager? And why should we use it?
More specifically, why not store the credentials using SharedPreference
and have full control without wasting time on this?
The great thing with AccountManager
is that it solves the corner cases, and little small details with ease, which you might forget to account for. It really shines well when you’re using OAuth2
and need to fetch a new token when the current one expires.
There are many more benefits to using AccountManager
but going through all of them is a little too much. I’ll add the links in comments if you ask for them. 🙂
A more in-depth look at AccountManager
can be found here.
We’ll be using it to store the user credentials and auth_token
.
Things we need to support
- Users can add an account
- They can sync their data
- They can log out of their account
Pretty simple and straightforward.
Flow and important terms
Before we look at the code, let’s talk about the workflow.
When the app tries to access an account, and it’s auth_token
, this is how it goes :-
- The app asks the
AccountManager
for anauth_token
. - The
AccountManager
asks theAccountAuthenticator
whether it has a token - If it has none, it shows the user an
AccountsActivity
through which the user logs in - The user logs in after which the app obtains the
auth_token
from server - The
auth_token
is stored by theAccountManager
for any future use which it returns the next time app asks for it
The good thing about using AccountManager
is that system will start the correct activities and handle most of the corner cases we as developers get lazy about.
This assures us that we’ll have a valid auth_token
while syncing, so we can avoid defensive programming.
Building our Authenticator
Authenticator is the class which AccountManager
uses to handle our account related tasks like storing the auth_token
or account password.
The first thing you need to do is extend the AbstractAccountAuthenticator
and implement its methods to create our Authenticator.
You may decide to implement all the methods, or just leave out some by having them throw an UnsupportedOperationException
. You will, however need to implement the addAccount
method to allow users to add an account in the system.
@Override public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException { final Intent intent = new Intent(mContext, AccountsActivity.class); // This key can be anything. Try to use your domain/package intent.putExtra("YOUR ACCOUNT TYPE", accountType); // This key can be anything too. It's just a way of identifying the token's type (used when there are multiple permissions) intent.putExtra("full_access", authTokenType); // This key can be anything too. Used for your reference. Can skip it too. intent.putExtra("is_adding_new_account", true); // Copy this exactly from the line below. intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); final Bundle bundle = new Bundle(); bundle.putParcelable(AccountManager.KEY_INTENT, intent); return bundle; } |
Another method you should implement is getAuthToken
. This method allows your SyncAdapter
, and really your whole app, to acquire the auth_token
for making network calls.
This is how I’ve implemented it.
@Override public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle bundle) throws NetworkErrorException { AccountManager am = AccountManager.get(mContext); String authToken = am.peekAuthToken(account, authTokenType); if (TextUtils.isEmpty(authToken)) { authToken = HTTPNetwork.login(account.name, am.getPassword(account)); } if (!TextUtils.isEmpty(authToken)) { final Bundle result = new Bundle(); result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type); result.putString(AccountManager.KEY_AUTHTOKEN, authToken); return result; } // If you reach here, person needs to login again. or sign up // If we get here, then we couldn't access the user's password - so we // need to re-prompt them for their credentials. We do that by creating // an intent to display our AuthenticatorActivity which is the AccountsActivity in my case. final Intent intent = new Intent(mContext, AccountsActivity.class); intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); intent.putExtra("YOUR ACCOUNT TYPE", account.type); intent.putExtra("full_access", authTokenType); Bundle retBundle = new Bundle(); retBundle.putParcelable(AccountManager.KEY_INTENT, intent); return retBundle; } |
This kind of completes the Authenticator, at least for our purposes. You can have a look at the entire file by visiting this gist for Authenticator.java.
Building the Authentication Activity
Now, you must have noticed that there is an AccountsActivity
here. This is a special activity which is shown to the user whenever they have to login for Authenticator
to get token.
Create the activity as you normally would, making network calls, validations and everything else normally. When you wish to add the credentials to Accounts, use a method like the one below.
public void createAccount(String email, String password, String authToken) { Account account = new Account(email, "YOUR ACCOUNT TYPE"); AccountManager am = AccountManager.get(this); am.addAccountExplicitly(account, password, null); am.setAuthToken(account, "full_access", authToken); } |
This will create the account for you to use.
Building the Authentication Service
At this point, the Activity will be ready but we haven’t registered our Authenticator
with the system.
To do that, we’ll have to define an AuthenticatorService
and register it in the Manifest file with special filter and meta-data.
The service is pretty simple (link) and looks like:-
public class AuthenticatorService extends Service { // Instance field that stores the authenticator object // Notice, this is the same Authenticator class we defined earlier private Authenticator mAuthenticator; @Override public void onCreate() { // Create a new authenticator object mAuthenticator = new Authenticator(this); } /* * When the system binds to this Service to make the RPC call * return the authenticator's IBinder. */ @Override public IBinder onBind(Intent intent) { return mAuthenticator.getIBinder(); } } |
After making this we need to declare this service in Manifest with appropriate tags.
<service android:name="com.example.AuthenticatorService" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="android.accounts.AccountAuthenticator" /> </intent-filter> <meta-data android:name="android.accounts.AccountAuthenticator" android:resource="@xml/authenticator" /> <!-- We haven't defined this --> </service> |
If you notice the meta-data
tag there, you’ll find android:resource
‘s value something which hasn’t been defined before. This xml file is important because it lets the system know how to represent your app in the settings.
Create a folder named xml
in the res
folder. In that folder, make a new file named authenticator.xml
(name can be anything, as long as you specify it in the Manifest file). The contents of that file for my project looks like this:-
<?xml version="1.0" encoding="utf-8"?> <account-authenticator xmlns:android="http://schemas.android.com/apk/res/android" android:accountType="@string/account_type" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:smallIcon="@drawable/ic_launcher" /> |
Most of these values would have been already defined by you, except the account_type
which needs to be the same as the one your provided earlier (we have used "YOUR ACCOUNT TYPE"
as placeholder).
And that’s it!
This will let you create a very basic version of app supporting the accounts in your system. Look how good our app’s name looks in Account’s screen 😛
To access your user’s account, all you will need to do is
AccountManager accountManager = AccountManager.get(context); Account[] accounts = accountManager.getAccountsByType(AccountConstants.ACCOUNT_TYPE); // Use accounts[0] (or whatever number of account) after checking that accounts.length > 1 |
That wraps up our introduction to creating an account in system. If you want a detailed information about what other things are possible, check out udinic’s post which is an excellent but overwhelming resource.
I urge you to leave a comment here if you have any questions or tweet them to me, @shobhitic.
Next week, I’ll be creating a SyncAdapter
which uses this Account for communication. If you’d like to hear about that, sign up for my email list.
(The post is here – Android Sync Adapter)
What about update email in Account Authenticator?
http://stackoverflow.com/questions/42731571/android-accountauthenticator-change-remove-account
Any Example?
In my example, I’ve used email as account name. So for me, I’ll have to get `am`, an instance of account manager and then call `am.renameAccount(account, “new email”, accountManagerCallback, handler);`
Its only available for > API 21
What about older versions?
You should target your build to > API 21. It’ll work in that case. My app supports > API 16, and this works.
>Next week, I’ll be creating a SyncAdapter which uses this Account for communication
Didn’t make it yet?
Oops, I forgot about it :-). Will do it this week. Would you like to be notified?
Yes please. And thank you for the great tutorial!
Published the next tutorial Android Sync Adapter
Great tutorial!
Can you provide an example code on github ? thanks
PS: wait for next 😉
Published the next tutorial Android Sync Adapter
authToken = HTTPNetwork.login(account.name, am.getPassword(account));//what this line does?and if HTTPNetwork is an inbuilt class..?
This basically makes a call to our own servers and gets an API Auth Token for the username password combo.
HTTPNetwork is a custom class. You can make yours however you want, or use things like Retrofit.
Hi Shobhit,
Great post – you have explained the innards of your code – but I have an even more basic question as I am just starting to write my own app.
What tool do I need to use to edit this code? I have Visual Studio 2017 Community and Android SDK installed.
Does this work only on one of the OS’s or on all (at least on Android and iOS)?
Thanks in advance.
Tom
This is only for Android. As for the tools, you’re better off using Android Studio than anything else – https://developer.android.com/studio/index.html
Hey Shobhit,
I was following your tutorial and I am kind of stuck in one of the areas where you make the actual network call to get the authentication token. I have this exact question:
https://stackoverflow.com/questions/44636019/creating-an-authentication-manger-with-android
Do you have any suggestions on how to do it? I’m really stuck and have been surfing for answers for hours but nothing is coming to me! Do let me know 🙂
Why not make a Synchronous Request with Volley? I’ve also responded on Stack Overflow.
Hi Shobhit,
I have tried to integrate this hole thing in an app that I have developed earlier. But I don’t really understand how it works. Initially my app launched with the LoginActivity and after the login was successful the app passed to a MainActivity. But now I don’t know which is going to be my launcher Activity. Can you please explain me how it really works ? Is the system going to start the AuthenticationService before launching any activity ? Or …
The AuthenticationService is launched whenever you call it, in our case MainActivity will start it to check credentials.
MainActivity will be your Launcher activity (unless you define something else ;).
MainActivity first checks if there is a valid login, and if there isn’t it just opens LoginActivity.
As somebody already requested, the working github repo would be highly appreciated as there are quite a few black holes… for example the before mentioned AuthenticationService call is not clear to me neither. How do I call the service from the MainActivity? I see the service returns an IBinder if I am to call the onBind, but then I didn’t had any business with IBinder until now… Thanks for following along.
what is the physical location of android account manager, where does it store users password on local storage?
I think it is stored in the system’s storage, where they are encrypted before storage, just like your fingerprint or pin.
Hi I have two apps , one is already gets published and another one is about to get published. How should i implement this with an existing app and with a new app. My requirement is that I want to login my user in either of the app, and once he is signed in one app i should get the auth token in second app. Any help will be appreciated. Thanks a Lot
Hello Sobhit, how can I implement this in my application , the thing is that my first app is already published in, and the second one is about to get published soon. The requirement is that the user can login both the apps with the same user credentials. How can i implement this in existing apps that if the user logs in to one app he is automatically logged in other app as well in brief sharing the same token in both the apps. Any help will be appreciated.
Thanks a Lot for this article.
Follow this tutorial to accomplish that – https://www.captechconsulting.com/blogs/Android-Single-Account-Multiple-Application-Prescription