Firebase Auth手机认证遇auth/quota-exceeded配额超限错误求助
Firebase免费版手机认证触发配额超限问题
我正在测试一款使用Firebase免费版手机认证功能的应用,已按照官方文档将测试用手机号注册为虚拟号码,但测试时出现以下错误:
FirebaseError: Firebase: Exceeded quota. (auth/quota-exceeded). at _errorWithCustomMessage (index-e3d5d3f4.js:476:20) at index-e3d5d3f4.js:908:23 at Generator.next (<anonymous>) at asyncGeneratorStep (asyncToGenerator.js:3:1) at _next (asyncToGenerator.js:22:1) at _ZoneDelegate.invoke (zone.js:368:26) at Zone.run (zone.js:129:43) at zone.js:1257:36 at _ZoneDelegate.invokeTask (zone.js:402:31) at Zone.runTask (zone.js:173:47) node_modules_ionic_core_dist_esm_ion-backdrop_entry_js.js:2 ERROR Error: Uncaught (in promise): FirebaseError: Firebase: Exceeded quota. (auth/quota-exceeded). FirebaseError: Firebase: Exceeded quota. (auth/quota-exceeded). at _errorWithCustomMessage (index-e3d5d3f4.js:476:20) at index-e3d5d3f4.js:908:23 at Generator.next (<anonymous>) at asyncGeneratorStep (asyncToGenerator.js:3:1) at _next (asyncToGenerator.js:22:1) at _ZoneDelegate.invoke (zone.js:368:26) at Zone.run (zone.js:129:43) at zone.js:1257:36 at _ZoneDelegate.invokeTask (zone.js:402:31) at Zone.runTask (zone.js:173:47) at resolvePromise (zone.js:1193:31) at resolvePromise (zone.js:1147:17) at zone.js:1260:17 at _ZoneDelegate.invokeTask (zone.js:402:31) at core.mjs:10735:55 at AsyncStackTaggingZoneSpec.onInvokeTask (core.mjs:10735:36) at _ZoneDelegate.invokeTask (zone.js:401:60) at Object.onInvokeTask (core.mjs:11048:33) at _ZoneDelegate.invokeTask (zone.js:401:60) at Zone.runTask (zone.js:173:47)
我不清楚为何会触发配额超限,因为查看配额使用情况并未显示任何限制已被触发。此外,我该如何准确知晓具体超出了哪项限制,以及超限的确切原因?
补充代码:
TS组件代码:
import { Component, OnInit } from '@angular/core'; import { LoadingController } from '@ionic/angular'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { Router } from '@angular/router'; import { MaskitoOptions, MaskitoElementPredicateAsync } from '@maskito/core'; import { AuthService } from 'src/app/services/auth.service'; @Component({ selector: 'app-phone-method', templateUrl: './phone-method.component.html', styleUrls: ['./phone-method.component.scss'], }) export class PhoneMethodComponent implements OnInit { phoneForm: FormGroup; phoneNumber: string; constructor(private formBuilder: FormBuilder, private authService: AuthService, private router: Router, private loadingCtrl: LoadingController) { this.phoneForm = this.formBuilder.group({ phoneNum: ['', Validators.compose([Validators.required, Validators.pattern(/^\+\d{2}\s\(\d{3}\)\s\d{3}-\d{4}$/)]) ]}); this.phoneNumber=""; } async sendSMS() { this.phoneNumber=this.phoneForm.get('phoneNum')?.value; const loading = await this.loadingCtrl.create({ message: 'Enviando mensaje...', cssClass: 'custom-loading' }); this.authService.signInWithPhoneNumber(this.phoneNumber,loading).then(result=>{ loading.dismiss(); let encodedPhoneNumber = encodeURIComponent(this.phoneNumber); this.router.navigate(['/phone-otp',encodedPhoneNumber]); }); /*let phone=this.phoneForm.get('phoneNum')?.value; if (this.phoneForm.valid) { console.log(phone); }*/ } goBack() { // Navigate one step back in the browser's history this.router.navigate(['/']) } readonly phoneMask: MaskitoOptions = { mask: ['+','5','7',' ','(',/\d/,/\d/,/\d/,')',' ',/\d/,/\d/,/\d/,'-',/\d/,/\d/,/\d/,/\d/], }; readonly maskPredicate: MaskitoElementPredicateAsync = async (el) => (el as HTMLIonInputElement).getInputElement(); ngOnInit() {} }
HTML模板代码:
<ion-content class="ion-padding"> <a (click)="goBack()">< Atrás</a> <h3 class="tittle">Ingresa tu número celular</h3> <p class="description">Te enviaremos un código para confirmarlo</p> <form [formGroup]="phoneForm" (ngSubmit)="sendSMS()"> <ion-item> <ion-input class="margin-top" label="Número de celular" type="tel" class="custom" formControlName="phoneNum" labelPlacement="floating" placeholder="+57 (xxx) xxx-xxxx" [maskito]="phoneMask" [maskitoElement]="maskPredicate"> </ion-input> </ion-item> <div id="recaptcha-container"></div> <div class="ion-text-center margin-top"> <ion-button type="submit" *ngIf="phoneForm.valid" fill="outline">Enviar SMS</ion-button> </div> </form> </ion-content>
Auth服务代码:
import { Injectable } from '@angular/core'; import {Auth,signInWithEmailAndPassword,createUserWithEmailAndPassword,signInWithPhoneNumber,RecaptchaVerifier,signOut} from '@angular/fire/auth'; @Injectable({ providedIn: 'root' }) export class AuthService { appVerifier: any; confirmationResult: any; constructor(private _fireAuth: Auth) { _fireAuth.languageCode ='es'; _fireAuth.settings.appVerificationDisabledForTesting=true; } recaptcha(loading: HTMLIonLoadingElement) { this.appVerifier = new RecaptchaVerifier('recaptcha-container', { size: 'invisible', callback: (response: any) => { loading.present(); }, 'expired-callback': () => { } }, this._fireAuth); } async signInWithPhoneNumber(phoneNumber: string, loading: HTMLIonLoadingElement) { try { if(!this.appVerifier){ this.recaptcha(loading); } const confirmationResult = await signInWithPhoneNumber(this._fireAuth, phoneNumber, this.appVerifier); this.confirmationResult = confirmationResult; loading.dismiss(); return confirmationResult; } catch(e) { console.log(e); throw(e); } } async verifyOtp(otp: any, loading: HTMLIonLoadingElement) { try { if(!this.appVerifier){ this.recaptcha(loading); } const result = await this.confirmationResult.confirm(otp); const user = result?.user; loading.dismiss(); return user; } catch(e) { console.log(e); } } /* async register({ email, password }) { try { const user = await createUserWithEmailAndPassword(this.auth, email, password); return user; } catch (e) { return null; } } async login({ email, password }) { try { const user = await signInWithEmailAndPassword(this.auth, email, password); return user; } catch (e) { return null; } }*/ logout() { return signOut(this._fireAuth); } }
问题解答
1. 配额超限但控制台未显示的原因
Firebase免费版的手机认证有几个易被忽略的限制:
- 单手机号请求频率限制:免费版对单个测试手机号的请求频率有严格限制(比如短时间内多次发送验证码),这类限制不会在配额控制台的总用量中显示,只会触发
auth/quota-exceeded错误。 - Recaptcha验证请求配额:即使启用了
appVerificationDisabledForTesting,Recaptcha Verifier的请求仍会占用Google Cloud的Recaptcha配额,而这个配额不会在Firebase控制台单独展示。 - 统计延迟:Firebase配额用量统计存在延迟,刚触发限制时控制台可能还未更新数据。
2. 如何定位具体超限项
- 查看Cloud Logging日志:进入项目的Cloud Logging,筛选
auth/quota-exceeded相关日志,日志中会包含具体的配额名称(比如PHONE_AUTH_REQUESTS_PER_MINUTE_PER_NUMBER),明确哪项限制被触发。 - 检查测试流程:如果短时间内重复点击发送验证码按钮,大概率是单手机号的频率限制。你的代码未对发送按钮做禁用处理,容易导致重复请求触发限制。
- 确认测试号码状态:确保测试号码已正确添加到Firebase项目的测试号码列表中,未添加的号码会占用正式配额(免费版额度极低)。
3. 解决建议
- 添加按钮禁用逻辑:在
sendSMS方法中,发送请求前禁用按钮,请求完成后再启用,避免重复提交:async sendSMS() { const submitBtn = document.querySelector('ion-button[type="submit"]'); if(submitBtn) submitBtn.setAttribute('disabled', 'true'); this.phoneNumber=this.phoneForm.get('phoneNum')?.value; const loading = await this.loadingCtrl.create({ message: 'Enviando mensaje...', cssClass: 'custom-loading' }); try { await this.authService.signInWithPhoneNumber(this.phoneNumber,loading); loading.dismiss(); let encodedPhoneNumber = encodeURIComponent(this.phoneNumber); this.router.navigate(['/phone-otp',encodedPhoneNumber]); } catch(e) { loading.dismiss(); console.error(e); } finally { if(submitBtn) submitBtn.removeAttribute('disabled'); } } - 调整Recaptcha配置:既然启用了
appVerificationDisabledForTesting,可在测试环境中跳过Recaptcha验证,避免占用配额。 - 查看Cloud Console配额详情:进入Google Cloud Console的IAM与管理>配额页面,筛选
Firebase Authentication和reCAPTCHA相关配额,查看具体用量和限额。
内容的提问来源于stack exchange,提问作者AlejoDev
相关产品推荐
相关产品推荐

