You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

已有用户邮箱时,Firebase无密码登录如何跳过邮箱二次输入?安全风险与实现方案咨询

Solution: Skip Email Prompt in Firebase Passwordless Login (Without Plaintext URL)

Great question—you’re right to avoid both repeating the email prompt and exposing sensitive data in plaintext. Let’s break down two secure approaches to solve this, tailored to your existing Firebase + AngularJS setup.

Approach 1: Encrypt the Email and Pass It as a URL Parameter

This method hides the email in plain sight by encrypting it before adding it to the login URL, then decrypting it on the frontend.

Backend (Python) Modifications

First, add encryption logic (we’ll use cryptography’s Fernet, a secure symmetric encryption standard):

import firebase_admin
from firebase_admin import auth
from google.cloud import firestore
from cryptography.fernet import Fernet
import os

# Store this key in an environment variable (never hardcode it!)
# Generate a key once with Fernet.generate_key()
ENCRYPTION_KEY = os.environ.get("FIREBASE_EMAIL_ENCRYPTION_KEY").encode()
cipher_suite = Fernet(ENCRYPTION_KEY)

# ... [your existing create_new_auth and create_new_pre_user functions] ...

def main(dictionary):
    firebase_admin.initialize_app()
    db = firestore.Client()
    uid = create_new_auth(dictionary)
    create_new_pre_user(db, dictionary, uid)
    
    # Encrypt the user's email
    encrypted_email = cipher_suite.encrypt(dictionary['email'].encode()).decode()
    
    action_code_settings = auth.ActionCodeSettings(
        url=f'http://localhost:4200/login?e={encrypted_email}',
        handle_code_in_app=True,
        ios_bundle_id='com.example.ios',
        android_package_name='com.example.android',
        android_install_app=True,
        android_minimum_version='12',
        dynamic_link_domain='magic42.page.link',
    )
    link = auth.generate_sign_in_with_email_link(dictionary['email'], action_code_settings)
    print(f"Generated login link: {link}")

Frontend (AngularJS) Modifications

Install the fernet-js package (npm install fernet-js) and update your login logic to decrypt the email:

import { Component } from '@angular/core';
import { AuthService } from './auth.service';
import { Router } from '@angular/router';
import Fernet from 'fernet-js';

// Fetch this key from your environment variables (match the backend key!)
const ENCRYPTION_KEY = import.meta.env.FIREBASE_EMAIL_ENCRYPTION_KEY;
const cipherSuite = new Fernet(ENCRYPTION_KEY);

@Component({ /* ... */ })
export class LoginComponent {
  constructor(private authService: AuthService, private router: Router) {}

  private signInWithEmail() {
    if (this.authService.isSignInWithEmailLink(window.location.href)) {
      let email: string | null = null;
      const urlParams = new URLSearchParams(window.location.search);
      const encryptedEmail = urlParams.get('e');

      if (encryptedEmail) {
        try {
          // Decrypt the email
          email = cipherSuite.decrypt(encryptedEmail).toString();
        } catch (err) {
          console.error("Failed to decrypt email:", err);
        }
      }

      // Fallback to prompt if decryption fails or parameter is missing
      if (!email) {
        email = window.prompt('Please provide your email for confirmation');
      }

      if (email) {
        this.authService.signInWithEmailLink(email, window.location.href)
          .then((result) => {
            window.localStorage.removeItem('emailForSignIn');
            this.router.navigate(['/patient', 'quiz']);
          })
          .catch((error) => {
            console.error("Login failed:", error);
          });
      }
    }
  }
}

This leverages Firebase’s Dynamic Links and Firestore to retrieve the email without encrypting anything. Since UIDs are non-sensitive public identifiers in Firebase, this is a clean, Firebase-native approach.

Backend (Python) Modifications

Add the user’s UID as a parameter to your login URL:

def main(dictionary):
    firebase_admin.initialize_app()
    db = firestore.Client()
    uid = create_new_auth(dictionary)
    create_new_pre_user(db, dictionary, uid)
    
    action_code_settings = auth.ActionCodeSettings(
        url=f'http://localhost:4200/login?uid={uid}',
        handle_code_in_app=True,
        ios_bundle_id='com.example.ios',
        android_package_name='com.example.android',
        android_install_app=True,
        android_minimum_version='12',
        dynamic_link_domain='magic42.page.link',
    )
    link = auth.generate_sign_in_with_email_link(dictionary['email'], action_code_settings)
    print(f"Generated login link: {link}")

Frontend (AngularJS) Modifications

Use Firebase’s Dynamic Links SDK to parse the UID, then fetch the email from Firestore:

import { Component } from '@angular/core';
import { AuthService } from './auth.service';
import { Router } from '@angular/router';
import { AngularFirestore } from '@angular/fire/firestore';
import { getDynamicLinks, parseDynamicLink } from "@firebase/dynamic-links";
import { AngularFireAuth } from '@angular/fire/auth';

@Component({ /* ... */ })
export class LoginComponent {
  constructor(
    private authService: AuthService, 
    private router: Router,
    private afs: AngularFirestore,
    private afAuth: AngularFireAuth
  ) {}

  private async signInWithEmail() {
    if (this.authService.isSignInWithEmailLink(window.location.href)) {
      let email: string | null = null;

      try {
        // Parse the dynamic link to get the UID
        const dynamicLink = await parseDynamicLink(
          getDynamicLinks(this.afAuth.app), 
          window.location.href
        );
        const uid = dynamicLink.url.searchParams.get('uid');

        if (uid) {
          // Fetch the user's email from Firestore
          const userDoc = await this.afs.collection('users').doc(uid).get().toPromise();
          if (userDoc?.exists) {
            email = userDoc.data()['email'];
          }
        }
      } catch (err) {
        console.error("Failed to retrieve user data:", err);
      }

      // Fallback to prompt if fetch fails
      if (!email) {
        email = window.prompt('Please provide your email for confirmation');
      }

      if (email) {
        this.authService.signInWithEmailLink(email, window.location.href)
          .then((result) => {
            window.localStorage.removeItem('emailForSignIn');
            this.router.navigate(['/patient', 'quiz']);
          })
          .catch((error) => {
            console.error("Login failed:", error);
          });
      }
    }
  }
}

Security Considerations

  • Approach 1: Keep your encryption key secure (use environment variables or a secret manager like Google Cloud Secret Manager). Even if the encrypted email is intercepted, attackers can’t decrypt it without the key—and they still need the valid action code from the login link to authenticate.
  • Approach 2: UIDs are public, but only users with a valid login link can use the UID to fetch the email and complete authentication. Ensure your Firestore rules allow read access to user documents for this initial step (you can tighten rules once the user is logged in).

Both methods eliminate the need for users to re-enter their email while avoiding plaintext exposure. Approach 2 is generally preferred since it fits seamlessly into Firebase’s ecosystem.

内容的提问来源于stack exchange,提问作者cuzureau

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 04:02:36