Reputation: 504
I'm developing an Android app that uses Firebase Authentication. After creating a new user, I'm sending a verification email.
Firebase.auth.currentUser?.let {user ->
user.sendEmailVerification().addOnCompleteListener {verificationTask ->
if(verificationTask.isSuccessful){
_verificationSuccess.value = true
} else {
_verificationError.value = verificationTask.exception
}
}
}
In my manifest, I added an Intent filter that opens my app when the user tries to open the verification link.
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:host="test-project.firebaseapp.com"
android:scheme="https"/>
</intent-filter>
The problem is that, when the link opens my app, the email is not verified. I have this validation that always returns false
if(Firebase.auth.currentUser.isEmailVerified){
//Email verified
} else {
throw Exception("Email not verified")
}
Only when the verification link is opened in the web explorer, the validation returns true. How can I verify the email inside my app?
Upvotes: 0
Views: 392
Reputation: 26306
By applying the intent filter you have, you've told Android that you will handle all web requests that target https://test-project.firebaseapp.com
. Now that your application has trapped browsing to this URL, you now need to make the request to the server on the user's behalf.
By default, when you send an email verification off to the user, they will receive an email with a link that looks like:
https://<PROJECT_ID>.firebaseapp.com/__/auth/action?mode=verifyEmail&oobCode=<VERIFICATION_CODE>&apiKey=<API_KEY>&lang=<LANGUAGE>
Note: These links can be customized using custom action handlers or you could make use of dynamic links so that you don't intercept everything headed for https://test-project.firebaseapp.com
.
Because a user may wish to visit your app's website in a browser, you should make your intent filter more specific to just handling email actions.
<intent-filter android:label="@string/filter_view_handle_action_code">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https"
android:host="test-project.firebaseapp.com"
android:pathPrefix="/__/auth/action" />
</intent-filter>
Disclaimer: I haven't tested if the below method works, but in theory it should.
If you intercept such a URL, you need to handle it in the onCreate
method:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Intent intent = this.getIntent();
if (intent.hasCategory(android.intent.category.BROWSABLE)) {
// intent came from browser
Uri data = intent.getData();
String mode = data.getQueryParameter("mode");
String oobCode = data.getQueryParameter("oobCode");
if (mode != "verifyEmail" || oobCode == null) {
Log.i("MyActivity", "Started with unexpected browsable intent");
return;
}
handleVerifyEmailActionCode(oobCode);
}
}
private handleVerifyEmailActionCode(String oobCode) {
String email = null;
FirebaseAuth.getInstance().checkActionCode(oobCode)
.onSuccessTask( actionCodeResult -> {
if (actionCodeResult.getOperation() != ActionCodeResult.VERIFY_EMAIL) {
throw new UnsupportedActionCodeOperationException(actionCodeResult.getOperation());
}
email = actionCodeResult.getInfo().getEmail();
return FirebaseAuth.getInstance().applyActionCode(oobCode);
})
.addOnCompleteListener( applyActionCodeTask -> {
if (!applyActionCodeTask.isSuccessful()) {
Exception ex = applyActionCodeTask.getException();
// TODO: Handle exceptions
Toast.makeText(getApplicationContext(), "Failed to verify email. " + ex.getMessage(), Toast.LENGTH_SHORT).show();
return;
}
// email verified successfully!
// do something interesting
Toast.makeText(getApplicationContext(), email + " was verified!", Toast.LENGTH_SHORT).show();
FirebaseUser user = FirebaseAuth.getCurrentUser(); // WARN: May not have finished initializing yet?
if (user != null && user.getEmail() == email) {
user.reload()
.addOnFailureListener(ex -> {
Log.e(MyActivity.class, "Failed to refresh user token: " + ex.getMessage());
});
} else {
Log.i(
MyActivity.class,
user == null
? "User wasn't signed in, skipping reload"
: "User email mismatch, skipping reload"
);
}
});
}
/**
* Custom exception to be used when a OOB code's `ActionCodeResult.Operation`
* doesn't match what it was expected to be.
*
* @see https://firebase.google.com/docs/reference/android/com/google/firebase/auth/ActionCodeResult#constant-summary
*/
public class UnsupportedActionCodeOperationException extends Exception {
protected int operation;
public UnsupportedActionCodeOperationException(int operation) {
super("The ActionCodeResult.Operation value of " + operation + " is not supported");
this.operation = operation;
}
public int getOperation() {
return this.operation;
}
}
Upvotes: 2