Multi-factor authentication is a feature that allows the user to add an extra layer of security. It can be a code received by SMS or saved in a special app. You’ll need to provide this code to connect when you connect to your app. If your application is manipulating sensitive data, it’s often a good idea to add Multi-factor authentication so your user’s data is more secure.
🚀 Phone multi-factor Authentication is now supported in FlutterFire! You’re going to be able to offer an extra layer of security to your users.
In this tutorial, you’ll learn how to add phone multi-factor authentication to your Flutter app, on Android, iOS, and Web!
💡 Only phone is currently supported. The support for other methods (action code) is coming soon. Moreover, Android, iOS, and Web platforms are supported.
Before you begin
- Go to your Firebase console, and enable the email and password provider.
- Ensure that your app is verifying user emails. MFA requires email verification. It’s to prevent locking out the real owner of an email from the account.
You can verify the email of your user with user.sendEmailVerification()
method.
Enabling MFA
To use MFA, you first need to activate it in your Google Cloud console:
- Go to the Identity Platform MFA page in the Google Cloud console.
- In the box titled SMS-Based Multi-Factor Authentication, click Enable.
- Enter the phone numbers you’ll use for testing. 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 allow list by clicking Add domain on the right (required for Web).
- Click Save.
There are multiple patterns for MFA enrollment that you can check in the official documentation. Choose the one that suits your app best.
Verifying your app
For iOS, you’ll need an extra setup step for verifying your app; check the steps “Verifying your app” step on the official guide. You can either choose APN or reCaptcha verification.
Enrolling a second factor
To enroll a new secondary factor for a user:
- Re-authenticate the user.
- Ask the user for their phone number.
Note: Google stores and uses phone numbers to improve spam and abuse prevention across all Google services. Ensure you obtain appropriate consent from your users before sending their phone numbers to the Identity Platform.
1- Get the multi-factor session for the user:
final session = await user.multiFactor.getSession();
2- Verify the phone number with the multi factor session:
await FirebaseAuth.instance.verifyPhoneNumber(
multiFactorSession: session,
phoneNumber: phoneNumber,
verificationCompleted: (_) {},
verificationFailed: (_) {},
codeSent: (String verificationId, int? resendToken) async {
...
},
codeAutoRetrievalTimeout: (_) {},
);
3-
Get the SMS code from the user and create a new phone credential:
final credential = PhoneAuthProvider.credential(
verificationId: verificationId,
smsCode: smsCode,
);
4- Enroll the new factor:
await user.multiFactor.enroll(
PhoneMultiFactorGenerator.getAssertion(
credential,
),
);
Complete example:
final session = await user.multiFactor.getSession();
final auth = FirebaseAuth.instance;
await auth.verifyPhoneNumber(
multiFactorSession: session,
phoneNumber: phoneController.text,
verificationCompleted: (_) {},
verificationFailed: (_) {},
codeSent:
(String verificationId, int? resendToken) async {
// See `firebase_auth` example app for a method of retrieving user's sms code:
// https://github.com/firebase/flutterfire/blob/master/packages/firebase_auth/firebase_auth/example/lib/auth.dart#L591
final smsCode = await getSmsCodeFromUser(context);
if (smsCode != null) {
// Create a PhoneAuthCredential with the code
final credential = PhoneAuthProvider.credential(
verificationId: verificationId,
smsCode: smsCode,
);
try {
await user.multiFactor.enroll(
PhoneMultiFactorGenerator.getAssertion(
credential,
),
);
} on FirebaseAuthException catch (e) {
print(e.message);
}
}
},
codeAutoRetrievalTimeout: (_) {},
);
Signing users in with a second factor
To sign in a user with two-factor SMS verification:
1. Sign the user in with their first factor, then catch the error. This error contains a resolver, hints on the enrolled second factors, and an underlying session proving the user successfully authenticated with the first factor
Sign the user in with their first factor, then catch the error. This error contains a resolver, hints on the enrolled second factors, and an underlying session proving the user successfully authenticated with the first factor.
try {
await _auth.signInWithEmailAndPassword(
email: emailController.text,
password: passwordController.text,
);
} on FirebaseAuthMultiFactorException catch (e) {
...
2. If the user has multiple secondary factors enrolled, ask them which one to use:
final session = e.resolver.session;
final hint = e.resolver.hints[selectedHint];
3- Send a verification message to the user’s phone using the resolver’s session and the hint (note that you don’t need the user’s phoneNumber):
await FirebaseAuth.instance.verifyPhoneNumber(
multiFactorSession: session,
multiFactorInfo: hint,
verificationCompleted: (_) {},
verificationFailed: (_) {},
codeSent: (String verificationId, int? resendToken) async {
...
},
codeAutoRetrievalTimeout: (_) {},
);
4- Resolve the sign in with the resolver:
final smsCode = await getSmsCodeFromUser(context);
if (smsCode != null) {
// Create a PhoneAuthCredential with the code
final credential = PhoneAuthProvider.credential(
verificationId: verificationId,
smsCode: smsCode,
);
try {
await e.resolver.resolveSignIn(
PhoneMultiFactorGenerator.getAssertion(
credential,
),
);
} on FirebaseAuthException catch (e) {
print(e.message);
}
}
Complete example:
try {
await _auth.signInWithEmailAndPassword(
email: emailController.text,
password: passwordController.text,
);
} on FirebaseAuthMultiFactorException catch (e) {
setState(() {
error = '${e.message}';
});
final firstHint = e.resolver.hints.first;
if (firstHint is! PhoneMultiFactorInfo) {
return;
}
await FirebaseAuth.instance.verifyPhoneNumber(
multiFactorSession: e.resolver.session,
multiFactorInfo: firstHint,
verificationCompleted: (_) {},
verificationFailed: (_) {},
codeSent: (String verificationId, int? resendToken) async {
// See `firebase_auth` example app for a method of retrieving user's sms code:
// https://github.com/firebase/flutterfire/blob/master/packages/firebase_auth/firebase_auth/example/lib/auth.dart#L591
final smsCode = await getSmsCodeFromUser(context);
if (smsCode != null) {
// Create a PhoneAuthCredential with the code
final credential = PhoneAuthProvider.credential(
verificationId: verificationId,
smsCode: smsCode,
);
try {
await e.resolver.resolveSignIn(
PhoneMultiFactorGenerator.getAssertion(
credential,
),
);
} on FirebaseAuthException catch (e) {
print(e.message);
}
}
},
codeAutoRetrievalTimeout: (_) {},
);
} catch (e) {
...
}
Congratulations! 🎉 You successfully signed in a user using multi-factor authentication.