How to handle Auth with WebBrowser.openAuthSessionAsync in Expo? - react-native

I created a auth flow using WebBrowser.openAuthSessionAsync, the opening and the closing are working as expected but my problem comes with the return, I only receive back from the browser: {"type": "success", "url": "exp://192.168.0.11:19000/--/App"}, the type and my return URL.
I'm trying to figure out how to have access to any kind of info from the return, from cookies to query string on the URL as I have control over how my API handles the return, but I'm not being able to access any kind of info from the AuthSession.
Here is the implementation:
import { useEffect, useState } from "react"
import { Button, View, Text } from "react-native"
import * as WebBrowser from "expo-web-browser"
import * as Linking from "expo-linking"
export default () => {
const [result, setResult] =
useState<WebBrowser.WebBrowserAuthSessionResult | null>(null)
useEffect(() => {
console.log(result)
}, [result])
const _handlePressButtonAsync = async () => {
const baseUrl = "https://...com"
const callbackUrl = Linking.createURL("App", { scheme: "myapp" })
setResult(
await WebBrowser.openAuthSessionAsync(
`${baseUrl}/login?returnUrl=${encodeURIComponent(
`${baseUrl}/_v/login?token=...&iv=...&returnUrl=${callbackUrl}`
)}`,
callbackUrl
)
)
}
return (
<View className="items-center justify-center flex-1">
<Button title="Open Auth Session" onPress={_handlePressButtonAsync} />
{result && <Text>{JSON.stringify(result)}</Text>}
</View>
)
}
It's a SASS platform, that does not follow only the OAuth flow, which made me choose this method over https://docs.expo.dev/versions/latest/sdk/auth-session/

Related

Use RN Expo Web Browser dismissBrowser when on specific URL

Trying to achieve the following scenario
Client clicks a button and it opens the Expo web browser with a URL e.g wwww.example.com/test
User does something and is redirected to a URL like wwww.example.com/success
The app recognizes the URL and auto-closes the web browser
It correctly opens the web browser but nothing happens afterward when I go to wwww.example.com/success.
I'm not getting any errors and with the iOS Expo preview, I get no console.log triggers, but with the Web Expo preview, I get generic logging.
Code below
import React, { useState } from 'react';
import { Button, StyleSheet, Text, View } from 'react-native';
import * as Linking from 'expo-linking';
import * as WebBrowser from 'expo-web-browser';
export const WebVerification = () => {
const [redirectData, setRedirectData] = useState(null);
const _handleRedirect = (event) => {
console.log('handle Request is called')
let data = Linking.parse(event.url);
console.log(data)
if( data.url === "wwww.example.com/success") {
WebBrowser.dismissBrowser();
}
setRedirectData(data);
};
const _openBrowserAsync = async () => {
try {
_addLinkingListener();
let result = await WebBrowser.openBrowserAsync(
`wwww.example.com/test`
);
} catch (error) {
alert(error);
console.log(error);
}
};
const _addLinkingListener = () => {
Linking.addEventListener("url", _handleRedirect);
};
const _removeLinkingListener = () => {
Linking.removeEventListener("url", _handleRedirect);
};
const _maybeRenderRedirectData = () => {
console.log("check RenderRedirect" + redirectData)
if (!redirectData) {
return;
}
return (
<Text style={{ marginTop: 30 }}>
{JSON.stringify(redirectData)}
</Text>
);
};
return (
<View>
<Text>Redirect Example</Text>
<Button onPress={_openBrowserAsync} title="Open Browser" />
{_maybeRenderRedirectData()}
</View>
);
};

Why isn't the google accounts login page showing up even though I have implemented the expo auth session library with react native?

I have been following the tinder 2.0 react native tutorial https://youtu.be/qJaFIGjyRms At 1:04:00 he sets the sign in method to: "await Google.logInAsync()" but I have noticed the google app auth library used in the video is now deprecated, I am redirected to use expo auth session instead, with this new library I cannot tell whether the google sign in is working or not as I am simply redirected back to the homepage after clicking the login button.
Here is my code with response printed in the console:
Screenshot:
code:
import React, { createContext, useContext } from 'react'
import * as WebBrowser from "expo-web-browser";
import { Button } from "react-native";
import * as Google from "expo-auth-session/providers/google";
import { useEffect, useState } from "react";
import { useNavigation } from "#react-navigation/native";
import { GoogleAuthProvider, signInWithCredential } from 'firebase/auth';
const AuthContext = createContext({});
const user = null
WebBrowser.maybeCompleteAuthSession();
const GoogleLogin = () => {
const navigation = useNavigation();
const [request, response, promptAsync] = Google.useAuthRequest({
expoClientId:
"236293699216-bst43767un873mcddmmrpgf4v2h088jd.apps.googleusercontent.com",
iosClientId:
"236293699216-6jdpm0rd6kn5d0qlbh1vgva5afgbqgib.apps.googleusercontent.com",
webClientId:
"236293699216-9a0nknjdq7ie79h40iubg0tddokgogfv.apps.googleusercontent.com",
scopes: ["profile", "email"],
permissions: ["public_profile","email", "gender", "location"],
});
const asyncAuthRequest = async () => {
if (response?.type === "success") {
const { authentication } = response;
// await AsyncStorage.setItem("accessTocken", "hihi");
//navigation.navigate"Home");
const { idToken, accessToken} = response;
const credential = GoogleAuthProvider.credential(idToken, accessToken);
await signInWithCredential(auth, credential)
}
return Promise.reject();
};
useEffect(() => {
asyncAuthRequest();
}, [response]);
console.log('response', response)
return (
<Button
disabled={!request}
title="Login"
onPress={() => {
promptAsync();
}}
/>
);
};
export default GoogleLogin;

react-native WebView session after closing IOS application

I have a simple react native application with a WebView component for displaying my website based on php. So it works. But, when I login and close the application (on IOS), the session state was loose and I need login again. I know that all webview data like cookies cleared when the application close. So I need AsyncStorage to save all cookies to application storage and get it when application open for passing to headers.
Here's my code:
import React, {Component} from 'react';
import AsyncStorage from '#react-native-community/async-storage';
import CookieManager from '#react-native-community/cookies';
import {SafeAreaView, StyleSheet, Alert} from 'react-native';
import {WebView} from 'react-native-webview';
let domain = "[https]://mysite.com";
export default class WebViewScreen extends Component {
constructor(props) {
super(props);
this.currentUrl = domain;
this.myWebView = React.createRef();
this.state = {
isReady: false,
cookiesString: '',
};
}
jsonCookiesToCookieString = (json) => {
let cookiesString = '';
for (let [key, value] of Object.entries(json)) {
cookiesString += `${key}=${value.value}; `;
}
return cookiesString;
};
UNSAFE_componentWillMount() {
this.provideMeSavedCookies()
.then(async (savedCookies) => {
let cookiesString = this.jsonCookiesToCookieString(savedCookies);
const PHPSESSID = await AsyncStorage.getItem('PHPSESSID');
if (PHPSESSID) {
cookiesString += `PHPSESSID=${PHPSESSID};`;
}
this.setState({cookiesString, isReady: true});
})
.catch((e) => {
this.setState({isReady: true});
});
}
onLoadEnd = (syntheticEvent) => {
let successUrl = `${domain}/dashboard.php`;
if (this.currentUrl === successUrl) {
CookieManager.getAll(true).then((res) => {
AsyncStorage.setItem('savedCookies', JSON.stringify(res));
if (res.PHPSESSID) {
AsyncStorage.setItem('PHPSESSID', res.PHPSESSID.value);
}
});
}
};
onNavigationStateChange = (navState) => {
this.currentUrl = navState.url;
};
provideMeSavedCookies = async () => {
try {
let value = await AsyncStorage.getItem('savedCookies');
if (value !== null) {
return Promise.resolve(JSON.parse(value));
}
} catch (error) {
return {}
}
};
render() {
const {cookiesString, isReady} = this.state;
if (!isReady) {
return null;
}
return (
<SafeAreaView>
<WebView
ref={this.myWebView}
source={{
uri: domain,
headers: {
Cookie: cookiesString,
},
}}
allowsBackForwardNavigationGestures
originWhitelist={['*']}
scalesPageToFit
useWebKit
onLoadEnd={this.onLoadEnd}
onNavigationStateChange={this.onNavigationStateChange}
sharedCookiesEnabled={true}
javaScriptEnabled={true}
domStorageEnabled={true}
/>
</SafeAreaView>
);
}
}
I missed all styles for most clearly and understanding code
So, I login and close the application. Then open again. It works only for one request, but not for multiple or when navigation. When I'm trying go to another page I loose my session and redirect to login page. If I change domain variable from mysite.com to mysite.com/my-profile where /my-profile page only for authorized users, it works too. My headers work only for first request page. How to fix it? How save the session after closing the app for each pages, not only for domain value?

Expo: How to detect when a WebBrowser instance is closed by the user?

I have an Expo app that will open some web page with a redirect to expo itself. On that case, this is to perform 3DS callbacks. Here is a very simplified version:
import React, {
FC, useEffect, useState,
} from 'react';
import * as Linking from 'expo-linking';
import * as WebBrowser from 'expo-web-browser';
import {
Button,
} from '#private/apps-components';
import {
ButtonProps,
View,
} from 'react-native';
export const MyComponent: FC = () => {
const [loading, setLoading] = useState<boolean>(false);
const urlEventHandler = async (event): Promise<void> => {
console.log('url-event', event);
setLoading(false);
// Stuff...
};
useEffect(() => {
Linking.addEventListener('url', urlEventHandler);
return () => Linking.removeEventListener('url', urlEventHandler);
}, []);
const handlePress: ButtonProps['onPress'] = () => {
setLoading(false);
WebBrowser.openBrowserAsync(aRandomUrlThatWillRedirectToTheApp, {
showInRecents: true,
})
}
return (
<View>
<Button
title="Test"
onPress={handlePress}
loading={loading}
/>
</View>
);
};
export default null;
This is working. However, if the customer close the navigator before the web redirect is being processed, the app is stuck on the loading state.
The question is: How to detect if a user has closed the opened WebBrowser?
Solved this using AppState:
https://reactnative.dev/docs/appstate
if (appState === "active") { // do things after closing the browser }
I haven't actually tested this - could follow up - but you could probably use react-navigation to detect whether the component is in focus or not. IE when you open the web browser, the component is not in focus, but when you close the web browser, the component is back in focus.
For react navigation version 4 you would wrap the component in withNavigationFocus in order to achieve this: https://reactnavigation.org/docs/4.x/function-after-focusing-screen#triggering-an-action-with-the-withnavigationfocus-higher-order-component. For 5, and 5+, you can use the useIsFocused hook: https://reactnavigation.org/docs/5.x/function-after-focusing-screen/#re-rendering-screen-with-the-useisfocused-hook

React Native Navigate To Another Screen Automatically

I am trying to navigate back to the LogIn screen as soon as logout.js is hit.
At one point this seemed to work and then stopped and I can't work out why.
Here is my code:
import React, { Component } from 'react';
import { Text, View, Button } from 'react-native';
import * as SecureStore from 'expo-secure-store';
import { globalStyles } from '../styles/global';
export default class App extends Component {
componentDidMount() {
(async () => {
const token = await SecureStore.getItemAsync('token');
// Your fetch code
this.setState({loaded:false, error: null});
let url = 'https://www.example.com/api/auth/logout';
let h = new Headers();
h.append('Authorization', `Bearer ${token}`);
h.append('Content-Type', 'application/json');
h.append('X-Requested-With', 'XMLHttpRequest');
let req = new Request(url, {
headers: h,
method: 'GET'
});
fetch(req)
.then(response=>response.json())
//.then(this.showData)
.catch(this.badStuff)
})();
this.deleteToken()
}
badStuff = (err) => {
this.setState({loaded: true, error: err.message});
}
deleteToken() {
(async () => {
const token = await SecureStore.deleteItemAsync('token')
});
this.goToLogInScreen()
}
goToLogInScreen() {
this.props.navigation.navigate('LogIn')
}
render() {
return (
<View style={globalStyles.container}>
<Button
title="Go to Details"
onPress={() => this.props.navigation.navigate('LogIn')}
/>
</View>
);
}
}
I need the code to:
Send the command to the API to log out
Delete the token from SecureStore
Navigate to the LogIn screen (containing the log in form).