Le linee guida di Apple per l'App Store (App Store Review Guideline 4.8) richiedono obbligatoriamente che qualsiasi applicazione che offre il login tramite terze parti (come Google, Facebook o GitHub) offra come opzione equivalente anche Accedi con Apple (Sign in with Apple).
In questo tutorial vediamo come implementare un'esperienza di login fluida e sicura sia con Apple che con Google in un'app SwiftUI, convalidando crittograficamente i token JWT ricevuti sul backend Laravel Sanctum containerizzato con Docker.
1. Il Flusso di Autenticazione Mobile Sicuro
Un errore di sicurezza gravissimo è inviare dal client solo l'indirizzo email e fidarsi ciecamente della richiesta. Il flusso corretto prevede:
- L'utente effettua il login nativo tramite Face ID / Touch ID (Apple) o web sheet OAuth (Google).
- Il framework di sistema restituisce un Identity Token crittografato (JWT) firmato direttamente da Apple o Google.
- L'app invia questo
identity_tokenalle API del backend. - Il backend Laravel verifica la firma digitale del token contattando gli endpoint pubblici di Apple (JWKS) o Google, estrae l'identificativo univoco (
sub) e l'email, crea o aggiorna l'utente nel database PostgreSQL ed emette un Bearer Token Sanctum per le successive richieste autenticate.
2. Implementazione Client SwiftUI
1. Pulsante "Accedi con Apple" in SwiftUI:
import SwiftUI
import AuthenticationServices
struct AppleLoginButtonView: View {
@ObservedObject var authManager = AuthManager.shared
var body: some View {
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 identityTokenData = appleIDCredential.identityToken
let identityToken = identityTokenData.flatMap { String(data: $0, encoding: .utf8) } ?? ""
let email = appleIDCredential.email ?? ""
let fullName = [appleIDCredential.fullName?.givenName, appleIDCredential.fullName?.familyName]
.compactMap { $0 }.joined(separator: " ")
Task {
await authManager.loginWithApple(
identityToken: identityToken,
userIdentifier: userIdentifier,
email: email,
fullName: fullName
)
}
}
case .failure(let error):
print("Errore Sign in with Apple: \(error.localizedDescription)")
}
}
.signInWithAppleButtonStyle(.white)
.frame(height: 50)
.clipShape(RoundedRectangle(cornerRadius: 12))
}
}
3. Convalida del Token sul Backend Laravel (Docker)
Nel container Docker del backend Laravel, creiamo un controller dedicato per validare i token e creare la sessione utente.
Controller AuthController.php in Laravel:
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
use Firebase\JWT\JWT;
use Firebase\JWT\JWK;
class AuthController extends Controller
{
/**
* Autenticazione con Sign in with Apple
*/
public function appleLogin(Request $request)
{
$validated = $request->validate([
'identity_token' => 'required|string',
'user_identifier' => 'required|string',
'email' => 'nullable|email',
'full_name' => 'nullable|string',
]);
// Recupera le chiavi pubbliche ufficiali di Apple (JWKS)
$response = Http::get('https://appleid.apple.com/auth/keys');
if (!$response->successful()) {
return response()->json(['error' => 'Impossibile contattare i server Apple'], 500);
}
$jwks = $response->json();
$keys = JWK::parseKeySet($jwks);
try {
$decoded = JWT::decode($validated['identity_token'], $keys);
} catch (\Exception $e) {
return response()->json(['error' => 'Token Apple non valido o scaduto'], 401);
}
$appleSub = $decoded->sub;
$email = $validated['email'] ?? $decoded->email ?? "{$appleSub}@privaterelay.appleid.com";
$name = $validated['full_name'] ?: 'Utente Apple';
// Crea o trova l'utente nel DB PostgreSQL
$user = User::firstOrCreate(
['apple_id' => $appleSub],
[
'name' => $name,
'email' => $email,
'password' => bcrypt(Str::random(32)),
'handle' => Str::slug($name) . '_' . Str::random(4),
]
);
$token = $user->createToken('mobile-ios-app')->plainTextToken;
return response()->json([
'token' => $token,
'user' => $user,
]);
}
/**
* Autenticazione con Google Sign-In
*/
public function googleLogin(Request $request)
{
$validated = $request->validate([
'id_token' => 'required|string',
'full_name' => 'nullable|string',
]);
// Verifica token direttamente con Google OAuth2
$response = Http::get('https://oauth2.googleapis.com/tokeninfo', [
'id_token' => $validated['id_token'],
]);
if (!$response->successful()) {
return response()->json(['error' => 'Token Google non valido'], 401);
}
$payload = $response->json();
$googleId = $payload['sub'];
$email = $payload['email'];
$name = $validated['full_name'] ?: ($payload['name'] ?? 'Utente Google');
$user = User::firstOrCreate(
['google_id' => $googleId],
[
'name' => $name,
'email' => $email,
'password' => bcrypt(Str::random(32)),
'handle' => Str::slug($name) . '_' . Str::random(4),
]
);
$token = $user->createToken('mobile-ios-app')->plainTextToken;
return response()->json([
'token' => $token,
'user' => $user,
]);
}
}
4. Gestione della Private Relay Email & Privacy
Quando un utente seleziona l'opzione "Nascondi la mia email", Apple genera un indirizzo del tipo xyz@privaterelay.appleid.com. Ricordati che Apple restituisce il nome e l'email solo durante il primissimo login. Salva immediatamente questi dati nel tuo database durante la registrazione iniziale!
5. Sicurezza e Checklist di Rilascio
- Zero Secret Hardcoded: Configura sempre le chiavi di Bundle ID e Client ID tramite variabili di ambiente
.envall'interno dei container Docker. - Persistenza Sicura: Salva il token Sanctum nel Keychain di iOS tramite Security Framework o KeychainAccess, mai in chiaro in file di testo non protetti.
- Revoca delle Sessioni: Implementa l'endpoint
/api/auth/logoutche revoca il token corrente:$request->user()->currentAccessToken()->delete();.
Vuoi implementare un sistema di autenticazione moderno e a prova di bomba?
Offriamo sviluppo backend e client nativo iOS con standard di sicurezza elevati, supporto per autenticazione biometrica (Face ID) e conformità alle linee guida Apple App Store.