Firebase ile Kullanıcı Doğrulama (OTP) Nasıl Yapılır?

Yunus Emre Güzel
22 Ocak 202512 dkFirebase
Firebase ile Kullanıcı Doğrulama (OTP) Nasıl Yapılır?

Firebase Authentication, kullanıcı doğrulama işlemlerini güvenli ve kolay bir şekilde gerçekleştirmenizi sağlar. Bu yazıda, SMS tabanlı OTP (One-Time Password) doğrulama sisteminin nasıl implemente edileceğini detaylıca inceleyeceğiz.

Firebase OTP Doğrulama Nedir?

Firebase OTP doğrulama, kullanıcının telefon numarasını doğrulamak için tek kullanımlık şifre gönderen bir sistemdir. Bu sistem:

  • Kullanıcı kimliğini güvenli bir şekilde doğrular
  • SMS üzerinden tek kullanımlık şifre gönderir
  • Otomatik SMS algılama özelliği sunar (Android ve iOS 17+)
  • Çoklu platform desteği sağlar (iOS, Android, Web)
  • Güvenlik ve dolandırıcılık koruması sunar
  • App Check entegrasyonu ile güvenliği artırır
  • Multi-factor authentication (MFA) desteği sunar

Firebase Projesini Hazırlama

1. Firebase Console Ayarları

Öncelikle Firebase Console'da gerekli ayarları yapmamız gerekiyor:

  1. Firebase Console'a gidin
  2. Yeni bir proje oluşturun (veya mevcut projenizi seçin)
  3. Authentication > Sign-in method bölümüne gidin
  4. "Phone" sağlayıcısını etkinleştirin
  5. App Check'i etkinleştirin (önerilen)
  6. Test telefon numaralarını ekleyin (geliştirme aşaması için)

2. Firebase SDK Kurulumu

# NPM ile kurulum
npm install firebase @firebase/auth

# Yarn ile kurulum
yarn add firebase @firebase/auth

3. Firebase Konfigürasyonu

// firebase/config.ts
import { initializeApp } from 'firebase/app';
import { getAuth, initializeAppCheck, ReCaptchaV3Provider } from 'firebase/auth';

const firebaseConfig = {
  apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
  authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
  projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
  storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID
};

// Firebase'i başlat
const app = initializeApp(firebaseConfig);

// App Check'i etkinleştir (önerilen)
if (process.env.NODE_ENV === 'production') {
  initializeAppCheck(app, {
    provider: new ReCaptchaV3Provider(process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY!),
    isTokenAutoRefreshEnabled: true
  });
}

// Auth nesnesini oluştur
export const auth = getAuth(app);

OTP Doğrulama Implementasyonu

1. Telefon Numarası Girişi ve Doğrulama Başlatma

// components/PhoneAuth.tsx
import { useState } from 'react';
import { RecaptchaVerifier, signInWithPhoneNumber } from 'firebase/auth';
import { auth } from '../firebase/config';

export function PhoneAuth() {
  const [phoneNumber, setPhoneNumber] = useState('');
  const [verificationId, setVerificationId] = useState('');
  const [error, setError] = useState<string | null>(null);

  // reCAPTCHA doğrulayıcısını oluştur
  const setupRecaptcha = () => {
    const recaptchaVerifier = new RecaptchaVerifier(auth, 'recaptcha-container', {
      size: 'normal',
      callback: () => {
        // reCAPTCHA başarılı
        startPhoneVerification();
      },
      'expired-callback': () => {
        // reCAPTCHA süresi doldu
        setError('reCAPTCHA süresi doldu. Lütfen tekrar deneyin.');
      }
    });

    return recaptchaVerifier;
  };

  // Telefon doğrulamasını başlat
  const startPhoneVerification = async () => {
    try {
      const recaptchaVerifier = setupRecaptcha();
      
      const confirmationResult = await signInWithPhoneNumber(
        auth,
        phoneNumber,
        recaptchaVerifier
      );
      
      setVerificationId(confirmationResult.verificationId);
      setError(null);
    } catch (error) {
      setError('Doğrulama kodu gönderilemedi. Lütfen tekrar deneyin.');
      console.error('Phone verification error:', error);
    }
  };

  return (
    <div className="max-w-md mx-auto p-6">
      <h2 className="text-2xl font-bold mb-4">
        Telefon Numarası Doğrulama
      </h2>
      
      <div className="space-y-4">
        <div>
          <label className="block text-sm font-medium mb-1">
            Telefon Numarası
          </label>
          <input
            type="tel"
            value={phoneNumber}
            onChange={(e) => setPhoneNumber(e.target.value)}
            placeholder="+90 5XX XXX XX XX"
            className="w-full px-4 py-2 border rounded-md"
          />
        </div>

        {/* reCAPTCHA container */}
        <div id="recaptcha-container"></div>

        <button
          onClick={startPhoneVerification}
          className="w-full px-4 py-2 bg-blue-600 text-white rounded-md"
        >
          Doğrulama Kodu Gönder
        </button>

        {error && (
          <p className="text-red-500 text-sm">{error}</p>
        )}
      </div>
    </div>
  );
}

2. OTP Kodu Doğrulama

// components/OTPVerification.tsx
import { useState } from 'react';
import { PhoneAuthProvider, signInWithCredential } from 'firebase/auth';
import { auth } from '../firebase/config';

interface OTPVerificationProps {
  verificationId: string;
  onSuccess: () => void;
}

export function OTPVerification({ verificationId, onSuccess }: OTPVerificationProps) {
  const [otp, setOtp] = useState('');
  const [error, setError] = useState<string | null>(null);
  const [isVerifying, setIsVerifying] = useState(false);

  const verifyOTP = async () => {
    if (otp.length !== 6) {
      setError('Lütfen 6 haneli doğrulama kodunu girin.');
      return;
    }

    setIsVerifying(true);
    setError(null);

    try {
      // Credential oluştur
      const credential = PhoneAuthProvider.credential(
        verificationId,
        otp
      );

      // Credential ile giriş yap
      await signInWithCredential(auth, credential);
      
      onSuccess();
    } catch (error) {
      setError('Geçersiz doğrulama kodu. Lütfen tekrar deneyin.');
      console.error('OTP verification error:', error);
    } finally {
      setIsVerifying(false);
    }
  };

  return (
    <div className="max-w-md mx-auto p-6">
      <h2 className="text-2xl font-bold mb-4">
        Doğrulama Kodu
      </h2>
      
      <div className="space-y-4">
        <div>
          <label className="block text-sm font-medium mb-1">
            6 Haneli Kod
          </label>
          <input
            type="text"
            maxLength={6}
            value={otp}
            onChange={(e) => setOtp(e.target.value.replace(/\D/g, ''))}
            placeholder="123456"
            className="w-full px-4 py-2 border rounded-md text-center text-2xl tracking-widest"
          />
        </div>

        <button
          onClick={verifyOTP}
          disabled={isVerifying}
          className="w-full px-4 py-2 bg-blue-600 text-white rounded-md disabled:opacity-50"
        >
          {isVerifying ? 'Doğrulanıyor...' : 'Doğrula'}
        </button>

        {error && (
          <p className="text-red-500 text-sm">{error}</p>
        )}
      </div>
    </div>
  );
}

3. Ana Komponent ve Durum Yönetimi

// pages/auth/phone.tsx
import { useState } from 'react';
import { PhoneAuth } from '../../components/PhoneAuth';
import { OTPVerification } from '../../components/OTPVerification';

export default function PhoneAuthPage() {
  const [verificationId, setVerificationId] = useState<string | null>(null);
  const [isAuthenticated, setIsAuthenticated] = useState(false);

  const handleVerificationSuccess = () => {
    setIsAuthenticated(true);
    // Kullanıcıyı yönlendir veya state'i güncelle
  };

  if (isAuthenticated) {
    return (
      <div className="text-center p-6">
        <h2 className="text-2xl font-bold text-green-600">
          Başarıyla doğrulandı!
        </h2>
        <p className="mt-2">
          Telefon numaranız başarıyla doğrulandı.
        </p>
      </div>
    );
  }

  return (
    <div>
      {!verificationId ? (
        <PhoneAuth
          onVerificationSent={(id) => setVerificationId(id)}
        />
      ) : (
        <OTPVerification
          verificationId={verificationId}
          onSuccess={handleVerificationSuccess}
        />
      )}
    </div>
  );
}

Güvenlik Önlemleri

1. App Check Entegrasyonu

// utils/appCheck.ts
import { initializeAppCheck, ReCaptchaV3Provider } from 'firebase/app-check';

export function setupAppCheck(app: FirebaseApp) {
  if (process.env.NODE_ENV === 'production') {
    initializeAppCheck(app, {
      provider: new ReCaptchaV3Provider(process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY!),
      isTokenAutoRefreshEnabled: true
    });
  }
}

2. Rate Limiting ve Güvenlik Kuralları

// Firebase Security Rules
{
  "rules": {
    "phoneVerification": {
      "$uid": {
        ".read": "$uid === auth.uid",
        ".write": "$uid === auth.uid",
        "attempts": {
          ".validate": "newData.val() <= 5 && 
                       newData.val() >= 0 && 
                       (!data.exists() || newData.val() > data.val()) &&
                       now - data.child('lastAttempt').val() > 300000" // 5 dakika
        }
      }
    }
  }
}

3. Multi-Factor Authentication (MFA)

// utils/mfa.ts
import { multiFactor, PhoneAuthProvider, PhoneMultiFactorGenerator } from 'firebase/auth';

export async function enrollPhoneMFA(auth: Auth, phoneNumber: string) {
  const user = auth.currentUser;
  if (!user) throw new Error('Kullanıcı oturum açmamış');

  const multiFactorSession = await multiFactor(user).getSession();
  
  const phoneAuthProvider = new PhoneAuthProvider(auth);
  const verificationId = await phoneAuthProvider.verifyPhoneNumber({
    phoneNumber,
    session: multiFactorSession
  });

  return verificationId;
}

export async function verifyPhoneMFA(auth: Auth, verificationId: string, verificationCode: string) {
  const user = auth.currentUser;
  if (!user) throw new Error('Kullanıcı oturum açmamış');

  const cred = PhoneAuthProvider.credential(verificationId, verificationCode);
  const multiFactorAssertion = PhoneMultiFactorGenerator.assertion(cred);
  
  await multiFactor(user).enroll(multiFactorAssertion, 'Phone Number');
}

En İyi Pratikler (2024)

  1. Güvenlik

    • App Check kullanın
    • MFA desteği ekleyin
    • Rate limiting uygulayın
    • IP tabanlı kısıtlamalar ekleyin
    • Şüpheli aktiviteleri izleyin
  2. Kullanıcı Deneyimi

    • Progressive Web App (PWA) desteği
    • Otomatik SMS algılama
    • Offline destek
    • Hata durumlarında retry mekanizması
    • Accessibility standartlarına uyum
  3. Performans

    • Lazy loading
    • Code splitting
    • Service worker optimizasyonu
    • Firebase SDK modüllerini ayrı ayrı import edin
  4. Test ve İzleme

    • Firebase Analytics entegrasyonu
    • Error tracking
    • Performance monitoring
    • A/B testing
    • User feedback collection

Sonuç

Firebase OTP doğrulama sistemi, güvenli ve kullanıcı dostu bir kimlik doğrulama çözümü sunar. Bu yazıda öğrendiklerimizi özetleyelim:

  • Firebase Authentication kurulumu ve konfigürasyonu
  • Telefon numarası doğrulama akışı
  • OTP kodu doğrulama implementasyonu
  • Güvenlik önlemleri ve en iyi pratikler

Bu sistemi kendi projenize entegre ederek, güvenli bir telefon doğrulama sistemi oluşturabilirsiniz.

Kaynaklar