Sign in with Microsoft works on iOS
This commit is contained in:
parent
9f13d0357a
commit
07bf94d393
@ -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,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
46
package-lock.json
generated
46
package-lock.json
generated
@ -9,6 +9,8 @@
|
||||
"version": "52.0.59",
|
||||
"license": "0BSD",
|
||||
"dependencies": {
|
||||
"@azure/msal-browser": "^4.5.1",
|
||||
"@azure/msal-react": "^3.0.5",
|
||||
"@expo/metro-runtime": "~4.0.1",
|
||||
"@expo/ngrok": "4.1.0",
|
||||
"@expo/vector-icons": "^14.0.2",
|
||||
@ -39,6 +41,7 @@
|
||||
"expo-updates": "~0.26.13",
|
||||
"expo-web-browser": "~14.0.2",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-native": "0.76.6",
|
||||
@ -89,6 +92,40 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/msal-browser": {
|
||||
"version": "4.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-4.5.1.tgz",
|
||||
"integrity": "sha512-vcva6qA4ytVjg52Ew+RxXGKRuoDMdvNOwT+kECNC36kujYalFQe9B5SNud4WVa/Zk12KFa0bkOHFnjP8cgDv3A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@azure/msal-common": "15.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/msal-common": {
|
||||
"version": "15.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-15.2.0.tgz",
|
||||
"integrity": "sha512-HiYfGAKthisUYqHG1nImCf/uzcyS31wng3o+CycWLIM9chnYJ9Lk6jZ30Y6YiYYpTQ9+z/FGUpiKKekd3Arc0A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/msal-react": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@azure/msal-react/-/msal-react-3.0.5.tgz",
|
||||
"integrity": "sha512-TrkExiYuytgDnEX53Rfq02GeZXo7sCQ2isK9PJpR+5kZ+9nuhKnUjV4BL+KKF69RdkmRTcgqaOB6dOH3xlrO0Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@azure/msal-browser": "^4.3.0",
|
||||
"react": "^16.8.0 || ^17 || ^18"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.26.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
|
||||
@ -10869,6 +10906,15 @@
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/jwt-decode": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
|
||||
"integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
|
15
package.json
15
package.json
@ -18,7 +18,10 @@
|
||||
"preset": "jest-expo"
|
||||
},
|
||||
"dependencies": {
|
||||
"@azure/msal-browser": "^4.5.1",
|
||||
"@azure/msal-react": "^3.0.5",
|
||||
"@expo/metro-runtime": "~4.0.1",
|
||||
"@expo/ngrok": "4.1.0",
|
||||
"@expo/vector-icons": "^14.0.2",
|
||||
"@react-native-async-storage/async-storage": "^1.23.1",
|
||||
"@react-navigation/bottom-tabs": "^7.2.0",
|
||||
@ -27,13 +30,17 @@
|
||||
"aes-js": "^3.1.2",
|
||||
"expo": "~52.0.28",
|
||||
"expo-apple-authentication": "~7.1.3",
|
||||
"expo-auth-session": "~6.0.3",
|
||||
"expo-blur": "~14.0.3",
|
||||
"expo-constants": "~17.0.7",
|
||||
"expo-dev-client": "~5.0.12",
|
||||
"expo-device": "~7.0.2",
|
||||
"expo-font": "~13.0.3",
|
||||
"expo-haptics": "~14.0.1",
|
||||
"expo-insights": "~0.8.2",
|
||||
"expo-linking": "~7.0.5",
|
||||
"expo-location": "~18.0.7",
|
||||
"expo-notifications": "~0.29.13",
|
||||
"expo-router": "~4.0.17",
|
||||
"expo-secure-store": "~14.0.1",
|
||||
"expo-splash-screen": "~0.29.21",
|
||||
@ -43,6 +50,7 @@
|
||||
"expo-updates": "~0.26.13",
|
||||
"expo-web-browser": "~14.0.2",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-native": "0.76.6",
|
||||
@ -52,12 +60,7 @@
|
||||
"react-native-safe-area-context": "4.12.0",
|
||||
"react-native-screens": "~4.4.0",
|
||||
"react-native-web": "~0.19.13",
|
||||
"react-native-webview": "13.12.5",
|
||||
"@expo/ngrok": "4.1.0",
|
||||
"expo-notifications": "~0.29.13",
|
||||
"expo-device": "~7.0.2",
|
||||
"expo-location": "~18.0.7",
|
||||
"expo-auth-session": "~6.0.3"
|
||||
"react-native-webview": "13.12.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
|
Loading…
x
Reference in New Issue
Block a user