Reputation: 3202
I am developing an Ionic/Angular app with Capacitor V6, and I've been struggling to deploy it on iOS. I initially used the @angular/fire library for phone authentication, but encountered issues implementing reCAPTCHA on iOS. While it works fine on the web, I can't pass an ApplicationVerifier object on iOS.
I then explored the @capacitor-firebase/authentication library as an alternative and followed the example in this repository: capacitor-firebase-authentication-demo, which supposedly works with Ionic and Capacitor. However, despite following the instructions, I still can’t get it to work on iOS.
Here are the specific issues I’m facing with @capacitor-firebase/authentication library:
When running the app on the web (using ng serve) and selecting the "Phone" option in the authentication flow, I receive the following error: "recaptchaVerifier must be provided and must be an instance of RecaptchaVerifier." When I open the app in Xcode and run it on an iOS device, the phone authentication doesn’t work either. Has anyone else encountered this issue and successfully implemented phone authentication in an Ionic/Angular app with Capacitor on iOS? I've been trying different solutions for weeks, but still haven’t found one that works.
Many Thanks!
Upvotes: 1
Views: 201
Reputation: 1074
You should try the solution below, it handles both web and iOS platforms correctly:
import { Injectable } from '@angular/core';
import { Platform } from '@ionic/angular';
import { FirebaseAuthentication } from '@capacitor-firebase/authentication';
import { initializeApp } from 'firebase/app';
import {
getAuth,
RecaptchaVerifier,
signInWithPhoneNumber,
ApplicationVerifier
} from 'firebase/auth';
import { BehaviorSubject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class PhoneAuthService {
private recaptchaVerifier: RecaptchaVerifier | null = null;
private auth = getAuth();
private confirmationResult: any = null;
private verificationId = new BehaviorSubject<string>('');
constructor(private platform: Platform) {}
async initRecaptcha(buttonId: string) {
if (this.platform.is('capacitor')) {
// Native platform (iOS/Android) - no need for web recaptcha
return;
}
// Web platform - initialize recaptcha
if (!this.recaptchaVerifier) {
this.recaptchaVerifier = new RecaptchaVerifier(this.auth, buttonId, {
size: 'invisible',
callback: () => {
// reCAPTCHA solved
},
'expired-callback': () => {
// Reset reCAPTCHA
this.recaptchaVerifier?.render().then((widgetId) => {
grecaptcha.reset(widgetId);
});
}
});
}
}
async startPhoneAuth(phoneNumber: string, buttonId: string): Promise<void> {
try {
if (this.platform.is('capacitor')) {
// Native platform authentication
const result = await FirebaseAuthentication.signInWithPhoneNumber({
phoneNumber
});
this.verificationId.next(result.verificationId);
} else {
// Web platform authentication
await this.initRecaptcha(buttonId);
if (!this.recaptchaVerifier) {
throw new Error('RecaptchaVerifier not initialized');
}
this.confirmationResult = await signInWithPhoneNumber(
this.auth,
phoneNumber,
this.recaptchaVerifier as ApplicationVerifier
);
}
} catch (error) {
console.error('Error starting phone auth:', error);
throw error;
}
}
async verifyCode(code: string): Promise<any> {
try {
if (this.platform.is('capacitor')) {
// Native platform verification
const result = await FirebaseAuthentication.signInWithPhoneNumber({
verificationId: this.verificationId.getValue(),
verificationCode: code
});
return result;
} else {
// Web platform verification
if (!this.confirmationResult) {
throw new Error('No confirmation result available');
}
const result = await this.confirmationResult.confirm(code);
return result;
}
} catch (error) {
console.error('Error verifying code:', error);
throw error;
}
}
cleanup() {
if (this.recaptchaVerifier) {
this.recaptchaVerifier.clear();
this.recaptchaVerifier = null;
}
this.confirmationResult = null;
this.verificationId.next('');
}
}
To use this service, you'll also need to set up your component properly. Here's how to implement it:
import { Component, OnDestroy } from '@angular/core';
import { PhoneAuthService } from './phone-auth.service';
@Component({
selector: 'app-phone-auth',
template: `
<ion-content>
<form (ngSubmit)="startAuth()">
<ion-item>
<ion-label position="floating">Phone Number</ion-label>
<ion-input type="tel" [(ngModel)]="phoneNumber" name="phone"></ion-input>
</ion-item>
<ion-button id="sign-in-button" type="submit" expand="block">
Send Code
</ion-button>
</form>
<form *ngIf="codeSent" (ngSubmit)="verifyCode()">
<ion-item>
<ion-label position="floating">Verification Code</ion-label>
<ion-input type="text" [(ngModel)]="verificationCode" name="code"></ion-input>
</ion-item>
<ion-button type="submit" expand="block">
Verify Code
</ion-button>
</form>
</ion-content>
`
})
export class PhoneAuthComponent implements OnDestroy {
phoneNumber = '';
verificationCode = '';
codeSent = false;
constructor(private phoneAuthService: PhoneAuthService) {}
async startAuth() {
try {
await this.phoneAuthService.startPhoneAuth(
this.phoneNumber,
'sign-in-button'
);
this.codeSent = true;
} catch (error) {
console.error('Error starting authentication:', error);
// Handle error appropriately
}
}
async verifyCode() {
try {
const result = await this.phoneAuthService.verifyCode(this.verificationCode);
// Handle successful authentication
console.log('Successfully authenticated:', result);
} catch (error) {
console.error('Error verifying code:', error);
// Handle error appropriately
}
}
ngOnDestroy() {
this.phoneAuthService.cleanup();
}
}
To make it work, you need to ensure your iOS configuration is correct. Follow these steps:
In your capacitor.config.ts
:
import { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
plugins: {
FirebaseAuthentication: {
skipNativeAuth: false,
providers: ['phone']
}
}
};
export default config;
In your iOS project, make sure you've:
GoogleService-Info.plist
AppDelegate.swift
to initialize FirebaseEnable Phone Authentication in your Firebase Console:
This implementation handles both web and native iOS platforms automatically; uses the appropriate authentication flow for each platform; properly manages the reCAPTCHA verifier on web; cleanly separates the authentication logic into a service; and provides proper error handling and cleanup.
Differences from your original approach:
Hope this helps!
Upvotes: 0