Autenticazione Multimodale in SwiftUI: Sign-In con Apple, Google OAuth2 e Token JWT Sanctum con Gestione Guest Mode

Fornire diverse modalità di accesso è fondamentale per massimizzare il tasso di conversione in fase di onboarding su un'app iOS. Le linee guida di Apple (App Store Review Guidelines 4.8) impongono che se un'app offre l'accesso con terze parti (come Google o Facebook), deve obbligatoriamente offrire anche il Sign-In con Apple.

In questo articolo analizziamo la struttura completa di un gestore di autenticazione multimodale in SwiftUI collegato ad un backend Laravel Sanctum su VPS, coprendo Apple Sign-In, Google OAuth2, Login con Email/Password e la modalità Guest Mode (Esploratore Anonimo).

1. Architettura dell'Auth Manager in SwiftUI

L'autenticazione deve essere gestita da un singleton conforme al protocollo ObservableObject o al nuovo macro @Observable di Swift 5.9, con persistenza sicura dei token JWT all'interno dell'iOS Keychain.

import SwiftUI
import AuthenticationServices

@MainActor
public final class EchoSocialAuthManager: ObservableObject {
    public static let shared = EchoSocialAuthManager()

    @Published public var isAuthenticated: Bool = false
    @Published public var isGuest: Bool = false
    @Published public var currentUser: UserProfile?
    @Published public var authToken: String?

    private init() {
        loadStoredSession()
    }

    public func loadStoredSession() {
        if let token = KeychainHelper.standard.read(service: "bitre-auth", account: "sanctum-token") {
            self.authToken = token
            self.isAuthenticated = true
            Task { await fetchCurrentProfile() }
        }
    }

    public func setSession(token: String, user: UserProfile) {
        KeychainHelper.standard.save(token, service: "bitre-auth", account: "sanctum-token")
        self.authToken = token
        self.currentUser = user
        self.isAuthenticated = true
        self.isGuest = false
    }

    public func enableGuestMode() {
        self.isGuest = true
        self.isAuthenticated = true
        self.currentUser = UserProfile.guestUser()
    }

    public func logout() {
        KeychainHelper.standard.delete(service: "bitre-auth", account: "sanctum-token")
        self.authToken = nil
        self.currentUser = nil
        self.isAuthenticated = false
        self.isGuest = false
    }
}

2. Integrazione Native Sign-In con Apple

Con il framework AuthenticationServices, puoi integrare il pulsante nativo Apple in pochi righi di codice in SwiftUI:

SignInWithAppleButton(.signIn) { request in
    request.requestedScopes = [.fullName, .email]
} onCompletion: { result in
    switch result {
    case .success(let authorization):
        if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
            let userIdentifier = appleIDCredential.user
            let email = appleIDCredential.email ?? ""
            let fullName = [appleIDCredential.fullName?.givenName, appleIDCredential.fullName?.familyName]
                .compactMap { $0 }.joined(separator: " ")

            guard let identityTokenData = appleIDCredential.identityToken,
                  let identityTokenString = String(data: identityTokenData, encoding: .utf8) else { return }

            Task {
                await loginWithBackendApple(appleId: userIdentifier, email: email, name: fullName, token: identityTokenString)
            }
        }
    case .failure(let error):
        print("Apple Auth Error: \(error.localizedDescription)")
    }
}
.signInWithAppleButtonStyle(.whiteOutline)
.frame(height: 50)
.cornerRadius(12)

3. Sanificazione del Backend Laravel per Apple Private Relay

Quando l'utente seleziona "Nascondi la mia email", Apple genera un indirizzo anonimo del tipo xyz123@privaterelay.appleid.com. Inoltre, durante i login successivi al primo, Apple non reinvia il nome e l'email. Il controller Laravel deve sanificare le stringhe vuote ed evitare di sovrascrivere l'email già salvata:

<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Str;

class AuthController extends Controller
{
    public function appleAuth(Request $request)
    {
        $validated = $request->validate([
            'apple_id' => 'required|string',
            'email' => 'nullable|string',
            'name' => 'nullable|string',
        ]);

        $appleId = $validated['apple_id'];
        $rawEmail = trim($validated['email'] ?? '');
        $rawName = trim($validated['name'] ?? '');

        // Cerca utente esistente tramite apple_id
        $user = User::where('apple_id', $appleId)->first();

        if (!$user) {
            $email = !empty($rawEmail) ? $rawEmail : "apple_" . Str::random(8) . "@privaterelay.appleid.com";
            $name = !empty($rawName) ? $rawName : "Apple Explorer";
            $handle = Str::slug($name) . "_" . Str::random(4);

            $user = User::create([
                'apple_id' => $appleId,
                'name' => $name,
                'email' => $email,
                'handle' => $handle,
                'password' => bcrypt(Str::random(32)),
            ]);
        }

        $token = $user->createToken('ios-device')->plainTextToken;

        return response()->json([
            'status' => 'success',
            'token' => $token,
            'user' => $user
        ]);
    }
}

4. Gestione Sicura Keychain in iOS

I token JWT non dovrebbero mai essere memorizzati in UserDefaults perché non sono crittografati. Utilizza la Security API nativa di iOS per memorizzare le credenziali nel Keychain crittografato dal chip Secure Enclave del dispositivo.

Hai un'idea di App iOS o desideri consulenza tecnica per la tua infrastruttura Cloud VPS?

Sviluppo applicazioni iOS native in SwiftUI, backend Laravel ad alte prestazioni e supporto per monetizzazione internazionale.

Diallo Yunus

Diallo Yunus

Indie iOS & macOS Developer, Fondatore di DialloDev e autore di 11+ app pubblicate su Apple App Store. Aiuto startup, creator e aziende a costruire prodotti digitali di successo.

Torna a Tutte le 155 Guide del Blog