Android – Implementing two-step authentication through Google authenticator
I’ve recently spent a bit more time than I should on implementing two-factor authentication through Google authenticator in Android. The closest I got to getting the task done was when I used this Github repo presenting a server side code and a client example written in Java. This step got me to understand how two-factor authentication code works and how it’s implemented. But what actually helped me implement this feature in very few hours was this Authy service offered by Twilio. It makes the task simple as it supports One Time Password sent via voice and SMS, as well as Time-based OTP generated in their Authy app or in other authenticator apps .
We’ll use Authy API in this tutorial to generate TOTPs in a Google Authenticator app installed in the same device (through an intent to the app) or in a different device (through a QR code to be scanned) and we’ll use it in the following tutorial to generate OTPs to be sent via SMS then verified through the API.
What we’ll build
Source code for this sample is provided by the end of the tutorial. But I seriously recommend you go through all sections in the tutorial. Second section for ” Enabling two factor authentication ” could be skipped if needed, I only tried to make an explicit explanation of what you see in the demo.
Setting up the project
The demo presents two different uis for signup and login and eventuallay a ui for QR code validation and a home ui. I see it useless to post all uis code in here as you can customize it your way. So we’ll suppose you need to create a new Android project and add four empty activities (Signup,Login,QRcodeValidation,and Home activites).
Then, you’ll need to add some libraries to gradle build. We’ll be using firebase for user authentication, so firebase gradle references are needed. We’ll also need to add volley to make http calls to our REST API (authy API), and gson to deserialize json objects returned by the API. We’ll also need other libraries for ui like that country code picker you’re seeing in the demo and the picasso library to load QR code image.
So, you’ll need to add the following to your gradle file and sync:
// firebase core implementation 'com.google.firebase:firebase-core:11.8.0' // firebase authentification implementation 'com.google.firebase:firebase-auth:11.8.0' // firebase database implementation 'com.google.firebase:firebase-database:11.8.0' // to deserialize/serialize json ibjects returned by Authy implementation 'com.google.code.gson:gson:2.8.2' // volley implementation 'com.android.volley:volley:1.1.0' // to set imageView src implementation 'com.squareup.picasso:picasso:2.71828' // country code picker implementation 'com.hbb20:ccp:2.2.0'
Next, you need to build your uis. What is really mandatory here is that you give your user the ability to enable two-factor authentication or not on registration.
Enabling two-factor authentication (on signup)
So I’ll post only my part of the ui where I request user’s choice on enabling 2FA :
<CheckBox android:id="@+id/enable2FAchkBx" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Enable two factor authentication on login" fontPath="fonts/graublau_slab.ttf" android:textSize="8pt" android:layout_marginTop="25dp" tools:ignore="MissingPrefix" /> <com.github.aakira.expandablelayout.RelativeLayout android:id="@+id/phnNbrLayout" android:layout_width="wrap_content" android:layout_height="wrap_content" app:ael_expanded="true" app:ael_duration="500" app:ael_orientation="vertical" > <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="10dp" android:paddingBottom="5dp" android:textStyle="bold" fontPath="fonts/graublau_slab.ttf" android:text="Phone number required :" tools:ignore="MissingPrefix" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_vertical" android:background="@drawable/editextborder"> <com.hbb20.CountryCodePicker android:id="@+id/countryCodePicker" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <EditText android:id="@+id/phoneNumberEdt" android:layout_width="match_parent" android:layout_height="match_parent" android:hint="Phone number" android:inputType="number" android:layout_marginRight="20dp"/> </LinearLayout> </LinearLayout> </com.github.aakira.expandablelayout.RelativeLayout>
Next step is to define a global boolean variable, istwoFactorAuthOn, on SignupActivity.java and switch it to true or false on user’s action on the checkbox as follows:
/** enabling two factor authentication **/ ((CheckBox)findViewById(R.id.enable2FAchkBx)).setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if(isChecked ){ ((ExpandableRelativeLayout) findViewById(R.id.phnNbrLayout)).expand(); isTwoFactorAuthOn="true"; }if(!isChecked){ ((ExpandableRelativeLayout) findViewById(R.id.phnNbrLayout)).collapse(); isTwoFactorAuthOn = "false"; } } });
On signup button’s click, we’ll register a new user and add a new node to firebase table “Users”, where we’ll save an isTwoFactorAuthOn attribute. The point is to request this attribute’s value each time firebase user login is successful, to redirect them to home ui (if isTwoFactorAuthOn is false) or to request them for two-factor authentication (if isTwoFactorAuthOn is true).
public static void signUp(final String email, String password, final String fullName, final String isTwoFactorAuthOn, final String phoneNumber,final String phoneCountryCode, final Activity activity) { Statics.auth.createUserWithEmailAndPassword(email, password) .addOnSuccessListener(new OnSuccessListener() { @Override public void onSuccess(AuthResult authResult) { String[] splited = fullName.split("\\s+"); User userToAdd = new User(email,splited[0],splited[1],isTwoFactorAuthOn,phoneNumber,phoneCountryCode); usersTable.child(FirebaseAuth.getInstance().getCurrentUser().getUid()).setValue(userToAdd).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { Log.d("Failure",e.getMessage()); } }); Toast.makeText(activity, "Successfully signed to SampleAuth app!", Toast.LENGTH_LONG).show(); } }).addOnCompleteListener(new OnCompleteListener() { @Override public void onComplete(@NonNull Task task) { activity.startActivity(new Intent(activity, LoginActivity.class)); } }).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { Log.e("ERROR:",e.getMessage()); } }); }
Requesting TOTPs through a Google authenticator app on a different device (QR code scanning)
Here we’ll work on Authy API. On login button’s click we’ll retrieve isTwoFactorAuthOn value for the current user from database. If it’s a false we simply let the user in to home ui, if it’s a true we do one of the following ( depending on user’s choice, so we have to manage a way to request it, a dialog like in the demo would be fair).
(findViewById(R.id.authAppOnOtherDevice)).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Statics.usersTable.child(FirebaseAuth.getInstance().getCurrentUser().getUid()).addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { /** 1.Get user creds **/ email = (dataSnapshot.getValue(User.class)).getEmailAddress(); username = (dataSnapshot.getValue(User.class)).getFirstName()+" "+(dataSnapshot.getValue(User.class)).getLastName(); // password= (dataSnapshot.getValue(User.class)).getPassword(); // password= (dataSnapshot.getValue(User.class)).getPassword(); phoneNumber = (dataSnapshot.getValue(User.class)).getPhoneNumber(); countryCode = (dataSnapshot.getValue(User.class)).getPhoneCountryCode(); addUserUrl = "https://api.authy.com/protected/json/users/new?user[email]="+email +"&user[cellphone]="+phoneNumber +"&user[country_code]="+countryCode+"&api_key=xxxxxxxxxxxxxxxxxxx"; /** 2.Add the user to the Authy API **/ // post call for Authy api to add a user | response contains the added user's id JsonObjectRequest jsObjRequest = new JsonObjectRequest(Request.Method.POST,addUserUrl,null, new Response.Listener() { @Override public void onResponse(JSONObject response) { Gson gson = new Gson(); try { /** get the returned id **/ JsonObject addedUser = gson.fromJson(response.getString("user"),JsonObject.class); addedUserId = (addedUser.get("id")).getAsString(); //Toast.makeText(getApplicationContext(), "Res: "+addedUserId, Toast.LENGTH_LONG).show(); /** 3.Call the Authy API to generate a QRCode:will be handled in QRCodeActivity**/ // and pass it to next activity Intent qrCodeIntent = new Intent( getContext().getApplicationContext(), QRCodeActivity.class); qrCodeIntent.putExtra("userId",addedUserId); dismiss(); getContext().startActivity(qrCodeIntent); /** 4. validating user provided key: will be handled in QRCodeActivity**/ } catch (JSONException e) { e.printStackTrace(); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Log.e("ERROR! ",error.getMessage()); } }); (AppSingleton.getInstance(getContext()).getRequestQueue()).add(jsObjRequest); } @Override public void onCancelled(DatabaseError databaseError) { throw databaseError.toException(); } }); } });
We simply need to display a QR code generated by Authy API to the user, which contains a compatible OTP secret (step 3 in comments) and verify it through the API (step 4 in comments). But, to be able to use this API you have to register your app on your Twilio console, and register user’s phone number and email to authy service each time you register a user to your app (step 2 in comments).
Creating an app on Authy console goes as follows:
Adding a user to authy API is done through a simple REST call to
https://api.authy.com/protected/json/users/new
?user[email]=X&user[country_code]=Y&user[cellphone]=Z
which we did using Volley API in step 2 of previous code, after getting user’s phone number and email (step 1 of comments).
Now generating the QR code (our TOTP) is done in QRCodeActivity ( it’s a simple choice to display and validate the QR code in a different activity as you can see in the demo, you can simply put an ImageView, EditText to enter the passcode after scanning QR through Google authenticator, and a button for verification in the same ui, or in a dialog, ect..)
So we’ll call the authy API to get the QR code ( step 2 in comments below), load it in the ImageView using Picasso API (step 3 in comments below), and wait for the user to scan it and enter a passcode to our EditText to verify it (step 4 in comments below) :
/** 1. get auth creds from previous activity **/ Bundle extras = getIntent().getExtras(); if (extras != null) { userId= extras.getString("userId"); } qrCodeCallUrl="https://api.authy.com/protected/json/users/"+userId+"/secret?api_key=xxxxxxxxxxxxxxxxxx"; /** 2. call authy api to get qr code **/ JsonObjectRequest jsObjRequest = new JsonObjectRequest(Request.Method.POST,qrCodeCallUrl,null, new Response.Listener() { @Override public void onResponse(JSONObject response) { try { String qrCodePath = response.getString("qr_code"); /** 3. set the imageView's src **/ ImageView qrCodeImgVw = findViewById(R.id.qrCodeImgVw); Picasso.get().load(qrCodePath).into(qrCodeImgVw); } catch (JSONException e) { e.printStackTrace(); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Log.e("ERROR! ",error.getMessage()); } }); (AppSingleton.getInstance(getApplicationContext()).getRequestQueue()).add(jsObjRequest); /** 4. pass the code provided by user to the Authy API to verify it **/ (findViewById(R.id.confirmSignupBtn)).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Statics.validateSecurityCode(((EditText)findViewById(R.id.validationCode)).getText().toString(),userId,QRCodeActivity.this, ((EditText)findViewById(R.id.validationCode)),((TextView)findViewById(R.id.errorTxt))); } });
Note that the REST call to authy API to get a TOTP required a userId and an api key :
https://api.authy.com/protected/json/users/{AUTHY_ID}/secret?api_key=myApiKey
– the user Id is returned by the call we made when we registered the user to authy API. If you jump back to step 2 of previous code, you’ll find I retreived the userId:
/** get the returned id **/ JsonObject addedUser = gson.fromJson(response.getString("user"),JsonObject.class); addedUserId = (addedUser.get("id")).getAsString();
and passed it to QRCode activity as an intent extra data.
– and the api key is found on your app dashboard on twilio console :
Requesting Time-based One Time Passwords through a Google authenticator app in the same device
In the previous case the user added 2FAsample app to google authenticator by scanning the QR code we displayed. The second option any authentication app offers is to directly enter a provided key instead of generating it by scanning the QR. In the case of Google authenticator app it’s the following 2 options:
Implementing the second option takes less work than what we had in the first case. We’ll do almost the same of getting credentials of authenticated firebase user upon login (step 1 in comments), to register a new user in authy api as explained above, (step 2 in comments) and verify the passcode entered by the user (in next section). What’s different is we’ll simply start an intent to google authenticator app with a Uri containing our secrect key :
String uri = "otpauth://totp/2FAsampleApp:" + email + "?secret=" + mySecretKey + "&issuer=2FAsampleApp";
which will add our 2FAsampleApp to Google authenticator :
(findViewById(R.id.authAppOnThisPhone)).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Statics.usersTable.child(FirebaseAuth.getInstance().getCurrentUser().getUid()).addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { /***** 1.Get user creds *****/ email = (dataSnapshot.getValue(User.class)).getEmailAddress(); username = (dataSnapshot.getValue(User.class)).getFirstName()+" "+(dataSnapshot.getValue(User.class)).getLastName(); phoneNumber = (dataSnapshot.getValue(User.class)).getPhoneNumber(); countryCode = (dataSnapshot.getValue(User.class)).getPhoneCountryCode(); addUserUrl = "https://api.authy.com/protected/json/users/new?user[email]="+email +"&user[cellphone]="+phoneNumber +"&user[country_code]="+countryCode+"&api_key=xxxxxxxxxxxxxxxx"; /***** 2.Add the user to the Authy API *****/ // post call for Authy api to add a user | response contains the added user's id JsonObjectRequest jsObjRequest = new JsonObjectRequest(Request.Method.POST,addUserUrl,null, new Response.Listener() { @Override public void onResponse(JSONObject response) { Gson gson = new Gson(); try { /** get the returned id **/ JsonObject addedUser = gson.fromJson(response.getString("user"),JsonObject.class); addedUserId = (addedUser.get("id")).getAsString(); //Toast.makeText(getApplicationContext(), "Res: "+addedUserId, Toast.LENGTH_LONG).show(); /***** 3.Call the Authy API to generate appropriate passcode * then start an intent to GoogleAuthenticator app to use it ! *****/ String uri = "otpauth://totp/2FAAuthSample:" + email + "?secret=" + "811854" + "&issuer=2FAAuthSample"; Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri)); getContext().startActivity(intent); /***** 4.Ask user for passcode and validate it ****/ AlertDialog.Builder alertDialog = new AlertDialog.Builder(getContext()); alertDialog.setTitle("Validate security code"); alertDialog.setMessage("Enter the code you received in sms"); final EditText input = new EditText(getContext()); LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT); input.setLayoutParams(lp); alertDialog.setView(input); alertDialog.setPositiveButton("Validate", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { /** call authy api to validate code provided by the user Statics.validateSecurityCode(input.getText().toString(),addedUserId,getContext()); } }); alertDialog.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { dialog.cancel(); } }); alertDialog.show(); } catch (JSONException e) { e.printStackTrace(); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Log.e("ERROR! ",error.getMessage()); } }); (AppSingleton.getInstance(getContext()).getRequestQueue()).add(jsObjRequest); } @Override public void onCancelled(DatabaseError databaseError) { throw databaseError.toException(); } }); } });
TOTPs verification
Verifying TOTPs against codes entered by the user is as simple as performing this REST call :
GET https://api.authy.com/protected/json/verify/{TOKEN}/{AUTHY_ID}
with AUTHY_ID is the user ID you retreived when adding user to authy and TOKEN is the passcode you want to verify. For all verification call in this sample I used the following static method :
public static void validateSecurityCode(String code, final String userId, final Context context, final EditText codeTxt, final TextView errorTxt){
String codeValidationUrl="https://api.authy.com/protected/json/verify/"+code+"/"+userId+"?api_key=xxxxxxxxxxxxxxx";
JsonObjectRequest jsObjRequest = new JsonObjectRequest(Request.Method.GET,codeValidationUrl,null,
new Response.Listener() {
@Override
public void onResponse(JSONObject response) {
try {
if((response.getString("token")).equals("is valid"))
context.startActivity(new Intent(context, HomeActivity.class));
else
Toast.makeText(context, "You typed a wrong code!", Toast.LENGTH_LONG).show();
} catch (JSONException e) {
e.printStackTrace();
}
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
codeTxt.setText("");
errorTxt.setVisibility(View.VISIBLE);
codeTxt.startAnimation( AnimationUtils.loadAnimation(context, R.anim.errormsg_slide));
}
});
(AppSingleton.getInstance(context).getRequestQueue()).add(jsObjRequest);
}
That’s all coders !
I provide you with the Source code for the whole sample app and other references that helped me build this tutorial:
– Documentation for Authy API .
– Google Authenticator app support now available in Authy API on twilio.com.
You can follow up with the next tutorial on implementing SMS text two-factor authentication using the same service. Let me know in comments below if you have any doubts or questions.
Recent Comments