feat(auth/ios): native Apple Sign-In via expo-apple-authentication
Vorher: stores/auth.ts hatte TODO + fiel auf Supabase-Web-OAuth-Flow zurück,
was fehlschlug mit 400 'Unsupported provider: missing OAuth client ID' weil
der Supabase-Apple-OAuth-Provider nicht konfiguriert ist.
Jetzt: native Flow ohne Supabase-Provider-Config —
- expo-apple-authentication.signInAsync() → identityToken
- supabase.auth.signInWithIdToken({provider:'apple', token}) verifiziert direkt
gegen Apple's Public-Keys (kein Client-Secret-JWT-Setup nötig)
- User-Cancel (ERR_REQUEST_CANCELED) → leeres Resultat statt Error
- Platform-Guard: Apple-Path nur auf iOS
app.config.ts: ios.usesAppleSignIn=true → Expo prebuild generiert das
com.apple.developer.applesignin-Entitlement in die .entitlements. Beim
ersten EAS-Build wird die Capability auto-registriert im Apple-Developer-
Portal für org.rebreak.app.
signin.tsx + signup.tsx: Apple-Button conditional auf Platform.OS==='ios'
gerendert. Android-User sehen nur Google-Button (auf Android gibt es kein
natives Apple Sign-In).
App-Store-Submission-Pflicht (Apple Guideline 4.8 — wer OAuth-Login mit
3rd-Party-Provider anbietet, muss auch Apple Sign-In bieten).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
534f978b4e
commit
23cc147231
@ -21,6 +21,10 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
|
||||
supportsTablet: true,
|
||||
bundleIdentifier: "org.rebreak.app",
|
||||
buildNumber: "10",
|
||||
// Apple Sign-In Entitlement — Pflicht für expo-apple-authentication nativen
|
||||
// signInAsync()-Flow. Ohne flag generiert Expo's prebuild den
|
||||
// com.apple.developer.applesignin-Entitlement nicht in die .entitlements.
|
||||
usesAppleSignIn: true,
|
||||
config: {
|
||||
usesNonExemptEncryption: false,
|
||||
},
|
||||
|
||||
@ -5,6 +5,7 @@ import {
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
ActivityIndicator,
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
@ -268,6 +269,7 @@ export default function SignInScreen() {
|
||||
<Text className="text-neutral-900 text-base" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('auth.googleSignin')}</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
{Platform.OS === 'ios' ? (
|
||||
<TouchableOpacity
|
||||
onPress={() => onOAuth('apple')}
|
||||
disabled={isLoading}
|
||||
@ -282,6 +284,7 @@ export default function SignInScreen() {
|
||||
)}
|
||||
<Text className="text-white text-base" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('auth.appleSignin')}</Text>
|
||||
</TouchableOpacity>
|
||||
) : null}
|
||||
|
||||
{/* Divider */}
|
||||
<View className="flex-row items-center mb-6">
|
||||
|
||||
@ -6,6 +6,7 @@ import {
|
||||
TouchableOpacity,
|
||||
Image,
|
||||
ActivityIndicator,
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
@ -132,6 +133,7 @@ export default function SignUpScreen() {
|
||||
<Text className="text-neutral-900 text-base" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('auth.googleSignup')}</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
{Platform.OS === 'ios' ? (
|
||||
<TouchableOpacity
|
||||
onPress={() => onOAuth('apple')}
|
||||
disabled={isLoading}
|
||||
@ -146,6 +148,7 @@ export default function SignUpScreen() {
|
||||
)}
|
||||
<Text className="text-white text-base" style={{ fontFamily: 'Nunito_600SemiBold' }}>{t('auth.appleSignup')}</Text>
|
||||
</TouchableOpacity>
|
||||
) : null}
|
||||
|
||||
{/* Divider */}
|
||||
<View className="flex-row items-center mb-6">
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { create } from 'zustand';
|
||||
import { Platform } from 'react-native';
|
||||
import type { Session, User } from '@supabase/supabase-js';
|
||||
import * as WebBrowser from 'expo-web-browser';
|
||||
import * as Linking from 'expo-linking';
|
||||
import * as AppleAuthentication from 'expo-apple-authentication';
|
||||
import { supabase } from '../lib/supabase';
|
||||
import { apiFetch } from '../lib/api';
|
||||
|
||||
@ -129,15 +131,44 @@ export const useAuthStore = create<AuthState>((set) => ({
|
||||
},
|
||||
|
||||
signInWithOAuth: async (provider) => {
|
||||
const redirectUri = Linking.createURL('auth/callback');
|
||||
|
||||
// Apple Sign-In native flow (iOS only) — KEIN Supabase-Apple-OAuth-Provider-
|
||||
// Config nötig (Bundle-ID = Apple-Client-ID). expo-apple-authentication
|
||||
// liefert identityToken → supabase.auth.signInWithIdToken verifiziert direkt
|
||||
// gegen Apple's public keys. App-Store-Submit-Pflicht weil Google-Sign-In
|
||||
// auch da ist (Apple Guideline 4.8).
|
||||
if (provider === 'apple') {
|
||||
// TODO: configure Apple Sign-In
|
||||
// Requires expo-apple-authentication to be installed + Apple Developer entitlement.
|
||||
// Apple Client ID = Bundle ID (org.rebreak.app) for native flow.
|
||||
// For now we fall through to the Supabase OAuth web flow as a temporary path.
|
||||
if (Platform.OS !== 'ios') {
|
||||
return { error: 'Apple Sign-In ist nur auf iOS verfügbar.' };
|
||||
}
|
||||
const available = await AppleAuthentication.isAvailableAsync();
|
||||
if (!available) {
|
||||
return { error: 'Apple Sign-In auf diesem Gerät nicht verfügbar.' };
|
||||
}
|
||||
try {
|
||||
const credential = await AppleAuthentication.signInAsync({
|
||||
requestedScopes: [
|
||||
AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
|
||||
AppleAuthentication.AppleAuthenticationScope.EMAIL,
|
||||
],
|
||||
});
|
||||
if (!credential.identityToken) {
|
||||
return { error: 'Apple identityToken fehlt.' };
|
||||
}
|
||||
const { data, error } = await supabase.auth.signInWithIdToken({
|
||||
provider: 'apple',
|
||||
token: credential.identityToken,
|
||||
});
|
||||
if (error) return { error: error.message };
|
||||
set({ session: data.session, user: data.user ?? null });
|
||||
return {};
|
||||
} catch (e: any) {
|
||||
// User-Cancel ist kein Error — leere Antwort.
|
||||
if (e?.code === 'ERR_REQUEST_CANCELED') return {};
|
||||
return { error: e?.message ?? 'Apple Sign-In fehlgeschlagen.' };
|
||||
}
|
||||
}
|
||||
|
||||
const redirectUri = Linking.createURL('auth/callback');
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider,
|
||||
options: {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user