Sign in with Microsoft works on iOS

This commit is contained in:
2025-03-07 15:05:58 -06:00
parent 9f13d0357a
commit 07bf94d393
3 changed files with 158 additions and 76 deletions

View File

@ -1,102 +1,135 @@
import React, { useState } from 'react';
import { StyleSheet, Alert } from 'react-native';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, useAuthRequest, useAutoDiscovery } from 'expo-auth-session';
import { Platform, StyleSheet } from 'react-native';
import * as Linking from 'expo-linking';
import * as AuthSession from 'expo-auth-session';
import * as QueryParams from 'expo-auth-session/build/QueryParams';
import { supabase } from '@/lib/supabase';
import { ThemedView, ThemedTextButton } from '@/components/theme';
import { ThemedView, ThemedButton, ThemedText } from '@/components/theme';
import { Colors } from '@/constants/Colors';
// This is important - it completes the auth session when the browser redirects back to your app
WebBrowser.maybeCompleteAuthSession();
// Configuration for Azure AD
const tenantId = process.env.EXPO_PUBLIC_AZURE_TENANT_ID;
const clientId = process.env.EXPO_PUBLIC_AZURE_CLIENT_ID;
// Create MSAL auth request
const redirectUri = Linking.createURL('auth/callback');
const discovery = {
authorizationEndpoint: `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize`,
tokenEndpoint: `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`,
};
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');
console.log('Starting Azure sign-in with tenant-specific endpoint');
console.log('Redirect URI:', redirectUri);
// Create the MSAL auth request
const request = new AuthSession.AuthRequest({
clientId: clientId!,
scopes: ['openid', 'profile', 'email', 'offline_access', 'User.Read'],
redirectUri,
usePKCE: true,
responseType: AuthSession.ResponseType.Code,
});
// Generate the auth URL with PKCE
const authUrl = await request.makeAuthUrlAsync(discovery);
console.log('Generated auth URL:', authUrl);
// Open the auth URL in a browser
const result = await WebBrowser.openAuthSessionAsync(
authUrl,
redirectUri,
{
showInRecents: true,
}
} 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',
);
console.log('Auth session result type:', result.type);
if (result.type === 'success' && result.url) {
// Parse the URL to get the authorization code
const { params, errorCode } = QueryParams.getQueryParams(result.url);
if (errorCode || params.error) {
const errorMessage = params.error_description || params.error || errorCode;
throw new Error(`Error during authentication: ${errorMessage}`);
}
if (!params.code) {
throw new Error('No authorization code received');
}
console.log('Authorization code received');
// Exchange the code for tokens
const tokenResult = await AuthSession.exchangeCodeAsync(
{
clientId: clientId!,
code: params.code,
redirectUri,
extraParams: {
code_verifier: request.codeVerifier || '',
},
},
discovery
);
console.log('Token exchange successful');
if (!tokenResult.idToken) {
throw new Error('No ID token received');
}
// Now use the ID token to sign in with Supabase
const { data, error } = await supabase.auth.signInWithIdToken({
provider: 'azure',
token: tokenResult.idToken,
});
if (error) {
console.error('Supabase sign-in error:', error);
throw error;
}
console.log('Successfully signed in with Azure via Supabase');
return data;
} else {
console.log('Authentication was canceled or failed');
}
} catch (error) {
} catch (error: any) {
console.error('Error signing in with Azure:', error);
alert(`Error signing in: ${error.message}`);
Alert.alert('Sign In Error', error.message || 'An error occurred during sign in');
} finally {
setLoading(false);
}
};
return (
<ThemedView style={styles.verticallySpaced}>
<ThemedTextButton
text="Sign in with Microsoft"
disabled={loading || !request}
<ThemedButton
disabled={loading}
onPress={signInWithAzure}
fontSize={18}
/>
>
<ThemedText
lightColor={Colors.dark.text}
darkColor={Colors.light.text}
type="defaultSemiBold"
>
{loading ? "Signing in..." : "Sign in with Microsoft"}
</ThemedText>
</ThemedButton>
</ThemedView>
);
};
export default AzureSignIn;
const styles = StyleSheet.create({
@ -106,4 +139,4 @@ const styles = StyleSheet.create({
alignItems: 'center',
marginTop: 20,
},
});
});