Adding multi-factor authentication to your Android app
This document shows you how to add SMS multi-factor authentication to your Android app.
Multi-factor authentication increases the security of your app. While attackers often compromise passwords and social accounts, intercepting a text message is more difficult.
Before you begin
Enable at least one provider that supports multi-factor authentication. Every provider supports MFA, except phone auth, anonymous auth, and Apple Game Center.
Ensure your app is verifying user emails. MFA requires email verification. This prevents malicious actors from registering for a service with an email they don't own, and then locking out the real owner by adding a second factor.
Register your app's SHA-1 hash in the Firebase Console (your changes will automatically carry over to Google Cloud Identity Platform).
Follow the steps in Authenticating your client to obtain your app's SHA-1 hash.
Open the Firebase Console.
Navigate to Project Settings.
Under Your apps, click the Android icon.
Follow the guided steps to add your SHA-1 hash.
Enabling multi-factor authentication
Go to the Identity Platform MFA page in the Google Cloud console.
Go to MFAIn Multi-Factor Authentication, click Enable.
Enter the phone numbers you'll be testing your app with. While optional, registering test phone numbers is strongly recommended to avoid throttling during development.
If you haven't already authorized your app's domain, add it to the allowlist by clicking Add domain.
Click Save.
Choosing an enrollment pattern
You can choose whether your app requires multi-factor authentication, and how and when to enroll your users. Some common patterns include:
Enroll the user's second factor as part of registration. Use this method if your app requires multi-factor authentication for all users.
Offer a skippable option to enroll a second factor during registration. Apps that want to encourage, but not require, multi-factor authentication might prefer this approach.
Provide the ability to add a second factor from the user's account or profile management page, instead of the sign up screen. This minimizes friction during the registration process, while still making multi-factor authentication available for security-sensitive users.
Require adding a second factor incrementally when the user wants to access features with increased security requirements.
Enrolling a second factor
To enroll a new secondary factor for a user:
Re-authenticate the user.
Ask the user enter their phone number.
Get a multi-factor session for the user:
Kotlin+KTX
user.multiFactor.session.addOnCompleteListener{task-> if(task.isSuccessful){ valmultiFactorSession:MultiFactorSession? =task.result } }Java
user.getMultiFactor().getSession() .addOnCompleteListener( newOnCompleteListener<MultiFactorSession>(){ @Override publicvoidonComplete(@NonNullTask<MultiFactorSession>task){ if(task.isSuccessful()){ MultiFactorSessionmultiFactorSession=task.getResult(); } } });Build an
OnVerificationStateChangedCallbacksobject to handle different events in the verification process:Kotlin+KTX
valcallbacks=object:OnVerificationStateChangedCallbacks(){ overridefunonVerificationCompleted(credential:PhoneAuthCredential){ // This callback will be invoked in two situations: // 1) Instant verification. In some cases, the phone number can be // instantly verified without needing to send or enter a verification // code. You can disable this feature by calling // PhoneAuthOptions.builder#requireSmsValidation(true) when building // the options to pass to PhoneAuthProvider#verifyPhoneNumber(). // 2) Auto-retrieval. On some devices, Google Play services can // automatically detect the incoming verification SMS and perform // verification without user action. this@MainActivity.credential=credential } overridefunonVerificationFailed(e:FirebaseException){ // This callback is invoked in response to invalid requests for // verification, like an incorrect phone number. if(eisFirebaseAuthInvalidCredentialsException){ // Invalid request // ... }elseif(eisFirebaseTooManyRequestsException){ // The SMS quota for the project has been exceeded // ... } // Show a message and update the UI // ... } overridefunonCodeSent( verificationId:String,forceResendingToken:ForceResendingToken ){ // The SMS verification code has been sent to the provided phone number. // We now need to ask the user to enter the code and then construct a // credential by combining the code with a verification ID. // Save the verification ID and resending token for later use. this@MainActivity.verificationId=verificationId this@MainActivity.forceResendingToken=forceResendingToken // ... } }Java
OnVerificationStateChangedCallbackscallbacks= newOnVerificationStateChangedCallbacks(){ @Override publicvoidonVerificationCompleted(PhoneAuthCredentialcredential){ // This callback will be invoked in two situations: // 1) Instant verification. In some cases, the phone number can be // instantly verified without needing to send or enter a verification // code. You can disable this feature by calling // PhoneAuthOptions.builder#requireSmsValidation(true) when building // the options to pass to PhoneAuthProvider#verifyPhoneNumber(). // 2) Auto-retrieval. On some devices, Google Play services can // automatically detect the incoming verification SMS and perform // verification without user action. this.credential=credential; } @Override publicvoidonVerificationFailed(FirebaseExceptione){ // This callback is invoked in response to invalid requests for // verification, like an incorrect phone number. if(einstanceofFirebaseAuthInvalidCredentialsException){ // Invalid request // ... }elseif(einstanceofFirebaseTooManyRequestsException){ // The SMS quota for the project has been exceeded // ... } // Show a message and update the UI // ... } @Override publicvoidonCodeSent( StringverificationId,PhoneAuthProvider.ForceResendingTokentoken){ // The SMS verification code has been sent to the provided phone number. // We now need to ask the user to enter the code and then construct a // credential by combining the code with a verification ID. // Save the verification ID and resending token for later use. this.verificationId=verificationId; this.forceResendingToken=token; // ... } };Initialize a
PhoneInfoOptionsobject with the user's phone number, the multi-factor session, and your callbacks:Kotlin+KTX
valphoneAuthOptions=PhoneAuthOptions.newBuilder() .setPhoneNumber(phoneNumber) .setTimeout(30L,TimeUnit.SECONDS) .setMultiFactorSession(MultiFactorSession) .setCallbacks(callbacks) .build()Java
PhoneAuthOptionsphoneAuthOptions= PhoneAuthOptions.newBuilder() .setPhoneNumber(phoneNumber) .setTimeout(30L,TimeUnit.SECONDS) .setMultiFactorSession(multiFactorSession) .setCallbacks(callbacks) .build();By default, instant verification is enabled. To disable it, add a call to
requireSmsValidation(true).Send a verification message to the user's phone:
Kotlin+KTX
PhoneAuthProvider.verifyPhoneNumber(phoneAuthOptions)Java
PhoneAuthProvider.verifyPhoneNumber(phoneAuthOptions);While not required, it's a best practice to inform users beforehand that they will receive an SMS message, and that standard rates apply.
Once the SMS code is sent, ask the user to verify the code:
Kotlin+KTX
// Ask user for the verification code. valcredential=PhoneAuthProvider.getCredential(verificationId,verificationCode)Java
// Ask user for the verification code. PhoneAuthCredentialcredential =PhoneAuthProvider.getCredential(verificationId,verificationCode);Initialize a
MultiFactorAssertionobject with thePhoneAuthCredential:Kotlin+KTX
valmultiFactorAssertion =PhoneMultiFactorGenerator.getAssertion(credential)Java
MultiFactorAssertionmultiFactorAssertion =PhoneMultiFactorGenerator.getAssertion(credential);Complete the enrollment. Optionally, you can specify a display name for the second factor. This is useful for users with multiple second factors, since the phone number is masked during the authentication flow (for example, +1******1234).
Kotlin+KTX
// Complete enrollment. This will update the underlying tokens // and trigger ID token change listener. FirebaseAuth.getInstance() .currentUser ?.multiFactor ?.enroll(multiFactorAssertion,"My personal phone number") ?.addOnCompleteListener{ // ... }Java
// Complete enrollment. This will update the underlying tokens // and trigger ID token change listener. FirebaseAuth.getInstance() .getCurrentUser() .getMultiFactor() .enroll(multiFactorAssertion,"My personal phone number") .addOnCompleteListener( newOnCompleteListener<Void>(){ @Override publicvoidonComplete(@NonNullTask<Void>task){ // ... } });
The code below shows a complete example of enrolling a second factor:
Kotlin+KTX
valmultiFactorAssertion=PhoneMultiFactorGenerator.getAssertion(credential)
user.multiFactor.session
.addOnCompleteListener{task->
if(task.isSuccessful){
valmultiFactorSession=task.result
valphoneAuthOptions=PhoneAuthOptions.newBuilder()
.setPhoneNumber(phoneNumber)
.setTimeout(30L,TimeUnit.SECONDS)
.setMultiFactorSession(multiFactorSession)
.setCallbacks(callbacks)
.build()
// Send SMS verification code.
PhoneAuthProvider.verifyPhoneNumber(phoneAuthOptions)
}
}
// Ask user for the verification code.
valcredential=PhoneAuthProvider.getCredential(verificationId,verificationCode)
valmultiFactorAssertion=PhoneMultiFactorGenerator.getAssertion(credential)
// Complete enrollment.
FirebaseAuth.getInstance()
.currentUser
?.multiFactor
?.enroll(multiFactorAssertion,"My personal phone number")
?.addOnCompleteListener{
// ...
}
Java
MultiFactorAssertionmultiFactorAssertion=PhoneMultiFactorGenerator.getAssertion(credential);
user.getMultiFactor().getSession()
.addOnCompleteListener(
newOnCompleteListener<MultiFactorSession>(){
@Override
publicvoidonComplete(@NonNullTask<MultiFactorSession>task){
if(task.isSuccessful()){
MultiFactorSessionmultiFactorSession=task.getResult();
PhoneAuthOptionsphoneAuthOptions=
PhoneAuthOptions.newBuilder()
.setPhoneNumber(phoneNumber)
.setTimeout(30L,TimeUnit.SECONDS)
.setMultiFactorSession(multiFactorSession)
.setCallbacks(callbacks)
.build();
// Send SMS verification code.
PhoneAuthProvider.verifyPhoneNumber(phoneAuthOptions);
}
}
});
// Ask user for the verification code.
PhoneAuthCredentialcredential=
PhoneAuthProvider.getCredential(verificationId,verificationCode);
MultiFactorAssertionmultiFactorAssertion=PhoneMultiFactorGenerator.getAssertion(credential);
// Complete enrollment.
FirebaseAuth.getInstance()
.getCurrentUser()
.getMultiFactor()
.enroll(multiFactorAssertion,"My personal phone number")
.addOnCompleteListener(
newOnCompleteListener<Void>(){
@Override
publicvoidonComplete(@NonNullTask<Void>task){
// ...
}
});
Congratulations! You successfully registered a second authentication factor for a user.
Signing users in with a second factor
To sign in a user with two-factor SMS verification:
Sign the user in with their first factor, then catch the
FirebaseAuthMultiFactorExceptionexception. This error contains a resolver, which you can use to obtain the user's enrolled second factors. It also contains an underlying session proving the user successfully authenticated with their first factor.For example, if the user's first factor was an email and password:
Kotlin+KTX
FirebaseAuth.getInstance() .signInWithEmailAndPassword(email,password) .addOnCompleteListener( OnCompleteListener{task-> if(task.isSuccessful){ // User is not enrolled with a second factor and is successfully // signed in. // ... return@OnCompleteListener } if(task.exceptionisFirebaseAuthMultiFactorException){ // The user is a multi-factor user. Second factor challenge is // required. valmultiFactorResolver= (task.exceptionasFirebaseAuthMultiFactorException).resolver // ... }else{ // Handle other errors, such as wrong password. } })Java
FirebaseAuth.getInstance() .signInWithEmailAndPassword(email,password) .addOnCompleteListener( newOnCompleteListener<AuthResult>(){ @Override publicvoidonComplete(@NonNullTask<AuthResult>task){ if(task.isSuccessful()){ // User is not enrolled with a second factor and is successfully // signed in. // ... return; } if(task.getException()instanceofFirebaseAuthMultiFactorException){ // The user is a multi-factor user. Second factor challenge is // required. MultiFactorResolvermultiFactorResolver=task.getException().getResolver(); // ... }else{ // Handle other errors such as wrong password. } } });If the user's first factor is a federated provider, such as OAuth, catch the error after calling
startActivityForSignInWithProvider().If the user has multiple secondary factors enrolled, ask them which one to use:
Kotlin+KTX
// Ask user which second factor to use. // You can get the list of enrolled second factors using // multiFactorResolver.hints // Check the selected factor: if(multiFactorResolver.hints[selectedIndex].factorId ===PhoneMultiFactorGenerator.FACTOR_ID ){ // User selected a phone second factor. valselectedHint= multiFactorResolver.hints[selectedIndex]asPhoneMultiFactorInfo }elseif(multiFactorResolver.hints[selectedIndex].factorId ===TotpMultiFactorGenerator.FACTOR_ID){ // User selected a TOTP second factor. }else{ // Unsupported second factor. }Java
// Ask user which second factor to use. // You can get the masked phone number using // resolver.getHints().get(selectedIndex).getPhoneNumber() // You can get the display name using // resolver.getHints().get(selectedIndex).getDisplayName() if(resolver.getHints() .get(selectedIndex) .getFactorId() .equals(PhoneMultiFactorGenerator.FACTOR_ID)){ // User selected a phone second factor. MultiFactorInfoselectedHint= multiFactorResolver.getHints().get(selectedIndex); }elseif(resolver .getHints() .get(selectedIndex) .getFactorId() .equals(TotpMultiFactorGenerator.FACTOR_ID)){ // User selected a TOTP second factor. }else{ // Unsupported second factor. }Initialize a
PhoneAuthOptionsobject with the hint and multi-factor session. These values are contained in the resolver attached to theFirebaseAuthMultiFactorException.Kotlin+KTX
valphoneAuthOptions=PhoneAuthOptions.newBuilder() .setMultiFactorHint(selectedHint) .setTimeout(30L,TimeUnit.SECONDS) .setMultiFactorSession(multiFactorResolver.session) .setCallbacks(callbacks)// Optionally disable instant verification. // .requireSmsValidation(true) .build()Java
PhoneAuthOptionsphoneAuthOptions= PhoneAuthOptions.newBuilder() .setMultiFactorHint(selectedHint) .setTimeout(30L,TimeUnit.SECONDS) .setMultiFactorSession(multiFactorResolver.getSession()) .setCallbacks(callbacks) // Optionally disable instant verification. // .requireSmsValidation(true) .build();Send a verification message to the user's phone:
Kotlin+KTX
// Send SMS verification code PhoneAuthProvider.verifyPhoneNumber(phoneAuthOptions)Java
// Send SMS verification code PhoneAuthProvider.verifyPhoneNumber(phoneAuthOptions);Once the SMS code is sent, ask the user to verify the code:
Kotlin+KTX
// Ask user for the verification code. Then, pass it to getCredential: valcredential= PhoneAuthProvider.getCredential(verificationId,verificationCode)Java
// Ask user for the verification code. Then, pass it to getCredential: PhoneAuthCredentialcredential =PhoneAuthProvider.getCredential(verificationId,verificationCode);Initialize a
MultiFactorAssertionobject with thePhoneAuthCredential:Kotlin+KTX
valmultiFactorAssertion=PhoneMultiFactorGenerator.getAssertion(credential)Java
MultiFactorAssertionmultiFactorAssertion =PhoneMultiFactorGenerator.getAssertion(credential);Call
resolver.resolveSignIn()to complete secondary authentication. You can then access the original sign-in result, which includes the standard provider-specific data and authentication credentials:Kotlin+KTX
multiFactorResolver .resolveSignIn(multiFactorAssertion) .addOnCompleteListener{task-> if(task.isSuccessful){ valauthResult=task.result // AuthResult will also contain the user, additionalUserInfo, // and an optional credential (null for email/password) // associated with the first factor sign-in. // For example, if the user signed in with Google as a first // factor, authResult.getAdditionalUserInfo() will contain data // related to Google provider that the user signed in with; // authResult.getCredential() will contain the Google OAuth // credential; // authResult.getCredential().getAccessToken() will contain the // Google OAuth access token; // authResult.getCredential().getIdToken() contains the Google // OAuth ID token. } }Java
multiFactorResolver .resolveSignIn(multiFactorAssertion) .addOnCompleteListener( newOnCompleteListener<AuthResult>(){ @Override publicvoidonComplete(@NonNullTask<AuthResult>task){ if(task.isSuccessful()){ AuthResultauthResult=task.getResult(); // AuthResult will also contain the user, additionalUserInfo, // and an optional credential (null for email/password) // associated with the first factor sign-in. // For example, if the user signed in with Google as a first // factor, authResult.getAdditionalUserInfo() will contain data // related to Google provider that the user signed in with. // authResult.getCredential() will contain the Google OAuth // credential. // authResult.getCredential().getAccessToken() will contain the // Google OAuth access token. // authResult.getCredential().getIdToken() contains the Google // OAuth ID token. } } });
The code below shows a complete example of signing in a multi-factor user:
Kotlin+KTX
FirebaseAuth.getInstance()
.signInWithEmailAndPassword(email,password)
.addOnCompleteListener{task->
if(task.isSuccessful){
// User is not enrolled with a second factor and is successfully
// signed in.
// ...
return@addOnCompleteListener
}
if(task.exceptionisFirebaseAuthMultiFactorException){
valmultiFactorResolver=
(task.exceptionasFirebaseAuthMultiFactorException).resolver
// Ask user which second factor to use. Then, get
// the selected hint:
valselectedHint=
multiFactorResolver.hints[selectedIndex]asPhoneMultiFactorInfo
// Send the SMS verification code.
PhoneAuthProvider.verifyPhoneNumber(
PhoneAuthOptions.newBuilder()
.setActivity(this)
.setMultiFactorSession(multiFactorResolver.session)
.setMultiFactorHint(selectedHint)
.setCallbacks(generateCallbacks())
.setTimeout(30L,TimeUnit.SECONDS)
.build()
)
// Ask user for the SMS verification code, then use it to get
// a PhoneAuthCredential:
valcredential=
PhoneAuthProvider.getCredential(verificationId,verificationCode)
// Initialize a MultiFactorAssertion object with the
// PhoneAuthCredential.
valmultiFactorAssertion:MultiFactorAssertion=
PhoneMultiFactorGenerator.getAssertion(credential)
// Complete sign-in.
multiFactorResolver
.resolveSignIn(multiFactorAssertion)
.addOnCompleteListener{task->
if(task.isSuccessful){
// User successfully signed in with the
// second factor phone number.
}
// ...
}
}else{
// Handle other errors such as wrong password.
}
}
Java
FirebaseAuth.getInstance()
.signInWithEmailAndPassword(email,password)
.addOnCompleteListener(
newOnCompleteListener<AuthResult>(){
@Override
publicvoidonComplete(@NonNullTask<AuthResult>task){
if(task.isSuccessful()){
// User is not enrolled with a second factor and is successfully
// signed in.
// ...
return;
}
if(task.getException()instanceofFirebaseAuthMultiFactorException){
FirebaseAuthMultiFactorExceptione=
(FirebaseAuthMultiFactorException)task.getException();
MultiFactorResolvermultiFactorResolver=e.getResolver();
// Ask user which second factor to use.
MultiFactorInfoselectedHint=
multiFactorResolver.getHints().get(selectedIndex);
// Send the SMS verification code.
PhoneAuthProvider.verifyPhoneNumber(
PhoneAuthOptions.newBuilder()
.setActivity(this)
.setMultiFactorSession(multiFactorResolver.getSession())
.setMultiFactorHint(selectedHint)
.setCallbacks(generateCallbacks())
.setTimeout(30L,TimeUnit.SECONDS)
.build());
// Ask user for the SMS verification code.
PhoneAuthCredentialcredential=
PhoneAuthProvider.getCredential(verificationId,verificationCode);
// Initialize a MultiFactorAssertion object with the
// PhoneAuthCredential.
MultiFactorAssertionmultiFactorAssertion=
PhoneMultiFactorGenerator.getAssertion(credential);
// Complete sign-in.
multiFactorResolver
.resolveSignIn(multiFactorAssertion)
.addOnCompleteListener(
newOnCompleteListener<AuthResult>(){
@Override
publicvoidonComplete(@NonNullTask<AuthResult>task){
if(task.isSuccessful()){
// User successfully signed in with the
// second factor phone number.
}
// ...
}
});
}else{
// Handle other errors such as wrong password.
}
}
});
Congratulations! You successfully signed in a user using multi-factor authentication.
What's next
- Manage multi-factor users programmatically with the Admin SDK.