Skip to content
~/sph.sh

Micro Frontend'lerde Multi-Audience Auth0: Token Yönetimi Kalıpları ve Implementasyon

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

Özet

Auth0 multi-audience authentication'ı dağıtık micro frontend'ler arasında uygulamak, özellikle hem web hem de React Native WebView ortamlarını desteklerken benzersiz zorluklar sunar. Bu vaka çalışması, tek login akışında birden fazla API audience'ını destekleyen birleşik token yönetim sistemi için etkili kalıpları belgeler.

Ele Alınan Temel Zorluklar:

  • Micro frontend'ler arasında cross-domain token paylaşımı
  • Auth0 ile multi-audience JWT token yönetimi
  • React Native WebView'larda silent authentication
  • Dağıtık uygulamalar arasında token refresh koordinasyonu

Çözüm Özeti: Mesaj tabanlı iletişim, silent authentication akışları ve güvenliği korurken birden fazla login gereksinimini ortadan kaldıran native-web köprüleri ile merkezileştirilmiş token yöneticisi. Token refresh koordinasyonu shell uygulamasında merkezi tutulur; micro frontend'ler sadece mevcut token'ı alır.

Problem Tanımı

Micro frontend migrasyonumuz kritik bir authentication darboğazını ortaya çıkardı. Mimari şunları gerektiriyordu:

Bu dağıtık sistem mimarisini düşünün:

Temel Problem: Her API, JWT token'da farklı bir audience gerektirir, ancak Auth0'nun standart akışı authentication başına yalnızca bir audience'ı destekler. Bu, kullanıcıları üç kez ayrı ayrı authenticate etmeye zorlayacaktır. Tek sign-on deneyimini korumak için alternatif token stratejileri gerekiyordu.

Ek Karmaşıklık: Sistemin React Native WebView'larda sorunsuz çalışması gerekiyordu; burada geleneksel web authentication kalıpları cookie kısıtlamaları ve cross-origin sınırlamaları nedeniyle bozulur.

Başarısız Yaklaşımlar

Çalışan çözümümüze ulaşmadan önce, gereksinimlerimizi karşılamayan birkaç yaklaşımı keşfettik:

Yaklaşım 1: Birden Fazla Auth0 Application

Konsept: Her micro frontend için ayrı Auth0 application'ları oluştur. Neden başarısız oldu: Kullanıcılar yine birden fazla login yapmak zorundaydı ve application konfigürasyonlarını yönetmek hantal hale geldi. Cross-application session paylaşımı güvenilmez oldu.

Yaklaşım 2: Permission Scoping ile Tek Audience

Konsept: API erişimini kontrol etmek için ince taneli scope'lar ile bir audience kullan. Neden başarısız oldu: API'ler audience-spesifik permission'ları düzgün validate edemedi ve takımlar arasında scope yönetimi karmaşık hale geldi.

Yaklaşım 3: Server-Side Token Exchange

Konsept: Farklı audience'lar için token'ları server-side'da exchange et. Neden başarısız oldu: Önemli gecikme ekledi, tüm servislerde backend değişiklikleri gerektirdi ve React Native implementasyonunu karmaşıklaştırdı.

Çalışan Çözüm: Koordineli Multi-Audience Token Yönetimi

Başarılı yaklaşımımız, tüm micro frontend'ler için authentication'ı orkestre eden shell application'a odaklanır:

1. Token Manager Mimarisi

typescript
// token-manager.ts - Auth sistemimizin beyniinterface TokenSet {  accessToken: string;  idToken: string;  refreshToken: string;  expiresAt: number;  audience: string;  scope: string;}
class MultiAudienceTokenManager {  private tokens: Map<string, TokenSet> = new Map();  private primaryRefreshToken: string | null = null;  private auth0Client: Auth0Client;
  constructor(config: Auth0Config) {    this.auth0Client = new Auth0Client({      domain: config.domain,      clientId: config.clientId,      cacheLocation: 'memory', // Micro frontend'ler için kritik      useRefreshTokens: true,      authorizeTimeoutInSeconds: 60    });  }
  async loginWithMultipleAudiences(audiences: string[]): Promise<void> {    // Adım 1: Primary audience ile login (tüm scope'ları içerir)    const primaryAudience = audiences[0];    const allScopes = this.getAllRequiredScopes();
    const result = await this.auth0Client.loginWithRedirect({      audience: primaryAudience,      scope: allScopes,      redirect_uri: window.location.origin    });
    // Redirect callback'ten sonra    const tokens = await this.auth0Client.handleRedirectCallback();    this.primaryRefreshToken = tokens.refreshToken;
    // Primary token'ı sakla    this.storeToken(primaryAudience, tokens);
    // Adım 2: Diğer audience'lar için sessizce token al    for (const audience of audiences.slice(1)) {      await this.getTokenForAudience(audience);    }  }
  async getTokenForAudience(audience: string): Promise<string> {    // Önce cache'i kontrol et    const cached = this.tokens.get(audience);    if (cached && cached.expiresAt > Date.now()) {      return cached.accessToken;    }
    try {      // Önce silent authentication dene      const token = await this.auth0Client.getTokenSilently({        audience: audience,        scope: this.getScopeForAudience(audience),        cacheMode: 'off' // Fresh token zorla      });
      this.storeToken(audience, {        accessToken: token,        expiresAt: Date.now() + 3600000, // 1 saat        audience: audience,        scope: this.getScopeForAudience(audience)      });
      return token;    } catch (error) {      // Silent auth başarısız olursa, refresh token kullan      if (this.primaryRefreshToken) {        return this.refreshTokenForAudience(audience);      }      throw error;    }  }
  private async refreshTokenForAudience(audience: string): Promise<string> {    // Auth0 token endpoint'i ile refresh token grant    const response = await fetch(`https://\${this.auth0Client.domain}/oauth/token`, {      method: 'POST',      headers: { 'Content-Type': 'application/json' },      body: JSON.stringify({        grant_type: 'refresh_token',        client_id: this.auth0Client.clientId,        refresh_token: this.primaryRefreshToken,        audience: audience,        scope: this.getScopeForAudience(audience)      })    });
    const data = await response.json();
    this.storeToken(audience, {      accessToken: data.access_token,      idToken: data.id_token,      expiresAt: Date.now() + (data.expires_in * 1000),      audience: audience,      scope: data.scope    });
    return data.access_token;  }}

2. Micro Frontend'ler için Cross-Domain Token Paylaşımı

En büyük zorluk: Token'ları farklı subdomain'ler arasında paylaşmak. Test edilmiş çözümümüz:

typescript
// shared-auth-context.tsx - Tüm micro frontend'ler tarafından kullanılırimport { createContext, useContext, useEffect, useState } from 'react';
interface SharedAuthState {  isAuthenticated: boolean;  tokens: Map<string, string>;  user: any;}
const SharedAuthContext = createContext<SharedAuthState | null>(null);
// Cross-tab/cross-iframe iletişim için broadcast channelconst authChannel = new BroadcastChannel('auth-sync');
export function SharedAuthProvider({ children, audience }: Props) {  const [authState, setAuthState] = useState<SharedAuthState>();  const [tokenManager] = useState(() => new MultiAudienceTokenManager());
  useEffect(() => {    // Diğer micro frontend'lerden auth güncellemelerini dinle    authChannel.onmessage = (event) => {      if (event.data.type === 'AUTH_UPDATE') {        setAuthState(event.data.payload);      }    };
    // Shared storage üzerinden authenticate olup olmadığımızı kontrol et    checkSharedAuthentication();  }, []);
  const checkSharedAuthentication = async () => {    // Birden fazla storage stratejisi dene
    // Strateji 1: iframe postMessage ile shared localStorage    const sharedToken = await getTokenFromShell();
    // Strateji 2: Server-side session kontrolü    if (!sharedToken) {      const session = await checkServerSession();      if (session) {        await silentAuthentication();      }    }
    // Strateji 3: Auth0 session kontrolü    if (!sharedToken) {      const auth0Session = await checkAuth0Session();      if (auth0Session) {        await getTokenSilently();      }    }  };
  const getTokenFromShell = (): Promise<string | null> => {    return new Promise((resolve) => {      // Shell application'a mesaj gönder      window.parent.postMessage(        { type: 'GET_TOKEN', audience },        'https://auth.myapp.com'      );
      // Cevabı dinle      const handler = (event: MessageEvent) => {        if (event.origin !== 'https://auth.myapp.com') return;        if (event.data.type === 'TOKEN_RESPONSE') {          window.removeEventListener('message', handler);          resolve(event.data.token);        }      };
      window.addEventListener('message', handler);
      // 1 saniye sonra timeout      setTimeout(() => {        window.removeEventListener('message', handler);        resolve(null);      }, 1000);    });  };
  return (    <SharedAuthContext.Provider value={authState}>      {children}    </SharedAuthContext.Provider>  );}

3. Shell Application - Authentication Orkestratörü

typescript
// shell-application.tsx - Authentication orkestratörüclass ShellAuthOrchestrator {  private microFrontends: Map<string, MicroFrontendConfig> = new Map();  private tokenManager: MultiAudienceTokenManager;  private sessionManager: SessionManager;
  async initialize() {    // Tüm micro frontend'leri ve gereken audience'larını kaydet    this.registerMicroFrontends([      {        name: 'billing',        url: 'https://billing.myapp.com',        audience: 'https://api.myapp.com/billing',        scopes: ['read:invoices', 'write:payments']      },      {        name: 'dashboard',        url: 'https://dashboard.myapp.com',        audience: 'https://api.myapp.com/core',        scopes: ['read:profile', 'read:data']      },      {        name: 'analytics',        url: 'https://analytics.myapp.com',        audience: 'https://api.myapp.com/analytics',        scopes: ['read:reports', 'read:metrics']      }    ]);
    // Micro frontend token request'leri için message handler kur    window.addEventListener('message', this.handleTokenRequest);
    // Authentication durumunu kontrol et    await this.checkAuthentication();  }
  private handleTokenRequest = async (event: MessageEvent) => {    // Origin'i validate et    const mfe = this.getMicroFrontendByOrigin(event.origin);    if (!mfe) return;
    if (event.data.type === 'GET_TOKEN') {      const token = await this.tokenManager.getTokenForAudience(        event.data.audience      );
      // Token'ı isteyen micro frontend'e geri gönder      event.source?.postMessage(        {          type: 'TOKEN_RESPONSE',          token: token,          audience: event.data.audience        },        event.origin      );    }  };
  async performLogin() {    // Tüm gereken audience'ları topla    const audiences = Array.from(this.microFrontends.values())      .map(mfe => mfe.audience);
    // Tüm audience'lar için tek login    await this.tokenManager.loginWithMultipleAudiences(audiences);
    // Tüm micro frontend'lere bildir    this.broadcastAuthUpdate();  }
  private broadcastAuthUpdate() {    const authChannel = new BroadcastChannel('auth-sync');    authChannel.postMessage({      type: 'AUTH_UPDATE',      payload: {        isAuthenticated: true,        user: this.tokenManager.getUser()      }    });  }}

Token Refresh Stratejisi

Micro frontend'lerde token refresh zor. Production'da test edilmiş yaklaşımımız:

typescript
// token-refresh-coordinator.tsclass TokenRefreshCoordinator {  private refreshPromises: Map<string, Promise<string>> = new Map();  private refreshTimers: Map<string, NodeJS.Timer> = new Map();
  setupAutoRefresh(audience: string, expiresIn: number) {    // Mevcut timer'ı temizle    const existingTimer = this.refreshTimers.get(audience);    if (existingTimer) clearTimeout(existingTimer);
    // Expiry'den 5 dakika önce refresh et    const refreshIn = (expiresIn - 300) * 1000;
    const timer = setTimeout(() => {      this.refreshToken(audience);    }, refreshIn);
    this.refreshTimers.set(audience, timer);  }
  async refreshToken(audience: string): Promise<string> {    // Aynı audience için concurrent refresh'i önle    const existing = this.refreshPromises.get(audience);    if (existing) return existing;
    const refreshPromise = this.performRefresh(audience);    this.refreshPromises.set(audience, refreshPromise);
    try {      const token = await refreshPromise;      return token;    } finally {      this.refreshPromises.delete(audience);    }  }
  private async performRefresh(audience: string): Promise<string> {    try {      // Önce silent refresh dene      const token = await auth0Client.getTokenSilently({        audience: audience,        ignoreCache: true      });
      // Expiry almak için decode et      const decoded = jwt_decode(token) as any;      const expiresIn = decoded.exp - Math.floor(Date.now() / 1000);
      // Sonraki refresh'i kur      this.setupAutoRefresh(audience, expiresIn);
      // Storage'ı güncelle      this.updateTokenStorage(audience, token);
      // Micro frontend'lere bildir      this.notifyTokenRefresh(audience, token);
      return token;    } catch (error) {      console.error(`Token refresh failed for \${audience}:`, error);
      // Refresh başarısız olursa, re-authentication dene      if (error.error === 'login_required') {        await this.handleLoginRequired();      }
      throw error;    }  }
  private notifyTokenRefresh(audience: string, token: string) {    // BroadcastChannel ile bildir    const channel = new BroadcastChannel('auth-sync');    channel.postMessage({      type: 'TOKEN_REFRESHED',      audience: audience,      token: token    });
    // iframe'lere postMessage ile bildir    const iframes = document.querySelectorAll('iframe');    iframes.forEach(iframe => {      iframe.contentWindow?.postMessage(        {          type: 'TOKEN_REFRESHED',          audience: audience,          token: token        },        '*'      );    });  }}

Multi-Audience Desteği için Auth0 Actions

Önemli: Auth0 Rules artık kullanımdan kaldırıldı. Multi-audience senaryoları için Actions kullanın:

javascript
// auth0-action.js - Actions kullanarak tüm audience'lar için custom claim'ler ekleexports.onExecutePostLogin = async (event, api) => {  const { user, request } = event;
  // Audience-specific permission'ları tanımla  const audiencePermissions = {    'https://api.myapp.com/billing': ['read:invoices', 'write:payments'],    'https://api.myapp.com/core': ['read:profile', 'read:data'],    'https://api.myapp.com/analytics': ['read:reports', 'read:metrics']  };
  // Hangi audience istendiğini kontrol et  const requestedAudience = request.query?.audience || request.body?.audience;
  // Collision önlemek için namespace ekle  const namespace = 'https://myapp.com/';
  // Tüm token'lara user metadata ekle  api.accessToken.setCustomClaim(namespace + 'email', user.email);  api.accessToken.setCustomClaim(namespace + 'roles', user.app_metadata?.roles || []);
  // Audience-specific permission'ları ekle  if (audiencePermissions[requestedAudience]) {    api.accessToken.setCustomClaim(namespace + 'permissions', audiencePermissions[requestedAudience]);  }
  // Sadece primary audience için refresh token göstergesi ekle  if (requestedAudience === 'https://api.myapp.com/core') {    api.accessToken.setCustomClaim(namespace + 'can_refresh', true);  }};

Silent Authentication: Sorunsuz Deneyimin Arkasındaki Sihir

Silent authentication, multi-audience yaklaşımının birden fazla login olmadan çalışmasını sağlayan şey:

typescript
// silent-auth-handler.tsclass SilentAuthHandler {  private iframe: HTMLIFrameElement | null = null;  private timeoutMs = 60000; // 60 saniye
  async performSilentAuth(options: SilentAuthOptions): Promise<TokenSet> {    // Silent auth için hidden iframe oluştur    this.iframe = this.createAuthIframe();
    const authUrl = this.buildAuthUrl(options);
    return new Promise((resolve, reject) => {      const timeout = setTimeout(() => {        this.cleanup();        reject(new Error('Silent authentication timeout'));      }, this.timeoutMs);
      // Auth response'u dinle      const handleMessage = (event: MessageEvent) => {        if (event.origin !== `https://${AUTH0_DOMAIN}`) return;
        clearTimeout(timeout);
        if (event.data.type === 'authorization_response') {          this.handleAuthResponse(event.data)            .then(resolve)            .catch(reject)            .finally(() => this.cleanup());        }
        if (event.data.type === 'authorization_error') {          this.cleanup();          reject(new Error(event.data.error));        }      };
      window.addEventListener('message', handleMessage);
      // iframe'i auth URL'e navigate et      this.iframe.src = authUrl;    });  }
  private createAuthIframe(): HTMLIFrameElement {    const iframe = document.createElement('iframe');    iframe.style.display = 'none';    iframe.style.visibility = 'hidden';    iframe.style.position = 'fixed';    iframe.style.width = '0';    iframe.style.height = '0';    document.body.appendChild(iframe);    return iframe;  }
  private buildAuthUrl(options: SilentAuthOptions): string {    const params = new URLSearchParams({      client_id: AUTH0_CLIENT_ID,      response_type: 'token id_token',      redirect_uri: `\${window.location.origin}/silent-callback.html`,      audience: options.audience,      scope: options.scope,      state: this.generateState(),      nonce: this.generateNonce(),      prompt: 'none', // Silent auth için kritik      response_mode: 'web_message' // postMessage kullan    });
    return `https://${AUTH0_DOMAIN}/authorize?${params}`;  }
  private async handleAuthResponse(response: any): Promise<TokenSet> {    // State ve nonce'u validate et    if (!this.validateState(response.state)) {      throw new Error('State validation failed');    }
    // Response'tan token'ları parse et    return {      accessToken: response.access_token,      idToken: response.id_token,      expiresIn: response.expires_in,      tokenType: response.token_type,      audience: response.audience    };  }
  private cleanup() {    if (this.iframe && this.iframe.parentNode) {      this.iframe.parentNode.removeChild(this.iframe);      this.iframe = null;    }  }}

React Native'de WebView Micro Frontend'ler

Şimdi gerçekten eğlenceli kısım - tüm bunları React Native'de WebView tabanlı micro frontend'lerle çalıştırmak:

typescript
// react-native-auth-bridge.tsximport React, { useRef, useEffect } from 'react';import { WebView } from 'react-native-webview';import AsyncStorage from '@react-native-async-storage/async-storage';import { authorize, refresh } from 'react-native-app-auth';
interface AuthBridge {  webViewRef: React.RefObject<WebView>;  tokens: Map<string, string>;}
export function AuthenticatedMicroFrontend({ url, audience }: Props) {  const webViewRef = useRef<WebView>(null);  const [tokens, setTokens] = useState<Map<string, string>>(new Map());
  // React Native için Auth0 config  const auth0Config = {    issuer: `https://${AUTH0_DOMAIN}`,    clientId: AUTH0_CLIENT_ID,    redirectUrl: 'com.myapp://auth/callback',    scopes: ['openid', 'profile', 'email', 'offline_access'],    additionalParameters: {      audience: audience    },    customHeaders: {      'Auth0-Client': Buffer.from(        JSON.stringify({ name: 'MyApp', version: '1.0.0' })      ).toString('base64')    }  };
  // Native authentication  const performNativeAuth = async () => {    try {      // Native Auth0 flow için react-native-app-auth kullan      const result = await authorize(auth0Config);
      // Token'ları sakla      await AsyncStorage.setItem('auth_tokens', JSON.stringify({        accessToken: result.accessToken,        idToken: result.idToken,        refreshToken: result.refreshToken,        expiresAt: new Date(result.accessTokenExpirationDate).getTime()      }));
      // Gerekirse diğer audience'lar için token'ları al      await getMultipleAudienceTokens(result.refreshToken);
      return result;    } catch (error) {      console.error('Native auth başarısız:', error);      throw error;    }  };
  // React Native ve WebView arasında köprü  const injectedJavaScript = `    (function() {      // Native bridge kullanmak için Auth0 client'ı override et      window.nativeAuth = {        getToken: function(audience) {          return new Promise((resolve, reject) => {            // Unique request ID oluştur            const requestId = Math.random().toString(36).substr(2, 9);
            // Response handler kur            window.handleTokenResponse = function(id, token, error) {              if (id !== requestId) return;
              if (error) {                reject(new Error(error));              } else {                resolve(token);              }
              delete window.handleTokenResponse;            };
            // React Native'den token iste            window.ReactNativeWebView.postMessage(JSON.stringify({              type: 'GET_TOKEN',              audience: audience,              requestId: requestId            }));          });        },
        silentAuth: function(options) {          return new Promise((resolve, reject) => {            window.ReactNativeWebView.postMessage(JSON.stringify({              type: 'SILENT_AUTH',              options: options            }));
            window.handleSilentAuthResponse = function(result, error) {              if (error) {                reject(error);              } else {                resolve(result);              }              delete window.handleSilentAuthResponse;            };          });        }      };
      // Auth0 client initialization'ı intercept et      if (window.createAuth0Client) {        const originalCreate = window.createAuth0Client;        window.createAuth0Client = async function(config) {          // Native bridge kullanan mock client döndür          return {            getTokenSilently: async (options) => {              return window.nativeAuth.getToken(options.audience);            },            loginWithRedirect: async () => {              window.ReactNativeWebView.postMessage(JSON.stringify({                type: 'LOGIN_REQUIRED'              }));            },            isAuthenticated: async () => {              return window.nativeAuth.isAuthenticated();            }          };        };      }    })();
    true; // Injection'ın çalışması için gerekli  `;
  // WebView'dan mesajları handle et  const handleWebViewMessage = async (event: any) => {    const message = JSON.parse(event.nativeEvent.data);
    switch (message.type) {      case 'GET_TOKEN':        await handleTokenRequest(message);        break;
      case 'SILENT_AUTH':        await handleSilentAuth(message);        break;
      case 'LOGIN_REQUIRED':        await performNativeAuth();        break;    }  };
  const handleTokenRequest = async (message: any) => {    try {      // İstenen audience için token al      let token = tokens.get(message.audience);
      if (!token || isTokenExpired(token)) {        // Native auth kullanarak token'ı refresh et        token = await refreshTokenForAudience(message.audience);        tokens.set(message.audience, token);      }
      // Token'ı WebView'a geri gönder      webViewRef.current?.injectJavaScript(`        window.handleTokenResponse(          '\${message.requestId}',          '\${token}',          null        );      `);    } catch (error) {      // Error'u WebView'a geri gönder      webViewRef.current?.injectJavaScript(`        window.handleTokenResponse(          '\${message.requestId}',          null,          '\${error.message}'        );      `);    }  };
  const handleSilentAuth = async (message: any) => {    try {      // Valid session var mı kontrol et      const storedTokens = await AsyncStorage.getItem('auth_tokens');
      if (storedTokens) {        const tokens = JSON.parse(storedTokens);
        if (tokens.expiresAt > Date.now()) {          // Valid token'larımız var, istenen audience için token al          const audienceToken = await getTokenForAudience(            message.options.audience          );
          webViewRef.current?.injectJavaScript(`            window.handleSilentAuthResponse({              accessToken: '\${audienceToken}',              expiresIn: 3600            }, null);          `);          return;        }      }
      // Refresh'i dene      const refreshed = await refreshAuth();      if (refreshed) {        const audienceToken = await getTokenForAudience(          message.options.audience        );
        webViewRef.current?.injectJavaScript(`          window.handleSilentAuthResponse({            accessToken: '\${audienceToken}',            expiresIn: 3600          }, null);        `);      } else {        throw new Error('Silent auth başarısız - login gerekli');      }    } catch (error) {      webViewRef.current?.injectJavaScript(`        window.handleSilentAuthResponse(null, '\${error.message}');      `);    }  };
  const refreshAuth = async () => {    try {      const storedTokens = await AsyncStorage.getItem('auth_tokens');      if (!storedTokens) return false;
      const { refreshToken } = JSON.parse(storedTokens);
      // Refresh için react-native-app-auth kullan      const result = await refresh(auth0Config, {        refreshToken: refreshToken      });
      // Saklanan token'ları güncelle      await AsyncStorage.setItem('auth_tokens', JSON.stringify({        accessToken: result.accessToken,        idToken: result.idToken,        refreshToken: result.refreshToken || refreshToken,        expiresAt: new Date(result.accessTokenExpirationDate).getTime()      }));
      return true;    } catch (error) {      console.error('Token refresh başarısız:', error);      return false;    }  };
  return (    <WebView      ref={webViewRef}      source={{ uri: url }}      injectedJavaScript={injectedJavaScript}      onMessage={handleWebViewMessage}      sharedCookiesEnabled={true} // Session paylaşımı için önemli      thirdPartyCookiesEnabled={true} // Auth0 cookie'leri için      domStorageEnabled={true} // localStorage için    />  );}

React Native'de Silent Login: Komple Flow

React Native'de micro frontend'lerle silent login'in uçtan uca nasıl çalıştığı:

typescript
// silent-login-flow.tsclass SilentLoginFlow {  private auth0: Auth0Native;  private tokenCache: TokenCache;  private webViewBridge: WebViewBridge;
  async performSilentLogin(): Promise<boolean> {    // Adım 1: Native token cache'i kontrol et    const cachedTokens = await this.tokenCache.getTokens();
    if (cachedTokens && !this.isExpired(cachedTokens)) {      // Valid token'larımız var, WebView bridge'i kur      await this.setupWebViewBridge(cachedTokens);      return true;    }
    // Adım 2: Refresh token var mı kontrol et    const refreshToken = await this.tokenCache.getRefreshToken();
    if (refreshToken) {      try {        // Refresh'i dene        const newTokens = await this.auth0.refreshTokens(refreshToken);        await this.tokenCache.storeTokens(newTokens);        await this.setupWebViewBridge(newTokens);        return true;      } catch (error) {        console.log('Refresh başarısız, Auth0 session deneniyor');      }    }
    // Adım 3: Auth0 session'ı kontrol et (SSO)    try {      const ssoTokens = await this.checkAuth0Session();      if (ssoTokens) {        await this.tokenCache.storeTokens(ssoTokens);        await this.setupWebViewBridge(ssoTokens);        return true;      }    } catch (error) {      console.log('Auth0 session bulunamadı');    }
    // Adım 4: Biometric authentication fallback    if (await this.isBiometricAvailable()) {      const bioTokens = await this.attemptBiometricAuth();      if (bioTokens) {        await this.setupWebViewBridge(bioTokens);        return true;      }    }
    return false; // Silent login başarısız, explicit login gerekli  }
  private async checkAuth0Session(): Promise<TokenSet | null> {    // SSO kontrolü için custom tab / ASWebAuthenticationSession kullan    const ssoCheckUrl = `https://${AUTH0_DOMAIN}/authorize?` +      `client_id=${CLIENT_ID}&` +      `response_type=token&` +      `redirect_uri=${REDIRECT_URI}&` +      `scope=openid profile email&` +      `prompt=none&` + // Silent auth için kritik      `response_mode=query`;
    try {      // Bu hidden web session'da açılır      const result = await InAppBrowser.openAuth(ssoCheckUrl, REDIRECT_URI, {        ephemeralWebSession: false, // Shared session kullan        preferEphemeralSession: false      });
      if (result.type === 'success' && result.url) {        const tokens = this.parseAuthResponse(result.url);        return tokens;      }    } catch (error) {      return null;    }  }
  private async setupWebViewBridge(tokens: TokenSet) {    // WebView yüklenmeden önce token'ları inject et    const script = `      window.__AUTH_TOKENS__ = {        accessToken: '\${tokens.accessToken}',        idToken: '\${tokens.idToken}',        expiresAt: \${tokens.expiresAt}      };
      // Auto-renewal kur      window.__AUTH_BRIDGE__ = {        renewToken: async function(audience) {          return new Promise((resolve) => {            window.ReactNativeWebView.postMessage(JSON.stringify({              type: 'RENEW_TOKEN',              audience: audience            }));            window.__pendingRenewal = resolve;          });        }      };    `;
    this.webViewBridge.injectScript(script);  }}

Multi-Resource Refresh Token'lar (MRRT)

Auth0 multi-audience senaryoları için Multi-Resource Refresh Token'ları tanıttı. Tek bir refresh token ile birden fazla audience için access token alınabilir. MRRTTokenManager ile refreshMultipleAudiences pattern'i kullanılabilir.

Güvenlik Konuları

Kritik Uyarı: JWT token'ları her zaman backend'de validate edin. Asla sadece client-side token validation'a güvenmeyin.

typescript
// Backend JWT validation örneğiconst jwt = require('jsonwebtoken');const jwksClient = require('jwks-rsa');
const client = jwksClient({  jwksUri: `https://${AUTH0_DOMAIN}/.well-known/jwks.json`});
function getKey(header, callback) {  client.getSigningKey(header.kid, (err, key) => {    const signingKey = key.publicKey || key.rsaPublicKey;    callback(null, signingKey);  });}
// Token'ı validate etconst verifyToken = (token, audience) => {  return new Promise((resolve, reject) => {    jwt.verify(token, getKey, {      audience: audience,      issuer: `https://${AUTH0_DOMAIN}/`,      algorithms: ['RS256']    }, (err, decoded) => {      if (err) reject(err);      else resolve(decoded);    });  });};

Ek Güvenlik Önlemleri:

  1. Token Depolama: Token'lar için güvenli, şifrelenmiş depolama kullanın
  2. Origin Validation: postMessage handler'larda her zaman mesaj origin'lerini validate edin
  3. Sadece HTTPS: Token'ları asla şifrelenmemiş bağlantılar üzerinden iletmeyin
  4. Token Rotation: Uygun refresh token rotation uygulayın
  5. Audience Validation: Audience claim'lerinin beklenen değerlerle eşleştiğini doğrulayın

Implementasyon Dersleri

Auth0 session yönetimi için cookie kullanır. React Native WebView'larda third-party cookie'ler genelde engellenir. Çözüm:

typescript
// WebView'lar arasında cookie paylaşımını etkinleştirconst cookieManager = require('@react-native-cookies/cookies');
// Auth0 cookie'lerini WebView'lar arasında paylaşawait cookieManager.setFromResponse(  `https://${AUTH0_DOMAIN}`,  'auth0_session=...; SameSite=None; Secure');

2. Token Boyutu Problemi

Multiple audience token'ları = büyük localStorage. 10MB limitine takıldık. Çözüm:

typescript
// Storage'dan önce token'ları sıkıştırimport pako from 'pako';
const compressToken = (token: string): string => {  const compressed = pako.deflate(token, { to: 'string' });  return btoa(compressed);};
const decompressToken = (compressed: string): string => {  const binary = atob(compressed);  return pako.inflate(binary, { to: 'string' });};

3. Race Condition

Birden fazla micro frontend aynı anda token istediğinde race condition oluştu. Çözüm:

typescript
class TokenRequestQueue {  private queue: Map<string, Promise<string>> = new Map();
  async getToken(audience: string): Promise<string> {    // Zaten fetch ediliyorsa, mevcut promise'i döndür    const existing = this.queue.get(audience);    if (existing) return existing;
    // Yeni fetch promise'i oluştur    const fetchPromise = this.fetchToken(audience);    this.queue.set(audience, fetchPromise);
    try {      const token = await fetchPromise;      return token;    } finally {      // Resolution'dan sonra temizle      this.queue.delete(audience);    }    }}

React Native Güvenlik Implementasyonu

  1. Token Storage: Token'ları asla plain text olarak saklama. Encrypted storage kullan:
typescript
import * as Keychain from 'react-native-keychain';
// Token'ları güvenli saklaawait Keychain.setInternetCredentials(  'auth.myapp.com',  'tokens',  JSON.stringify(tokens),  {    accessControl: Keychain.ACCESS_CONTROL.BIOMETRY_CURRENT_SET,    accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY  });
  1. WebView Güvenliği: Tüm mesajları validate et:
typescript
const validateWebViewMessage = (event: any): boolean => {  // İzin verilen origin'leri whitelist'le  const allowedOrigins = [    'https://billing.myapp.com',    'https://dashboard.myapp.com'  ];
  if (!allowedOrigins.includes(event.origin)) {    console.error('Geçersiz origin:', event.origin);    return false;  }
  // Mesaj yapısını validate et  if (!event.data || typeof event.data !== 'object') {    return false;  }
  // Mesaj imzasını validate et (implement edilmişse)  if (!verifyMessageSignature(event.data)) {    return false;  }
  return true;};

Temel Çıkarımlar

Micro frontend'lerde multi-audience authentication karmaşık ama doğru mimariyle çözülebilir:

  1. Önce Mimari: Token yönetimini baştan merkezileştirilmiş bir servis olarak tasarlayın
  2. Edge Case'leri Test Edin: Süresi dolmuş token'lar, network hataları ve race condition'lar gerçek karmaşıklığı ortaya çıkarır
  3. Güvenlik Odaklı Tasarım: Uygun JWT validation ve güvenli token depolama uygulayın
  4. Progressive Enhancement: Önce web için geliştirin, sonra React Native WebView'lar için adapte edin
  5. Mesaj Kontratları: Bileşenler arasında açık iletişim protokolleri tanımlayın

Bu yaklaşım, production ortamlarında tutarlı silent authentication başarı oranlarıyla yüksek hacimli authentication yüklerini işleyerek güvenilir olduğunu kanıtlamıştır.

Implementasyon, karmaşık authentication senaryolarının dikkatli mimari ve güvenlik temellerine odaklanarak çözülebileceğini göstermektedir. Implementasyon detaylarına dalmadan önce token akış tasarımına odaklanın.

İlgili Yazılar