2025-09-04
React Native'de Auth0 ve Biyometrik Kimlik Doğrulama ile Gerçek Dünya Session Yönetimi
Production React Native uygulamalarında Auth0, biyometrik kimlik doğrulama ve düzgün token yaşam döngüsü yönetimi ile güvenli oturum yönetimi uygulamak için adım adım kılavuz
Özet
Mobil uygulamalarda oturum yönetimi, web geliştiricilerinin nadiren karşılaştığı benzersiz zorluklar sunar. Mobil uygulamalar, kusursuz bir kullanıcı deneyimi sağlarken arka plan durumlarını, ağ kesintilerini, biyometrik kimlik doğrulamayı ve platforma özgü güvenlik kısıtlamalarını yönetmelidir. Bu kılavuz, Auth0 ve biyometrik kimlik doğrulama kullanarak React Native uygulamalarında güçlü oturum yönetimi uygulamak için etkili bir yaklaşım sunar.
Bu eğitimde, token yaşam döngüsünü yöneten, biyometrik kimlik doğrulamayı uygulayan, arka plan işlemlerini yöneten ve edge case’lerden zarif bir şekilde kurtaran eksiksiz bir oturum yönetim sistemi oluşturacaksınız. Uygulama, gerçek dünya senaryolarına odaklanır ve startup MVP’lerinden kurumsal uygulamalara kadar ölçeklenen, etkili kalıplar sağlar.
Ön Gereksinimler
Bu uygulamaya başlamadan önce, aşağıdakilere sahip olduğunuzdan emin olun:
- React Native geliştirme ortamı yapılandırılmış (React Native 0.73+)
- Yapılandırılmış uygulamaya sahip Auth0 hesabı
- iOS ve Android geliştirme ortamları kurulu
- OAuth 2.0 ve JWT token’larının temel anlayışı
- React Native navigasyon ve state yönetimine aşinalık
Gerekli paketler:
{
"react-native": "^0.73.0",
"react-native-auth0": "^3.2.0",
"react-native-biometrics": "^3.0.1",
"react-native-keychain": "^8.2.0",
"@react-native-async-storage/async-storage": "^1.21.0",
"@react-native-community/netinfo": "^11.2.0",
"react-native-background-fetch": "^4.2.0"
}
Adım 1: Session Yönetimi Mimarisini Anlamak
Kod uygulamasına başlamadan önce, uygulamamıza rehberlik edecek temel kavramları ve mimariyi oluşturalım.
Session Durumları ve Yaşam Döngüsü
Mobil uygulamalar, çeşitli kimlik doğrulama senaryolarını yönetmek için karmaşık durum yönetimi gerektirir:
// types/AuthState.ts
export enum UserState {
UNAUTHENTICATED = 'unauthenticated',
AUTHENTICATING = 'authenticating',
AUTHENTICATED = 'authenticated',
SESSION_EXPIRED = 'session_expired',
REQUIRES_VERIFICATION = 'requires_verification',
CHECKING_AUTH = 'checking_auth',
LOGGING_OUT = 'logging_out'
}
export interface AuthState {
userState: UserState;
user: User | null;
tokens: {
accessToken: string | null;
refreshToken: string | null;
idToken: string | null;
expiresAt: number | null;
};
lastActivity: number;
biometricEnabled: boolean;
sessionStartTime: number;
}
export interface User {
id: string;
email: string;
name: string;
picture?: string;
emailVerified: boolean;
}
Token Mimarisi
Üç token sistemini anlamak kritiktir:
- Access Token (15 dakika): API yetkilendirmesi için kullanılır
- Refresh Token (30 gün): Yeni access token’ları almak için kullanılır
- ID Token (1 saat): Kullanıcı profil bilgilerini içerir
Adım 2: Auth0 Entegrasyonunu Kurmak
Tüm kimlik doğrulama işlemlerini yöneten güçlü bir Auth0 servisi oluşturun:
// services/auth/AuthService.ts
import Auth0 from 'react-native-auth0';
import Config from 'react-native-config';
class AuthService {
private auth0: Auth0;
private readonly AUTH0_SCOPE = 'openid profile email offline_access';
constructor() {
this.auth0 = new Auth0({
domain: Config.AUTH0_DOMAIN,
clientId: Config.AUTH0_CLIENT_ID,
});
}
async login(): Promise<Credentials> {
try {
const credentials = await this.auth0.webAuth.authorize({
scope: this.AUTH0_SCOPE,
audience: Config.AUTH0_AUDIENCE,
prompt: 'login',
// Gelişmiş güvenlik için refresh token rotasyonunu etkinleştirin
parameters: {
device: 'mobile',
rotation: 'rotating'
}
});
return this.processCredentials(credentials);
} catch (error) {
if (error.error === 'a0.session.user_cancelled') {
throw new Error('Kullanıcı girişi iptal etti');
}
throw error;
}
}
async refreshTokens(refreshToken: string): Promise<Credentials> {
try {
// Auth0 SDK v3 rotasyon desteğiyle yerleşik token yenilemeye sahip
const credentials = await this.auth0.auth.refreshToken({
refreshToken,
scope: this.AUTH0_SCOPE
});
return this.processCredentials(credentials);
} catch (error) {
if (error.error === 'invalid_grant') {
throw new Error('Refresh token süresi dolmuş veya iptal edilmiş');
}
throw error;
}
}
async logout(): Promise<void> {
// Auth0 oturumunu ve çerezleri temizle
await this.auth0.webAuth.clearSession();
}
async getUserInfo(accessToken: string): Promise<User> {
const userInfo = await this.auth0.auth.userInfo({ token: accessToken });
return {
id: userInfo.sub,
email: userInfo.email,
name: userInfo.name || userInfo.nickname,
picture: userInfo.picture,
emailVerified: userInfo.email_verified
};
}
private processCredentials(credentials: any): Credentials {
return {
accessToken: credentials.accessToken,
refreshToken: credentials.refreshToken,
idToken: credentials.idToken,
expiresIn: credentials.expiresIn,
expiresAt: Date.now() + (credentials.expiresIn * 1000),
tokenType: credentials.tokenType,
scope: credentials.scope
};
}
}
export default new AuthService();
Adım 3: Güvenli Token Depolamayı Uygulamak
Token’ları platforma özgü güvenli depolama kullanarak güvenli bir şekilde saklayın:
// services/storage/SecureStorage.ts
import * as Keychain from 'react-native-keychain';
import AsyncStorage from '@react-native-async-storage/async-storage';
import CryptoJS from 'crypto-js';
class SecureStorage {
private readonly KEYCHAIN_SERVICE = 'com.yourapp.oauth';
private readonly KEYCHAIN_ACCESS_GROUP = 'group.com.yourapp';
async storeTokens(tokens: TokenSet): Promise<void> {
try {
// Hassas token'ları Keychain'de sakla
await Keychain.setInternetCredentials(
this.KEYCHAIN_SERVICE,
'tokens',
JSON.stringify({
accessToken: tokens.accessToken,
refreshToken: tokens.refreshToken,
expiresAt: tokens.expiresAt
}),
{
accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
accessGroup: this.KEYCHAIN_ACCESS_GROUP,
authenticatePrompt: 'Hesabınıza erişmek için kimlik doğrulayın',
authenticationPromptType: Keychain.AUTHENTICATION_TYPE.BIOMETRICS
}
);
// Hassas olmayan verileri AsyncStorage'da sakla
await AsyncStorage.setItem('@auth_metadata', JSON.stringify({
expiresAt: tokens.expiresAt,
scope: tokens.scope,
tokenType: tokens.tokenType
}));
} catch (error) {
console.error('Token'ları güvenli bir şekilde saklanamadı:', error);
throw new Error('Güvenli depolama başarısız');
}
}
async getTokens(): Promise<TokenSet | null> {
try {
const credentials = await Keychain.getInternetCredentials(
this.KEYCHAIN_SERVICE
);
if (!credentials) {
return null;
}
const tokens = JSON.parse(credentials.password);
const metadata = await AsyncStorage.getItem('@auth_metadata');
return {
...tokens,
...(metadata ? JSON.parse(metadata) : {})
};
} catch (error) {
console.error('Token'lar alınamadı:', error);
return null;
}
}
async clearTokens(): Promise<void> {
await Keychain.resetInternetCredentials(this.KEYCHAIN_SERVICE);
await AsyncStorage.removeItem('@auth_metadata');
}
async hasStoredCredentials(): Promise<boolean> {
try {
const credentials = await Keychain.hasInternetCredentials(
this.KEYCHAIN_SERVICE
);
return credentials;
} catch {
return false;
}
}
}
export default new SecureStorage();
Adım 4: Biyometrik Kimlik Doğrulamayı Uygulamak
Biyometrik kimlik doğrulama için modern react-native-biometrics kütüphanesini kullanın:
// services/auth/BiometricService.ts
import ReactNativeBiometrics, { BiometryTypes } from 'react-native-biometrics';
import SecureStorage from '../storage/SecureStorage';
class BiometricService {
private rnBiometrics: ReactNativeBiometrics;
constructor() {
this.rnBiometrics = new ReactNativeBiometrics({
allowDeviceCredentials: true
});
}
async isBiometricAvailable(): Promise<{
available: boolean;
biometryType: BiometryTypes | null;
}> {
try {
const { available, biometryType } = await this.rnBiometrics.isSensorAvailable();
return { available, biometryType };
} catch (error) {
console.error('Biyometrik kontrolü başarısız:', error);
return { available: false, biometryType: null };
}
}
async authenticateWithBiometrics(): Promise<AuthenticationResult> {
try {
const { available, biometryType } = await this.isBiometricAvailable();
if (!available) {
return {
success: false,
error: 'Biyometrik kimlik doğrulama mevcut değil'
};
}
// Kimlik doğrulama için imza oluştur
const { success, signature } = await this.rnBiometrics.createSignature({
promptMessage: 'Hesabınıza erişmek için kimlik doğrulayın',
payload: Date.now().toString(),
cancelButtonText: 'İptal'
});
if (!success) {
return {
success: false,
error: 'Biyometrik kimlik doğrulama başarısız'
};
}
// Başarılı kimlik doğrulamadan sonra saklanan token'ları al
const tokens = await SecureStorage.getTokens();
if (!tokens) {
return {
success: false,
error: 'Saklanan kimlik bilgileri bulunamadı'
};
}
// Token'ların yenilenmesi gerekip gerekmediğini kontrol et
if (this.shouldRefreshTokens(tokens)) {
const refreshedTokens = await AuthService.refreshTokens(tokens.refreshToken);
await SecureStorage.storeTokens(refreshedTokens);
return { success: true, tokens: refreshedTokens };
}
return { success: true, tokens };
} catch (error) {
console.error('Biyometrik kimlik doğrulama hatası:', error);
return {
success: false,
error: error.message || 'Bilinmeyen hata oluştu'
};
}
}
async enrollBiometrics(): Promise<boolean> {
try {
const { available } = await this.isBiometricAvailable();
if (!available) {
return false;
}
// Biyometrik kayıt için anahtar çifti oluştur
const { publicKey } = await this.rnBiometrics.createKeys();
// Gelecekteki doğrulama için genel anahtarı sakla
await AsyncStorage.setItem('@biometric_public_key', publicKey);
return true;
} catch (error) {
console.error('Biyometrik kayıt başarısız:', error);
return false;
}
}
async deleteBiometricKeys(): Promise<void> {
await this.rnBiometrics.deleteKeys();
await AsyncStorage.removeItem('@biometric_public_key');
}
private shouldRefreshTokens(tokens: TokenSet): boolean {
const REFRESH_THRESHOLD = 5 * 60 * 1000; // 5 dakika
const timeUntilExpiry = tokens.expiresAt - Date.now();
return timeUntilExpiry < REFRESH_THRESHOLD;
}
}
export default new BiometricService();
Adım 5: Token Yöneticisini Oluşturmak
Yeniden deneme mantığı ve ağ farkındalığı ile otomatik token yenilemeyi uygulayın:
// services/auth/TokenManager.ts
import NetInfo from '@react-native-community/netinfo';
import BackgroundFetch from 'react-native-background-fetch';
import AuthService from './AuthService';
import SecureStorage from '../storage/SecureStorage';
class TokenManager {
private refreshTimer: NodeJS.Timeout | null = null;
private refreshPromise: Promise<void> | null = null;
private readonly REFRESH_THRESHOLD = 5 * 60 * 1000; // 5 dakika
private readonly MAX_RETRY_ATTEMPTS = 3;
private readonly RETRY_DELAY_BASE = 1000; // 1 saniye
async initialize(): Promise<void> {
// Otomatik token yenilemeyi ayarla
await this.scheduleTokenRefresh();
// Token yenileme için arka plan fetch'i yapılandır
await this.configureBackgroundRefresh();
// Ağ değişikliklerini dinle
NetInfo.addEventListener(this.handleNetworkChange);
}
async scheduleTokenRefresh(): Promise<void> {
// Mevcut zamanlayıcıyı temizle
if (this.refreshTimer) {
clearTimeout(this.refreshTimer);
this.refreshTimer = null;
}
const tokens = await SecureStorage.getTokens();
if (!tokens || !tokens.expiresAt) {
return;
}
const timeUntilExpiry = tokens.expiresAt - Date.now();
const refreshTime = Math.max(0, timeUntilExpiry - this.REFRESH_THRESHOLD);
if (refreshTime > 0) {
this.refreshTimer = setTimeout(() => {
this.performTokenRefresh();
}, refreshTime);
} else {
// Token'ın hemen yenilenmesi gerekiyor
await this.performTokenRefresh();
}
}
async performTokenRefresh(): Promise<void> {
// Eş zamanlı yenileme girişimlerini önle
if (this.refreshPromise) {
return this.refreshPromise;
}
this.refreshPromise = this.doRefreshWithRetry();
try {
await this.refreshPromise;
} finally {
this.refreshPromise = null;
}
}
private async doRefreshWithRetry(): Promise<void> {
let lastError: Error | null = null;
for (let attempt = 0; attempt < this.MAX_RETRY_ATTEMPTS; attempt++) {
try {
// Ağ bağlantısını kontrol et
const netInfo = await NetInfo.fetch();
if (!netInfo.isConnected) {
throw new Error('Ağ bağlantısı yok');
}
// Mevcut token'ları al
const tokens = await SecureStorage.getTokens();
if (!tokens || !tokens.refreshToken) {
throw new Error('Refresh token mevcut değil');
}
// Yenilemeyi gerçekleştir
const newTokens = await AuthService.refreshTokens(tokens.refreshToken);
// Yeni token'ları sakla
await SecureStorage.storeTokens(newTokens);
// Sonraki yenilemeyi planla
await this.scheduleTokenRefresh();
// Başarı olayını yayınla
this.emitTokenRefreshSuccess();
return;
} catch (error) {
lastError = error;
if (error.message === 'Refresh token süresi dolmuş veya iptal edilmiş') {
// Bundan kurtulunamaz - kullanıcının tekrar giriş yapması gerekiyor
this.handleSessionExpired();
return;
}
if (attempt < this.MAX_RETRY_ATTEMPTS - 1) {
// Üstel geri çekilme gecikmesini hesapla
const delay = this.RETRY_DELAY_BASE * Math.pow(2, attempt);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
// Tüm yeniden denemeler başarısız oldu
console.error('Token yenileme tüm denemelerden sonra başarısız:', lastError);
this.handleSessionExpired();
}
private async configureBackgroundRefresh(): Promise<void> {
await BackgroundFetch.configure({
minimumFetchInterval: 15, // 15 dakika
forceAlarmManager: false,
stopOnTerminate: false,
enableHeadless: true,
startOnBoot: true,
requiredNetworkType: BackgroundFetch.NETWORK_TYPE_ANY
}, async (taskId) => {
try {
await this.performTokenRefresh();
} catch (error) {
console.error('Arka plan token yenilemesi başarısız:', error);
} finally {
BackgroundFetch.finish(taskId);
}
}, (taskId) => {
console.error('Arka plan fetch zaman aşımı');
BackgroundFetch.finish(taskId);
});
}
private handleNetworkChange = async (state: any) => {
if (state.isConnected && this.refreshPromise === null) {
// Ağ yeniden bağlandı, token'ların yenilenmesi gerekip gerekmediğini kontrol et
const tokens = await SecureStorage.getTokens();
if (tokens && this.shouldRefreshTokens(tokens)) {
await this.performTokenRefresh();
}
}
};
private shouldRefreshTokens(tokens: TokenSet): boolean {
const timeUntilExpiry = tokens.expiresAt - Date.now();
return timeUntilExpiry < this.REFRESH_THRESHOLD;
}
private handleSessionExpired(): void {
// Token'ları temizle
SecureStorage.clearTokens();
// Oturum sona erdi olayını yayınla
this.emitSessionExpired();
}
private emitTokenRefreshSuccess(): void {
// Uygulamanın işlemesi için olay yayınla
EventEmitter.emit('auth:token-refreshed');
}
private emitSessionExpired(): void {
// Uygulamanın işlemesi için olay yayınla
EventEmitter.emit('auth:session-expired');
}
cleanup(): void {
if (this.refreshTimer) {
clearTimeout(this.refreshTimer);
this.refreshTimer = null;
}
BackgroundFetch.stop();
}
}
export default new TokenManager();
Adım 6: Uygulama Durum Geçişlerini Yönetmek
Uygulama durum değişikliklerini yönetin ve boşta kalma zaman aşımını uygulayın:
// hooks/useAppStateAuth.ts
import { useEffect, useRef } from 'react';
import { AppState, AppStateStatus } from 'react-native';
import BiometricService from '../services/auth/BiometricService';
import TokenManager from '../services/auth/TokenManager';
interface AppStateAuthConfig {
idleTimeout?: number; // milisaniye
requireBiometricOnForeground?: boolean;
onSessionExpired?: () => void;
onAuthRequired?: () => void;
}
export const useAppStateAuth = (config: AppStateAuthConfig = {}) => {
const {
idleTimeout = 30 * 60 * 1000, // varsayılan 30 dakika
requireBiometricOnForeground = true,
onSessionExpired,
onAuthRequired
} = config;
const appState = useRef(AppState.currentState);
const backgroundTime = useRef<number>(0);
useEffect(() => {
const handleAppStateChange = async (nextAppState: AppStateStatus) => {
// Uygulama ön plana geliyor
if (
appState.current.match(/inactive|background/) &&
nextAppState === 'active'
) {
const timeInBackground = Date.now() - backgroundTime.current;
if (timeInBackground > idleTimeout) {
// Oturum zaman aşımına uğradı
onSessionExpired?.();
} else if (requireBiometricOnForeground && timeInBackground > 60000) {
// Arka planda 1 dakikadan fazla kaldıysa biyometrik iste
const result = await BiometricService.authenticateWithBiometrics();
if (!result.success) {
onAuthRequired?.();
} else {
// Gerekirse token'ları yenile
await TokenManager.scheduleTokenRefresh();
}
} else {
// Sadece token'ların taze olduğundan emin ol
await TokenManager.scheduleTokenRefresh();
}
}
// Uygulama arka plana gidiyor
else if (
appState.current === 'active' &&
nextAppState.match(/inactive|background/)
) {
backgroundTime.current = Date.now();
}
appState.current = nextAppState;
};
const subscription = AppState.addEventListener('change', handleAppStateChange);
return () => {
subscription.remove();
};
}, [idleTimeout, requireBiometricOnForeground, onSessionExpired, onAuthRequired]);
};
Adım 7: Auth Context’i Uygulamak
Tüm kimlik doğrulama durumunu yöneten kapsamlı bir auth context oluşturun:
// contexts/AuthContext.tsx
import React, { createContext, useContext, useEffect, useReducer } from 'react';
import AuthService from '../services/auth/AuthService';
import BiometricService from '../services/auth/BiometricService';
import SecureStorage from '../services/storage/SecureStorage';
import TokenManager from '../services/auth/TokenManager';
import { useAppStateAuth } from '../hooks/useAppStateAuth';
interface AuthContextValue {
state: AuthState;
login: () => Promise<void>;
logout: () => Promise<void>;
authenticateWithBiometrics: () => Promise<boolean>;
enableBiometrics: () => Promise<boolean>;
checkAuthStatus: () => Promise<void>;
}
const AuthContext = createContext<AuthContextValue | null>(null);
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth AuthProvider içinde kullanılmalıdır');
}
return context;
};
const authReducer = (state: AuthState, action: any): AuthState => {
switch (action.type) {
case 'SET_USER_STATE':
return { ...state, userState: action.payload };
case 'SET_AUTH_DATA':
return {
...state,
userState: UserState.AUTHENTICATED,
user: action.payload.user,
tokens: action.payload.tokens,
sessionStartTime: Date.now(),
lastActivity: Date.now()
};
case 'CLEAR_AUTH':
return {
...state,
userState: UserState.UNAUTHENTICATED,
user: null,
tokens: {
accessToken: null,
refreshToken: null,
idToken: null,
expiresAt: null
},
sessionStartTime: 0,
lastActivity: 0
};
case 'SET_BIOMETRIC_ENABLED':
return { ...state, biometricEnabled: action.payload };
default:
return state;
}
};
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [state, dispatch] = useReducer(authReducer, {
userState: UserState.CHECKING_AUTH,
user: null,
tokens: {
accessToken: null,
refreshToken: null,
idToken: null,
expiresAt: null
},
lastActivity: 0,
biometricEnabled: false,
sessionStartTime: 0
});
// Uygulama durum değişikliklerini yönet
useAppStateAuth({
idleTimeout: 30 * 60 * 1000,
requireBiometricOnForeground: state.biometricEnabled,
onSessionExpired: () => {
dispatch({ type: 'CLEAR_AUTH' });
},
onAuthRequired: () => {
dispatch({ type: 'SET_USER_STATE', payload: UserState.REQUIRES_VERIFICATION });
}
});
// İlk auth durumunu kontrol et
useEffect(() => {
checkAuthStatus();
// Oturum sona erdi olaylarını dinle
const handleSessionExpired = () => {
dispatch({ type: 'CLEAR_AUTH' });
};
EventEmitter.addListener('auth:session-expired', handleSessionExpired);
return () => {
EventEmitter.removeListener('auth:session-expired', handleSessionExpired);
};
}, []);
const checkAuthStatus = async () => {
try {
dispatch({ type: 'SET_USER_STATE', payload: UserState.CHECKING_AUTH });
const tokens = await SecureStorage.getTokens();
if (!tokens || !tokens.accessToken) {
dispatch({ type: 'CLEAR_AUTH' });
return;
}
// Token'ların süresi dolmuş mu kontrol et
if (tokens.expiresAt && tokens.expiresAt < Date.now()) {
// Yenilemeyi dene
try {
await TokenManager.performTokenRefresh();
const newTokens = await SecureStorage.getTokens();
if (newTokens) {
const user = await AuthService.getUserInfo(newTokens.accessToken);
dispatch({
type: 'SET_AUTH_DATA',
payload: { user, tokens: newTokens }
});
// Token yöneticisini başlat
await TokenManager.initialize();
}
} catch {
dispatch({ type: 'CLEAR_AUTH' });
}
} else {
// Token'lar hala geçerli
const user = await AuthService.getUserInfo(tokens.accessToken);
dispatch({
type: 'SET_AUTH_DATA',
payload: { user, tokens }
});
// Token yöneticisini başlat
await TokenManager.initialize();
}
// Biyometrik kaydı kontrol et
const { available } = await BiometricService.isBiometricAvailable();
if (available) {
const enrolled = await AsyncStorage.getItem('@biometric_enrolled');
dispatch({ type: 'SET_BIOMETRIC_ENABLED', payload: enrolled === 'true' });
}
} catch (error) {
console.error('Auth durumu kontrolü başarısız:', error);
dispatch({ type: 'CLEAR_AUTH' });
}
};
const login = async () => {
try {
dispatch({ type: 'SET_USER_STATE', payload: UserState.AUTHENTICATING });
const credentials = await AuthService.login();
await SecureStorage.storeTokens(credentials);
const user = await AuthService.getUserInfo(credentials.accessToken);
dispatch({
type: 'SET_AUTH_DATA',
payload: { user, tokens: credentials }
});
// Token yöneticisini başlat
await TokenManager.initialize();
// Biyometrik kaydı öner
const { available } = await BiometricService.isBiometricAvailable();
if (available && !state.biometricEnabled) {
// Kullanıcıya biyometrikleri etkinleştirmesini iste
// Bu genellikle bir modal gösterir veya ayarlara yönlendirir
}
} catch (error) {
dispatch({ type: 'CLEAR_AUTH' });
throw error;
}
};
const logout = async () => {
try {
dispatch({ type: 'SET_USER_STATE', payload: UserState.LOGGING_OUT });
// Token yöneticisini temizle
TokenManager.cleanup();
// Auth0 oturumunu temizle
await AuthService.logout();
// Saklanan token'ları temizle
await SecureStorage.clearTokens();
// Etkinse biyometrik anahtarları temizle
if (state.biometricEnabled) {
await BiometricService.deleteBiometricKeys();
}
dispatch({ type: 'CLEAR_AUTH' });
} catch (error) {
console.error('Çıkış hatası:', error);
// Bir şey başarısız olsa bile çıkışı zorla
dispatch({ type: 'CLEAR_AUTH' });
}
};
const authenticateWithBiometrics = async (): Promise<boolean> => {
const result = await BiometricService.authenticateWithBiometrics();
if (result.success && result.tokens) {
const user = await AuthService.getUserInfo(result.tokens.accessToken);
dispatch({
type: 'SET_AUTH_DATA',
payload: { user, tokens: result.tokens }
});
return true;
}
return false;
};
const enableBiometrics = async (): Promise<boolean> => {
const enrolled = await BiometricService.enrollBiometrics();
if (enrolled) {
await AsyncStorage.setItem('@biometric_enrolled', 'true');
dispatch({ type: 'SET_BIOMETRIC_ENABLED', payload: true });
return true;
}
return false;
};
const value = {
state,
login,
logout,
authenticateWithBiometrics,
enableBiometrics,
checkAuthStatus
};
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
};
Adım 8: Korumalı Route’lar Oluşturmak
Kimlik doğrulama durumuna saygı duyan navigasyon korumalarını uygulayın:
// navigation/AuthNavigator.tsx
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { useAuth } from '../contexts/AuthContext';
import { UserState } from '../types/AuthState';
// Ekranları içe aktar
import LoginScreen from '../screens/LoginScreen';
import BiometricPromptScreen from '../screens/BiometricPromptScreen';
import HomeScreen from '../screens/HomeScreen';
import LoadingScreen from '../screens/LoadingScreen';
const Stack = createNativeStackNavigator();
export const AuthNavigator: React.FC = () => {
const { state } = useAuth();
if (state.userState === UserState.CHECKING_AUTH) {
return <LoadingScreen />;
}
return (
<NavigationContainer>
<Stack.Navigator screenOptions={{ headerShown: false }}>
{state.userState === UserState.AUTHENTICATED ? (
<Stack.Group>
<Stack.Screen name="Home" component={HomeScreen} />
{/* Diğer kimlik doğrulamalı ekranları ekle */}
</Stack.Group>
) : state.userState === UserState.REQUIRES_VERIFICATION ? (
<Stack.Screen name="BiometricPrompt" component={BiometricPromptScreen} />
) : (
<Stack.Screen name="Login" component={LoginScreen} />
)}
</Stack.Navigator>
</NavigationContainer>
);
};
Yaygın Sorunları Giderme
Token Yenileme Hataları
Sorun: Token yenilemesi “invalid_grant” hatasıyla başarısız oluyor.
Çözüm: Bu genellikle refresh token’ın iptal edildiğinde veya süresi dolduğunda meydana gelir. Uygun hata işlemeyi uygulayın:
// TokenManager'da
if (error.error === 'invalid_grant') {
// Yerel token'ları temizle
await SecureStorage.clearTokens();
// Yeniden kimlik doğrulamayı zorla
EventEmitter.emit('auth:session-expired');
}
Biyometrik Kimlik Doğrulama Döngüsü
Sorun: Uygulama sürekli biyometrik kimlik doğrulama istiyor.
Çözüm: Bir geri çekilme mekanizması uygulayın:
class BiometricService {
private failureCount = 0;
private readonly MAX_FAILURES = 3;
async authenticateWithBiometrics(): Promise<AuthenticationResult> {
if (this.failureCount >= this.MAX_FAILURES) {
// Parola kimlik doğrulamasına geri dön
return { success: false, error: 'Maksimum deneme aşıldı' };
}
const result = await this.performBiometricAuth();
if (!result.success) {
this.failureCount++;
} else {
this.failureCount = 0;
}
return result;
}
}
iOS’ta Arka Plan Yenilemesi
Sorun: iOS’ta arka plan yenilemesi güvenilir şekilde çalışmıyor.
Çözüm: iOS’un katı sınırlamaları vardır. Kritik güncellemeler için sessiz push bildirimlerini kullanın:
// AppDelegate.m
- (void)application:(UIApplication *)application
didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
// Token yenilemesini tetikle
[RNBackgroundFetch performFetchWithCompletionHandler:completionHandler];
}
Ağ Durumu İşleme
Sorun: Ağ kararsız olduğunda token yenilemesi başarısız oluyor.
Çözüm: Ağ izleme ile kuyruk tabanlı yeniden deneme uygulayın:
class NetworkAwareTokenManager {
private refreshQueue: Array<() => Promise<void>> = [];
private isOnline = true;
constructor() {
NetInfo.addEventListener(state => {
const wasOffline = !this.isOnline;
this.isOnline = state.isConnected;
if (wasOffline && this.isOnline) {
this.processQueue();
}
});
}
async queueRefresh(): Promise<void> {
return new Promise((resolve, reject) => {
const task = async () => {
try {
await this.performTokenRefresh();
resolve();
} catch (error) {
reject(error);
}
};
if (this.isOnline) {
task();
} else {
this.refreshQueue.push(task);
}
});
}
private async processQueue(): Promise<void> {
while (this.refreshQueue.length > 0) {
const task = this.refreshQueue.shift();
if (task) await task();
}
}
}
Güvenlik En İyi Uygulamaları
1. Token Depolama Güvenliği
Her zaman platforma özgü güvenli depolama kullanın:
- iOS:
kSecAccessControlBiometryCurrentSetile Keychain - Android: Kullanıcı kimlik doğrulaması gerektiren Android Keystore
2. Sertifika Sabitleme
Auth0 endpoint’leri için sertifika sabitleme uygulayın:
// ios/YourApp/Info.plist
<key>NSAppTransportSecurity</key>
<dict>
<key>NSPinnedDomains</key>
<dict>
<key>your-tenant.auth0.com</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSPinnedCAIdentities</key>
<array>
<dict>
<key>SPKI-SHA256-BASE64</key>
<string>YOUR_PIN_HERE</string>
</dict>
</array>
</dict>
</dict>
</dict>
3. Jailbreak/Root Tespiti
Güvenliği ihlal edilmiş cihazları tespit edin ve işlevselliği sınırlayın:
import JailMonkey from 'jail-monkey';
const checkDeviceSecurity = (): SecurityStatus => {
if (JailMonkey.isJailBroken()) {
return { secure: false, reason: 'Cihaz jailbreak/root yapılmış' };
}
if (JailMonkey.isDebuggedMode()) {
return { secure: false, reason: 'Debugger tespit edildi' };
}
return { secure: true };
};
4. Token Rotasyonu
Auth0’da refresh token rotasyonunu etkinleştirin:
// Auth0 Dashboard > Applications > Settings > Refresh Token Rotation
{
"rotation": {
"enabled": true,
"leeway": 60
}
}
5. Güvenli İletişim
Tüm token işlemleri için şifreli kanallar kullanın:
class SecureAPIClient {
private async makeSecureRequest(url: string, options: RequestInit): Promise<Response> {
const tokens = await SecureStorage.getTokens();
if (!tokens || !tokens.accessToken) {
throw new Error('Geçerli access token yok');
}
const response = await fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${tokens.accessToken}`,
'X-Request-ID': generateRequestId(),
'X-App-Version': getAppVersion()
}
});
if (response.status === 401) {
// Token süresi dolmuş olabilir, yenilemeyi dene
await TokenManager.performTokenRefresh();
// İsteği yeni token ile tekrar dene
const newTokens = await SecureStorage.getTokens();
return fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${newTokens.accessToken}`
}
});
}
return response;
}
}
Performans Optimizasyon İpuçları
1. Token Önbellekleme Stratejisi
TTL ile bellek önbellekleme uygulayın:
class TokenCache {
private cache: Map<string, { value: any; expires: number }> = new Map();
set(key: string, value: any, ttl: number): void {
this.cache.set(key, {
value,
expires: Date.now() + ttl
});
}
get(key: string): any | null {
const item = this.cache.get(key);
if (!item) return null;
if (Date.now() > item.expires) {
this.cache.delete(key);
return null;
}
return item.value;
}
}
2. Toplu Token İşlemleri
İşlemleri toplu hale getirerek Keychain erişimini azaltın:
class BatchedSecureStorage {
private pendingWrites: Map<string, any> = new Map();
private writeTimer: NodeJS.Timeout | null = null;
async set(key: string, value: any): Promise<void> {
this.pendingWrites.set(key, value);
this.scheduleWrite();
}
private scheduleWrite(): void {
if (this.writeTimer) return;
this.writeTimer = setTimeout(() => {
this.flushWrites();
this.writeTimer = null;
}, 100);
}
private async flushWrites(): Promise<void> {
const writes = Array.from(this.pendingWrites.entries());
this.pendingWrites.clear();
await Promise.all(
writes.map(([key, value]) =>
Keychain.setInternetCredentials(this.SERVICE, key, value)
)
);
}
}
3. Önleyici Token Yenileme
Gecikmeleri önlemek için token’ları süreleri dolmadan yenileyin:
class PreemptiveTokenManager {
private readonly PREEMPTIVE_REFRESH_WINDOW = 10 * 60 * 1000; // 10 dakika
async getValidToken(): Promise<string> {
const tokens = await SecureStorage.getTokens();
if (!tokens) {
throw new Error('Token mevcut değil');
}
const timeUntilExpiry = tokens.expiresAt - Date.now();
// Pencere içindeyse önleyici olarak yenile
if (timeUntilExpiry < this.PREEMPTIVE_REFRESH_WINDOW) {
// Yenilemenin tamamlanmasını bekleme
this.performTokenRefresh().catch(console.error);
}
return tokens.accessToken;
}
}
Sonraki Adımlar
Bu oturum yönetim sistemini uyguladıktan sonra, şu geliştirmeleri düşünün:
- Çok Faktörlü Kimlik Doğrulama: TOTP veya SMS tabanlı 2FA ekleyin
- Cihaz Güveni: Cihaz parmak izi ve güvenilir cihaz yönetimi uygulayın
- Oturum Analitiği: Oturum süresi, yenileme kalıpları ve güvenlik olaylarını takip edin
- Çevrimdışı Destek: Çevrimdışı token doğrulama ve çevrimiçi olduğunda senkronizasyon uygulayın
- Gelişmiş Biyometrikler: Yedek mekanizmalar ve biyometrik değişiklik algılama ekleyin
Production dağıtımı için:
- Mobil optimizasyon için Auth0 kiracı ayarlarını yapılandırın
- Token yenileme hataları için izleme kurun
- Uygun hata takibi ve uyarı uygulayın
- Güvenlik olayları için kapsamlı loglama ekleyin
- Çeşitli cihazlarda ve işletim sistemi sürümlerinde test edin
Sonuç
React Native’de güçlü oturum yönetimi uygulamak, mobil özgü kısıtlamaların ve güvenlik gereksinimlerinin dikkatli bir şekilde değerlendirilmesini gerektirir. Bu uygulama, token yaşam döngüsünün karmaşıklıklarını, biyometrik kimlik doğrulamayı ve production’da sıklıkla sorunlara neden olan edge case’leri ele alan production’a hazır bir temel sağlar.
Bu uygulamadan çıkan temel içgörüler:
- Manuel yönetim yerine Auth0’ın yerleşik token rotasyon özelliklerini kullanın
- Üstel geri çekilme ile uygun yeniden deneme mantığı uygulayın
- Ağ durumu değişikliklerini zarif bir şekilde ele alın
- Platforma özgü güvenli depolama mekanizmaları kullanın
- Kullanıcı kesintisini önlemek için önleyici token yenileme uygulayın
Güvenliğin sürekli bir süreç olduğunu unutmayın. Düzenli güvenlik denetimleri, bağımlılık güncellemeleri ve kimlik doğrulama kalıplarının izlenmesi, güvenli ve güvenilir bir kimlik doğrulama sistemi sürdürmeye yardımcı olacaktır.
İlgili yazılar
React Native Expo uygulamasına Sentry hata izleme entegrasyonu için adım adım rehber. SDK başlatma, Expo Router enstrümantasyonu, session replay, EAS Build ve EAS Update için source map yükleme ve sık karşılaşılan sorunları kapsar.
Micro frontend'lerde Auth0 multi-audience authentication gerçek dünya implementasyonu, token yönetim stratejileri ve React Native'de WebView tabanlı micro frontend'lerle silent authentication
AI agent geliştirmek için TypeScript SDK'larının pratik karşılaştırması - Vercel AI SDK, OpenAI Agents SDK ve AWS Bedrock entegrasyonu. Kod örnekleri, karar frameworkleri ve production patternleri içeriyor.
Modern TypeScript linting ve formatlama araçlarının kapsamlı karşılaştırması - ESLint, Prettier, Biome ve Oxlint - performans ölçümleri, konfigürasyon örnekleri ve migration stratejileriyle.
Effect'i anlamak, adım adım öğrenmek ve AWS Lambda ile entegre etmek için kapsamlı bir rehber. Gerçek kod örnekleri, yaygın hatalar ve üretim kullanımından pratik desenler içerir.