Start trying to support Microsoft Azure Sign in

This commit is contained in:
Gabriel Brown 2025-03-06 16:55:13 -06:00
parent 84cae666bf
commit 9f13d0357a
7 changed files with 167 additions and 11 deletions

View File

@ -33,7 +33,10 @@
}, },
"permissions": [ "permissions": [
"android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_COARSE_LOCATION",
"android.permission.ACCESS_FINE_LOCATION" "android.permission.ACCESS_FINE_LOCATION",
"android.permission.RECEIVE_BOOT_COMPLETED",
"android.permission.VIBRATE",
"android.permission.INTERNET"
], ],
"package": "com.gbrown.techtracker" "package": "com.gbrown.techtracker"
}, },

View File

@ -24,6 +24,7 @@ const AppleSignInButton = () => {
//projectId, //projectId,
//}); //});
if (credential.identityToken) { if (credential.identityToken) {
const email = credential.email;
const full_name = ( const full_name = (
credential.fullName && credential.fullName &&
credential.fullName.givenName && credential.fullName.givenName &&
@ -41,16 +42,26 @@ const AppleSignInButton = () => {
}); });
console.log(JSON.stringify({ error, user }, null, 2)) console.log(JSON.stringify({ error, user }, null, 2))
if (!error) { if (!error) {
if (user) { if (user && session) {
const data: any = { const data: any = {};
full_name, if (email) data.email = email;
}; if (full_name) data.full_name = full_name;
const { error: updateError } = await supabase.auth.updateUser({ const { error: updateError } = await supabase.auth.updateUser({
data, data,
}); });
if (updateError) { if (updateError) {
console.error('Error updating user metadata:', updateError); console.error('Error updating user metadata:', updateError);
} }
const { error: updateProfileError } =
await supabase.from('profiles').upsert({
id: session.user.id,
username: data?.email.split('@')[0],
full_name: data?.full_name,
updated_at: new Date(),
});
if (updateProfileError) {
console.error('Error updating profile:', updateProfileError);
}
} }
} }
} else { } else {
@ -67,10 +78,10 @@ const AppleSignInButton = () => {
} }
} }
if (Platform.OS !== 'ios') return <div/>; if (Platform.OS !== 'ios') return <ThemedView />;
else return ( else return (
<ThemedView style={[styles.verticallySpaced, styles.mt20]}> <ThemedView style={styles.verticallySpaced}>
<AppleAuthentication.AppleAuthenticationButton <AppleAuthentication.AppleAuthenticationButton
buttonType={AppleAuthentication.AppleAuthenticationButtonType.SIGN_IN} buttonType={AppleAuthentication.AppleAuthenticationButtonType.SIGN_IN}
buttonStyle={ buttonStyle={
@ -92,8 +103,6 @@ const styles = StyleSheet.create({
paddingTop: 4, paddingTop: 4,
paddingBottom: 4, paddingBottom: 4,
alignItems: 'center', alignItems: 'center',
},
mt20: {
marginTop: 20, marginTop: 20,
}, },
}); });

View File

@ -2,7 +2,8 @@ import React, { useState, useEffect } from 'react';
import { Alert, StyleSheet, AppState, Image, Platform } from 'react-native'; import { Alert, StyleSheet, AppState, Image, Platform } from 'react-native';
import { supabase } from '@/lib/supabase'; import { supabase } from '@/lib/supabase';
import { ThemedView, ThemedText, ThemedTextButton, ThemedTextInput } from '@/components/theme'; import { ThemedView, ThemedText, ThemedTextButton, ThemedTextInput } from '@/components/theme';
import AppleSignInButton from '@/components/auth/AppleSignIn.native'; import AppleSignInButton from '@/components/auth/AppleSignIniOS';
import AzureSignIn from './AzureSignIn';
// Tells Supabase Auth to continuously refresh the session automatically if // Tells Supabase Auth to continuously refresh the session automatically if
// the app is in the foreground. When this is added, you will continue to receive // the app is in the foreground. When this is added, you will continue to receive
@ -103,6 +104,7 @@ const Auth = () => {
/> />
</ThemedView> </ThemedView>
<AppleSignInButton /> <AppleSignInButton />
<AzureSignIn />
</ThemedView> </ThemedView>
); );
}; };

View File

@ -0,0 +1,109 @@
import React, { useState } from 'react';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, useAuthRequest, useAutoDiscovery } from 'expo-auth-session';
import { Platform, StyleSheet } from 'react-native';
import { supabase } from '@/lib/supabase';
import { ThemedView, ThemedTextButton } from '@/components/theme';
// This is important - it completes the auth session when the browser redirects back to your app
WebBrowser.maybeCompleteAuthSession();
const AzureSignIn = () => {
const [loading, setLoading] = useState(false);
// Get environment variables
const tenantId = process.env.EXPO_PUBLIC_AZURE_TENANT_ID as string;
const clientId = process.env.EXPO_PUBLIC_AZURE_CLIENT_ID as string;
// Set up the discovery endpoint for Azure AD
const discovery = useAutoDiscovery(
`https://login.microsoftonline.com/${tenantId}/v2.0`
);
// Create a redirect URI that matches what you configured in Azure
// This should match the redirect URI you set in your Supabase dashboard
const redirectUri = makeRedirectUri({
scheme: Platform.OS === 'web' ? undefined : 'com.gbrown.techtracker',
path: Platform.OS === 'web' ? 'auth/callback' : undefined,
});
// Set up the auth request with the needed scopes
const [request, response, promptAsync] = useAuthRequest(
{
clientId,
scopes: ['openid', 'profile', 'email', 'offline_access'],
redirectUri,
responseType: 'code', // Important for Supabase
usePKCE: true, // Use PKCE for added security
},
discovery
);
const signInWithAzure = async () => {
try {
setLoading(true);
// For Expo Go and mobile apps, we need to use the Expo Auth Session flow
if (Platform.OS !== 'web') {
// Launch the browser for authentication
const result = await promptAsync();
if (result.type === 'success') {
// If we got an authorization code, use Supabase to exchange it for a session
const { code } = result.params;
const { data, error } = await supabase.auth.exchangeCodeForSession(code);
if (error) {
throw error;
}
console.log('Successfully signed in with Azure!', data.user);
} else if (result.type === 'error') {
throw new Error(result.error?.message || 'Authentication failed');
} else if (result.type === 'cancel') {
console.log('User cancelled the login flow');
}
} else {
// For web, we can use Supabase's built-in OAuth flow
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'azure',
options: {
scopes: 'email profile openid offline_access',
redirectTo: window.location.origin + '/auth/callback',
},
});
if (error) {
throw error;
}
}
} catch (error) {
console.error('Error signing in with Azure:', error);
alert(`Error signing in: ${error.message}`);
} finally {
setLoading(false);
}
};
return (
<ThemedView style={styles.verticallySpaced}>
<ThemedTextButton
text="Sign in with Microsoft"
disabled={loading || !request}
onPress={signInWithAzure}
fontSize={18}
/>
</ThemedView>
);
};
export default AzureSignIn;
const styles = StyleSheet.create({
verticallySpaced: {
paddingTop: 4,
paddingBottom: 4,
alignItems: 'center',
marginTop: 20,
},
});

31
package-lock.json generated
View File

@ -19,6 +19,7 @@
"aes-js": "^3.1.2", "aes-js": "^3.1.2",
"expo": "~52.0.28", "expo": "~52.0.28",
"expo-apple-authentication": "~7.1.3", "expo-apple-authentication": "~7.1.3",
"expo-auth-session": "~6.0.3",
"expo-blur": "~14.0.3", "expo-blur": "~14.0.3",
"expo-constants": "~17.0.7", "expo-constants": "~17.0.7",
"expo-dev-client": "~5.0.12", "expo-dev-client": "~5.0.12",
@ -7668,6 +7669,24 @@
"react-native": "*" "react-native": "*"
} }
}, },
"node_modules/expo-auth-session": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/expo-auth-session/-/expo-auth-session-6.0.3.tgz",
"integrity": "sha512-s7LmmMPiiY1NXrlcXkc4+09Hlfw9X1CpaQOCDkwfQEodG1uCYGQi/WImTnDzw5YDkWI79uC8F1mB8EIerilkDA==",
"license": "MIT",
"dependencies": {
"expo-application": "~6.0.2",
"expo-constants": "~17.0.5",
"expo-crypto": "~14.0.2",
"expo-linking": "~7.0.5",
"expo-web-browser": "~14.0.2",
"invariant": "^2.2.4"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/expo-blur": { "node_modules/expo-blur": {
"version": "14.0.3", "version": "14.0.3",
"resolved": "https://registry.npmjs.org/expo-blur/-/expo-blur-14.0.3.tgz", "resolved": "https://registry.npmjs.org/expo-blur/-/expo-blur-14.0.3.tgz",
@ -7693,6 +7712,18 @@
"react-native": "*" "react-native": "*"
} }
}, },
"node_modules/expo-crypto": {
"version": "14.0.2",
"resolved": "https://registry.npmjs.org/expo-crypto/-/expo-crypto-14.0.2.tgz",
"integrity": "sha512-WRc9PBpJraJN29VD5Ef7nCecxJmZNyRKcGkNiDQC1nhY5agppzwhqh7zEzNFarE/GqDgSiaDHS8yd5EgFhP9AQ==",
"license": "MIT",
"dependencies": {
"base64-js": "^1.3.0"
},
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-dev-client": { "node_modules/expo-dev-client": {
"version": "5.0.12", "version": "5.0.12",
"resolved": "https://registry.npmjs.org/expo-dev-client/-/expo-dev-client-5.0.12.tgz", "resolved": "https://registry.npmjs.org/expo-dev-client/-/expo-dev-client-5.0.12.tgz",

View File

@ -56,7 +56,8 @@
"@expo/ngrok": "4.1.0", "@expo/ngrok": "4.1.0",
"expo-notifications": "~0.29.13", "expo-notifications": "~0.29.13",
"expo-device": "~7.0.2", "expo-device": "~7.0.2",
"expo-location": "~18.0.7" "expo-location": "~18.0.7",
"expo-auth-session": "~6.0.3"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.25.2", "@babel/core": "^7.25.2",

View File

@ -72,6 +72,7 @@ async function registerForPushNotificationsAsync() {
} }
const projectId = Constants.expoConfig?.extra?.eas?.projectId; const projectId = Constants.expoConfig?.extra?.eas?.projectId;
if (!projectId) { if (!projectId) {
console.warn('No project id found');
alert('Project ID not found'); alert('Project ID not found');
return; return;
} }