← back to Costa Rica App
costa-rica-app: Sign in with Apple — expo-apple-authentication button on login, usesAppleSignIn + plugin, api.apple()
2d0b15d4c5963153d8d113d7f0a3d80f51c6ed11 · 2026-08-07 10:18:51 -0700 · Steve Abrams
Files touched
M app.jsonM app/login.tsxM package.jsonM src/api.ts
Diff
commit 2d0b15d4c5963153d8d113d7f0a3d80f51c6ed11
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Aug 7 10:18:51 2026 -0700
costa-rica-app: Sign in with Apple — expo-apple-authentication button on login, usesAppleSignIn + plugin, api.apple()
---
app.json | 3 ++-
app/login.tsx | 36 +++++++++++++++++++++++++++++++++++-
package.json | 1 +
src/api.ts | 1 +
4 files changed, 39 insertions(+), 2 deletions(-)
diff --git a/app.json b/app.json
index a43cfcc..ce74a36 100644
--- a/app.json
+++ b/app.json
@@ -9,6 +9,7 @@
"newArchEnabled": true,
"ios": {
"bundleIdentifier": "com.abrams.costarica",
+ "usesAppleSignIn": true,
"supportsTablet": true,
"infoPlist": {
"ITSAppUsesNonExemptEncryption": false,
@@ -19,7 +20,7 @@
"package": "com.abrams.costarica",
"permissions": ["ACCESS_FINE_LOCATION"]
},
- "plugins": ["expo-router", "expo-secure-store"],
+ "plugins": ["expo-router", "expo-secure-store", "expo-apple-authentication"],
"extra": {
"apiBase": "https://costarica.agentabrams.com",
"eas": { "projectId": "TO_BE_SET_BY_eas_init" }
diff --git a/app/login.tsx b/app/login.tsx
index b00b8b5..69cf9b4 100644
--- a/app/login.tsx
+++ b/app/login.tsx
@@ -1,5 +1,6 @@
import { useState } from 'react';
-import { View, Text, TextInput, TouchableOpacity, StyleSheet, Alert } from 'react-native';
+import { View, Text, TextInput, TouchableOpacity, StyleSheet, Alert, Platform } from 'react-native';
+import * as AppleAuthentication from 'expo-apple-authentication';
import { router } from 'expo-router';
import { api, setToken } from '../src/api';
@@ -21,9 +22,39 @@ export default function Login() {
router.back();
} catch (e: any) { Alert.alert('Error', e.message); } finally { setBusy(false); }
}
+ async function appleSignIn() {
+ try {
+ const cred = await AppleAuthentication.signInAsync({
+ requestedScopes: [
+ AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
+ AppleAuthentication.AppleAuthenticationScope.EMAIL,
+ ],
+ });
+ const fullName = [cred.fullName?.givenName, cred.fullName?.familyName].filter(Boolean).join(' ');
+ const r = await api.apple(cred.identityToken!, fullName || undefined);
+ await setToken(r.token);
+ router.back();
+ } catch (e: any) {
+ if (e.code === 'ERR_REQUEST_CANCELED') return;
+ Alert.alert('Apple sign-in failed', e.message);
+ }
+ }
+
return (
<View style={s.wrap}>
<Text style={s.h}>{mode === 'login' ? 'Welcome back' : 'Create account'}</Text>
+ {Platform.OS === 'ios' && (
+ <>
+ <AppleAuthentication.AppleAuthenticationButton
+ buttonType={AppleAuthentication.AppleAuthenticationButtonType.SIGN_IN}
+ buttonStyle={AppleAuthentication.AppleAuthenticationButtonStyle.BLACK}
+ cornerRadius={10}
+ style={{ height: 48, marginBottom: 14 }}
+ onPress={appleSignIn}
+ />
+ <View style={s.orRow}><View style={s.line} /><Text style={s.or}>or</Text><View style={s.line} /></View>
+ </>
+ )}
{mode === 'register' && <TextInput style={s.i} placeholder="Full name" value={name} onChangeText={setName} />}
<TextInput style={s.i} placeholder="Email" autoCapitalize="none" keyboardType="email-address" value={email} onChangeText={setEmail} />
{mode === 'register' && <TextInput style={s.i} placeholder="Phone (+506…)" keyboardType="phone-pad" value={phone} onChangeText={setPhone} />}
@@ -44,4 +75,7 @@ const s = StyleSheet.create({
cta: { backgroundColor: '#0a7d55', height: 52, borderRadius: 12, alignItems: 'center', justifyContent: 'center', marginTop: 6 },
ctaTxt: { color: '#fff', fontSize: 16, fontWeight: '700' },
switch: { color: '#0a7d55', textAlign: 'center', marginTop: 18 },
+ orRow: { flexDirection: 'row', alignItems: 'center', marginBottom: 14 },
+ line: { flex: 1, height: 1, backgroundColor: '#d8ddd6' },
+ or: { color: '#6b756e', marginHorizontal: 10, fontSize: 13 },
});
diff --git a/package.json b/package.json
index 9ac980b..d15b490 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,7 @@
},
"dependencies": {
"expo": "^52.0.0",
+ "expo-apple-authentication": "~7.1.0",
"expo-router": "~4.0.0",
"expo-constants": "~17.0.0",
"expo-linking": "~7.0.0",
diff --git a/src/api.ts b/src/api.ts
index 3aaaa50..f98a1cc 100644
--- a/src/api.ts
+++ b/src/api.ts
@@ -28,6 +28,7 @@ async function req(path: string, opts: RequestInit = {}) {
export const api = {
register: (b: any) => req('/auth/register', { method: 'POST', body: JSON.stringify(b) }),
login: (email: string, password: string) => req('/auth/login', { method: 'POST', body: JSON.stringify({ email, password }) }),
+ apple: (identity_token: string, full_name?: string) => req('/auth/apple', { method: 'POST', body: JSON.stringify({ identity_token, full_name }) }),
me: () => req('/me'),
listings: (q: Record<string, string> = {}) => req('/listings?' + new URLSearchParams(q).toString()),
listing: (slug: string) => req(`/listings/${slug}`),
← e816038 costa-rica-app: Expo/EAS native scaffold — Router nav, JWT a
·
back to Costa Rica App
·
costa-rica-app: Contacts screen — import address book (expo- 2368baa →