React Router Redirect from index '/' unable to go back to previous website - react-router-v4

Our React app does not have a '/' index route, when '/' is hit it always redirects to '/plan' using this code:
import { Route, Switch, Redirect } from 'react-router-dom';
<Switch>
<Redirect exact from="/" to="/plan" />
<Route path="/plan" component={PlanPage} />
</Switch>
Our problem now is when a user is on url 'google.com' and then goes to our website he is redirected to /plan but then if the user presses the back button in the browser it goes to '/' and then back to '/plan' while we would like for the user to then return to 'google.com'.
Does anybody know a way to accomplish this?

The piece of code I posted turned out to work as expected, the culprit was our custom middleware that made use of 'connected-react-router':
import {LOCATION_CHANGE} from 'connected-react-router';
export default store => next => action => {
const {dispatch} = store;
if (action.type === LOCATION_CHANGE) {
dispatch(someAction());
return next(action);
}else {
return next(action);
}
};
Doing next(action) first and then dispatching our action fixed the problem:
if (action.type === LOCATION_CHANGE) {
next(action);
dispatch(someAction());
}else {
return next(action);
}

Related

How do I set a fallback path/route with vueRouter if the navigation goBack ( < ) takes the user out of myb page?

I'm quite new with Vue, and vue-router. Right now I'm working on a page where we organize tennis matches, I have this component called goBack, it basically checks your browser history entries and take you back to the previous one, similar to the back-arrow in your browser. So I have this behavior which I don't like, and I don't know how to change it => if I set match, pass you the URL, if you enter and click on my goBack Comp it will take you out of my page. I want to avoid that with this logic, if your last browser history entry is not inside my page I want to redirect you to my home.
Here's the goBack component code:
setup(props, { attrs }) {
const router = useRouter()
const { t } = useI18n()
const goBack = (): void => {
if (attrs.onClick) return
if (router.options.history.state.back === null) {
router.replace('/')
} else {
router.back()
}
}
return {
t,
goBack
}
}
})
</script>
Thank you!

Vue Router - redirect to the same route does not triggers afterEach() guard

I'm usign vue-router for a Single Page Application and I've implemented several guards to use into the router itself. I've noticed that if a guard redirects me to the same page where I actually am, the afterEach() function does not tiggers.
Let me give You an example. Imagine you have the following flow:
I'm in the Login page; I use my credentials and I'm redirected to the Home page. Here I click the 'back' button to go back to the Login page, but a guard, that checks if I'm already logged in, redirects me back to the Home page.
In this scenario, the afterEach() function does not triggers.
How can I solve this problem?
This solution won't work for all cases, but it could work for the example you mentioned if you implement an additional manual navigation guard in your router's beforeEach hook.
Assume you have the following code where a user tries to navigate from /home to /login and ends up back on /home without the afterEach hook firing:
router.beforeEach((to, from, next) => {
if (user.isAuthenticated() && to.path === '/login') {
return next('/home')
}
return next()
})
router.afterEach(() => {
// some logic
})
Refactor this to:
const afterEach = function() {
// some logic
}
router.beforeEach((to, from, next) => {
if (user.isAuthenticated() && to.path === '/login') {
if (from.path === '/home') {
afterEach()
}
return next('/home')
}
return next()
})
router.afterEach(afterEach)

Protect routes in NextJS using Firebase Authentication

The route I want to protect: /account
If the user is NOT authenticated, then redirect to /signIn
Having an SSR NextJS project, and working with Firebase authentication, how can I achieve a production battle-tested proper protected routes?
The example provided on NextJS docs is not working right now:
with-firebase-auth
So I submitted an issue:
with-firebase-auth-example-not-working
Add to that that I'm new to NextJs and also, unfortunately, I've never used JWT :( or any sort of backend protected routes cookies/JWT/sessions implementation....Until now that I want/need it.
What sort of workaround I've tried, well, something like this:
import Account from "./Account.js";
import Loading from "./Loading.js";
import { useRequireAuth } from "./use-require-auth.js";
function Account(props) {
const auth = useRequireAuth();
// If auth is null (still fetching data)
// or false (logged out, above hook will redirect)
// then show loading indicator.
if (!auth) {
return <Loading />;
}
return (
<Account auth={auth} />
);
}
// Hook (use-require-auth.js)
import { useEffect } from "react";
import { useAuth } from "./use-auth.js";
import { useRouter } from "./use-router.js";
function useRequireAuth(redirectUrl = '/sigIn'){
const auth = useAuth();
const router = useRouter();
// If auth.user is false that means we're not
// logged in and should redirect.
useEffect(() => {
if (auth.user === false){
router.push(redirectUrl);
}
}, [auth, router]);
return auth;
}
But this is all happening on the client-side....the server is not checking anything.
I'm gonna a post a very basic answer to this. I dunno how you're going to check if a user is authenticated on firebase. My own code uses AWS Cognito for this purpose.
We' are going to put that piece of code at the end of the page. By doing so, if the user is not authenticated we will redirect the user to the sign in page.
export async function isAuthenticated(context) {
// your code to check firebase authentication
// return true if not authenticated, else return false
// Maybe this way
var user = firebase.auth().currentUser;
if (user)
return false;
else
return true;
}
export const getServerSideProps: GetServerSideProps = async (ctx) => {
let shouldRedirect = await isAuthenticated(ctx);
if (shouldRedirect) {
return {
redirect: {
destination: '/sign-in',
permanent: false
}
}
}
return {
props: {}
}
}
export default Account;
That's it. Now the route is protected through SSR.

How to show login page instead of home page

I'm using the default bottom tab navigation app from the expo example
My appnavigator.js looks like this
export default createAppContainer(createSwitchNavigator({
// You could add another route here for authentication.
// Read more at https://reactnavigation.org/docs/en/auth-flow.html
Main: MainTabNavigator,
}));
I want to check if the user is logged in, by checking the asyncstorage loginname value. In the home.js, if there is no loginname, then I want to redirect to the sigin.js page.
I suggest creating a file called initializing.js as a screen, which will be the first entry point in the app and put the logic there.
export default class Initializing extends Component {
async componentDidMount() {
try {
const token = await AsyncStorage.getItem('user_token');
const skipOnBoarding = true;
const authenticated = true;
if (token) await goToHome(skipOnBoarding, authenticated);
else await goToAuth();
SplashScreen.hide();
} catch (err) {
await goToAuth();
SplashScreen.hide();
}
}
render() {
return <View />;
}
}
I worked it out. was very easy.
First do this in the appNavigator.js
import SignIn from '../screens/SignIn'
export default createAppContainer(createSwitchNavigator({
// You could add another route here for authentication.
// Read more at https://reactnavigation.org/docs/en/auth-flow.html
Main: MainTabNavigator,
SignIn: SignIn // signIn is the login page
}));
Next, at the logic where you check for user logined do something like this.
AsyncStorage.getItem('loginname').then((value) => {
console.log(value)
this.props.navigation.navigate('SignIn')
})
the prop this.props.navigation.navigate is automatically avaiable in every stack, you dont need to pass it around to use it

React-navigation: Deep linking with authentication

I am building a mobile app with react-native and the react-navigation library for managing the navigation in my app. Right now, my app looks something like that:
App [SwitchNavigator]
Splash [Screen]
Auth [Screen]
MainApp [StackNavigator]
Home [Screen] (/home)
Profile [Screen] (/profile)
Notifications [Screen] (/notifications)
I have integrated Deep Linking with the patterns above for the screens Home, Profile and Notifications, and it works as expected. The issue I am facing is how to manage my user's authentication when using a deep link. Right now whenever I open a deep link (myapp://profile for instance) the app takes me on the screen whether or not I am authenticated. What I would want it to do is to check before in AsyncStorage if there is a userToken and if there isn't or it is not valid anymore then just redirect on the Auth screen.
I set up the authentication flow in almost exactly the same way as described here. So when my application starts the Splash screen checks in the user's phone if there is a valid token and sends him either on the Auth screen or Home screen.
The only solution I have come up with for now is to direct every deep link to Splash, authentify my user, and then parse the link to navigate to the good screen.
So for example when a user opens myapp://profile, I open the app on Splash, validate the token, then parse the url (/profile), and finally redirect either to Auth or Profile.
Is that the good way to do so, or does react-navigation provide a better way to do this ? The Deep linking page on their website is a little light.
Thanks for the help !
My setup is similar to yours. I followed Authentication flows · React Navigation and SplashScreen - Expo Documentation to set up my Auth flow, so I was a little disappointed that it was a challenge to get deep links to flow through it as well. I was able to get this working by customizing my main switch navigator, the approach is similar to what you stated was the solution you have for now. I just wanted to share my solution for this so there’s a concrete example of how it’s possible to get working. I have my main switch navigator set up like this (also I’m using TypeScript so ignore the type definitions if they are unfamiliar):
const MainNavigation = createSwitchNavigator(
{
SplashLoading,
Onboarding: OnboardingStackNavigator,
App: AppNavigator,
},
{
initialRouteName: 'SplashLoading',
}
);
const previousGetActionForPathAndParams =
MainNavigation.router.getActionForPathAndParams;
Object.assign(MainNavigation.router, {
getActionForPathAndParams(path: string, params: any) {
const isAuthLink = path.startsWith('auth-link');
if (isAuthLink) {
return NavigationActions.navigate({
routeName: 'SplashLoading',
params: { ...params, path },
});
}
return previousGetActionForPathAndParams(path, params);
},
});
export const AppNavigation = createAppContainer(MainNavigation);
Any deep link you want to route through your auth flow will need to start with auth-link, or whatever you choose to prepend it with. Here is what SplashLoading looks like:
export const SplashLoading = (props: NavigationScreenProps) => {
const [isSplashReady, setIsSplashReady] = useState(false);
const _cacheFonts: CacheFontsFn = fonts =>
fonts.map(font => Font.loadAsync(font as any));
const _cacheSplashAssets = () => {
const splashIcon = require(splashIconPath);
return Asset.fromModule(splashIcon).downloadAsync();
};
const _cacheAppAssets = async () => {
SplashScreen.hide();
const fontAssetPromises = _cacheFonts(fontMap);
return Promise.all([...fontAssetPromises]);
};
const _initializeApp = async () => {
// Cache assets
await _cacheAppAssets();
// Check if user is logged in
const sessionId = await SecureStore.getItemAsync(CCSID_KEY);
// Get deep linking params
const params = props.navigation.state.params;
let action: any;
if (params && params.routeName) {
const { routeName, ...routeParams } = params;
action = NavigationActions.navigate({ routeName, params: routeParams });
}
// If not logged in, navigate to Auth flow
if (!sessionId) {
return props.navigation.dispatch(
NavigationActions.navigate({
routeName: 'Onboarding',
action,
})
);
}
// Otherwise, navigate to App flow
return props.navigation.navigate(
NavigationActions.navigate({
routeName: 'App',
action,
})
);
};
if (!isSplashReady) {
return (
<AppLoading
startAsync={_cacheSplashAssets}
onFinish={() => setIsSplashReady(true)}
onError={console.warn}
autoHideSplash={false}
/>
);
}
return (
<View style={{ flex: 1 }}>
<Image source={require(splashIconPath)} onLoad={_initializeApp} />
</View>
);
};
I create the deep link with a routeName query param, which is the name of the screen to navigate to after the auth check has been performed (you can obviously add whatever other query params you need). Since my SplashLoading screen handles loading all fonts/assets as well as auth checking, I need every deep link to route through it. I was facing the issue where I would manually quit the app from multitasking, tap a deep link url, and have the app crash because the deep link bypassed SplashLoading so fonts weren’t loaded.
The approach above declares an action variable, which if not set will do nothing. If the routeName query param is not undefined, I set the action variable. This makes it so once the Switch router decides which path to take based on auth (Onboarding or App), that route gets the child action and navigates to the routeName after exiting the auth/splash loading flow.
Here’s an example link I created that is working fine with this system:
exp://192.168.1.7:19000/--/auth-link?routeName=ForgotPasswordChange&cacheKey=a9b3ra50-5fc2-4er7-b4e7-0d6c0925c536
Hopefully the library authors will make this a natively supported feature in the future so the hacks aren’t necessary. I'd love to see what you came up with as well!
On my side I achieved this without having to manually parse the route to extract path & params.
Here are the steps:
getting the navigation action returned by: getActionForPathAndParams
passing the navigation action to the Authentication view as param
then when the authentication succeed or if the authentication is already ok I dispatch the navigation action to go on the intended route
const previousGetActionForPathAndParams = AppContainer.router.getActionForPathAndParams
Object.assign(AppContainer.router, {
getActionForPathAndParams(path: string, params: NavigationParams) {
const navigationAction = previousGetActionForPathAndParams(path, params)
return NavigationActions.navigate({
routeName: 'Authentication',
params: { navigationAction }
})
}
})
Then In the Authentication view:
const navigationAction = this.navigation.getParam('navigationAction')
if (navigationAction)
this.props.navigation.dispatch(navigationAction)
I ended up using a custom URI to intercept the deeplink launch, and then passing those params to the intended route. My loading screen handles the auth check.
const previousGetActionForPathAndParams = AppContainer.router.getActionForPathAndParams
Object.assign(AppContainer.router, {
getActionForPathAndParams (path, params) {
if (path === 'auth' && params.routeName && params.userId ) {
// returns a profile navigate action for myApp://auth?routeName=chat&userId=1234
return NavigationActions.navigate({
routeName: 'Loading',
params: { ...params, path },
})
}
return previousGetActionForPathAndParams(path, params)
},
})
https://reactnavigation.org/docs/en/routers.html#handling-custom-uris
Then, in my Loading Route, I parse the params like normal but then route to the intended location, passing them once again.
const userId = this.props.navigation.getParam('userId')
https://reactnavigation.org/docs/en/params.html
I found an easier way, i'm maintaining the linking in a separate file and importing it in the main App.js
linking.js
const config = {
screens: {
Home:'home',
Profile:'profile,
},
};
const linking = {
prefixes: ['demo://app'],
config,
};
export default linking;
App.js
& during login I keep the token inside async storage and when user logs out the token is deleted. Based on the availability of token i'm attaching the linking to navigation and detaching it using state & when its detached it falls-back to SplashScreen.
Make sure to set initialRouteName="SplashScreen"
import React, {useState, useEffect} from 'react';
import {Linking} from 'react-native';
import AsyncStorage from '#react-native-async-storage/async-storage';
import {createStackNavigator} from '#react-navigation/stack';
import {NavigationContainer} from '#react-navigation/native';
import linking from './utils/linking';
import {Home, Profile, SplashScreen} from './components';
const Stack = createStackNavigator();
// This will be used to retrieve the AsyncStorage String value
const getData = async (key) => {
try {
const value = await AsyncStorage.getItem(key);
return value != null ? value : '';
} catch (error) {
console.error(`Error Caught while getting async storage data: ${error}`);
}
};
function _handleOpenUrl(event) {
console.log('handleOpenUrl', event.url);
}
const App = () => {
const [isLoggedIn, setIsLoggedIn] = useState(false);
useEffect(() => {
// Checks if the user is logged in or not, if not logged in then
// the app prevents the access to deep link & falls back to splash screen.
getData('studentToken').then((token) => {
if (token === '' || token === undefined) setIsLoggedIn(false);
else setIsLoggedIn(true);
});
Linking.addEventListener('url', _handleOpenUrl);
return () => {
Linking.removeEventListener('url', _handleOpenUrl);
};
}, []);
return (
//linking is enabled only if the user is logged in
<NavigationContainer linking={isLoggedIn && linking}>
<Stack.Navigator
initialRouteName="SplashScreen"
screenOptions={{...TransitionPresets.SlideFromRightIOS}}>
<Stack.Screen
name="SplashScreen"
component={SplashScreen}
options={{headerShown: false}}
/>
<Stack.Screen
name="Home"
component={Home}
options={{headerShown: false, gestureEnabled: false}}
/>
<Stack.Screen
name="Profile"
component={Profile}
options={{headerShown: false, gestureEnabled: false}}
/>
</Stack.Navigator>
</NavigationContainer>
);
};
export default App;
When a logged in user opens the deep link from notification then it will take him to the respective deep linked screen, if he's not logged in then it will open from splash screen.
I created a package for Auto Deep linking with Authentication Flow.
You can try it. auth-linking https://github.com/SohelIslamImran/auth-linking
auth-linking
Auto Deep linking with Authentication Flow
Deep linking is very easy to use with authentication. But some people take it hard way. So this package will help you to achieve the easiest way to handle Deep linking with Authentication Flow.
Installation
npm install auth-linking
yarn add auth-linking
Usage
AuthLinkingProvider
You need to wrap your app with AuthLinkingProvider.
import AuthLinkingProvider from "auth-linking";
...
const App = () => {
return (
<AuthLinkingProvider onAuthChange={onAuthChange}>
{/* Your app components */}
</AuthLinkingProvider>
);
};
export default App;
onAuthChange prop
You need to provide an onAuthChange prop to AuthLinkingProvider. This a function that should return a promise with the user or truthy value (if logged in) and null or falsy (if the user is not logged in).
const onAuthChange = () => {
return new Promise((resolve, reject) => {
onAuthStateChanged(auth, resolve, reject);
});
};
...
<AuthLinkingProvider onAuthChange={onAuthChange}>
useAutoRedirectToDeepLink
Call this hook inside a screen that will render after all auth flow is completed. So this hook will automatically redirect to the deep link through which the app is opened.
import { useAutoRedirectToDeepLink } from "auth-linking";
...
const Home = () => {
useAutoRedirectToDeepLink()
return (
<View>{...}</View>
);
};
All done.