Experiencing redirect loop with a protected route in Gatsby using Auth0 [duplicate] - redirect

This question already has an answer here:
Login doesn't show up in Gatsby using Auth0, withAuthenticationRequired
(1 answer)
Closed 1 year ago.
Note: This question is not a duplicate, I'm not sure why anyone is thinking that...
I’m having issue with implementing protected pages(routes) in Gatsby with Auth0
Currently, when I point the browser to localhost:8000/user/protectedpage, it goes to the login screen, and after a successful login, it comes back to that route, and the browser seems to be stuck on a loop loading between two routes.
When I tested with this, the page was doing a indefinite redirect loop while showing "Redirect..." on the page:
export default withAuthenticationRequired(ProtectedPage, {
onRedirecting: () => <div>Redirecting...</div>
});
redirectUri in Auth0Provider is set to redirectUri={window.location.origin + '/user'}
Allowed Callback URLs in the auth0 admin page, is set to, http://localhost:8000/user
If I change these routes to window.location.origin and http://localhost:8000/, then after a successful login, it’ll redirect to that page and stay there.
I need it to redirect to where it was trying to go to instead.
As in, if I navigate to localhost:8000/user/protectedpage, then after logging in, it should redirect to that route and load that page successfully, instead of being stuck in a loop like mentioned earlier.
Here are some codes:
// File structure
src
> pages
> user
> index.js
> protectedpage
index.js
gatsby-browser.js
// gatsby-browser.js
import React from 'react';
import { Auth0Provider } from '#auth0/auth0-react';
import { navigate } from 'gatsby';
const onRedirectCallback = (appState) => {
navigate(appState?.returnTo || '/', { replace: true });
};
export const wrapRootElement = ({ element }) => {
return (
<Auth0Provider
domain={process.env.AUTH0_DOMAIN}
clientId={process.env.AUTH0_CLIENTID}
redirectUri={window.location.origin + '/user'}
onRedirectCallback={onRedirectCallback}
>
{element}
</Auth0Provider>
);
};
// protectedpage.js
import React from 'react';
import { withAuthenticationRequired } from '#auth0/auth0-react';
const ProtectedPage = () => {
return (
<div>
Protected Page
</div>
);
};
export default withAuthenticationRequired(ProtectedPage);
// auth0 Application URIs
Allowed Callback URLs
http://localhost:8000/user

I'm not sure about your full implementation but to me, the fact that it gets stuck in an infinite loop could be related to the fact that you are replacing the history by removing the last visited page in:
navigate(appState?.returnTo || '/', { replace: true });
In addition, the callback is receiving an appState otherwise, it makes the history replacing but you are never providing it at (onRedirectCallback={onRedirectCallback}):
// gatsby-browser.js
import React from 'react';
import { Auth0Provider } from '#auth0/auth0-react';
import { navigate } from 'gatsby';
const onRedirectCallback = (appState) => {
navigate(appState?.returnTo || '/', { replace: true });
};
export const wrapRootElement = ({ element }) => {
return (
<Auth0Provider
domain={process.env.AUTH0_DOMAIN}
clientId={process.env.AUTH0_CLIENTID}
redirectUri={window.location.origin + '/user'}
onRedirectCallback={onRedirectCallback} //<-- here you are not providing an appState
>
{element}
</Auth0Provider>
);
};

Related

Vue routing redirect from path '/' to another page if a condition is met

I have a Login page that is bound to the path '/'. And if I login, I go to '/home'. The problem is that if I manually type the path '/' in the url of the browser, I go to the login page again. Is there a way to redirect me to '/home' if am already logged in? I know that I can use redirect like in the code below, but I am not sure where should I declare the a variable called isLoggedIn and how to use it. Or maybe it could be better do it in the <script> section of the Login page.
{
path: '/',
name: 'Login',
component: Login,
redirect: to => {
return {path: '/orders'}
}
}
What you are looking for is called navigation guard :
you will find more information in the vue router documentation https://router.vuejs.org/guide/advanced/navigation-guards.html#global-before-guards
And here is a quick example extracted from this page
router.beforeEach(async (to, from) => {
if (
// make sure the user is authenticated
!isAuthenticated &&
// ❗️ Avoid an infinite redirect
to.name !== 'Login'
) {
// redirect the user to the login page
return { name: 'Login' }
}
})

Mocking authentication when testing MSAL React Apps

Our app is wrapped in the MSAL Authentication Template from #azure/msal-react in a standard way - key code segments are summarized below.
We would like to test app's individual components using react testing library (or something similar). Of course, when a React component such as SampleComponentUnderTest is to be properly rendered by a test as is shown in the simple test below, it must be wrapped in an MSAL component as well.
Is there a proper way to mock the MSAL authentication process for such purposes? Anyway to wrap a component under test in MSAL and directly provide test user's credentials to this component under test? Any references to useful documentation, blog posts, video, etc. to point us in the right direction would be greatly appreciated.
A Simple test
test('first test', () => {
const { getByText } = render(<SampleComponentUnderTest />);
const someText = getByText('A line of text');
expect(someText).toBeInTheDocument();
});
Config
export const msalConfig: Configuration = {
auth: {
clientId: `${process.env.REACT_APP_CLIENT_ID}`,
authority: `https://login.microsoftonline.com/${process.env.REACT_APP_TENANT_ID}`,
redirectUri:
process.env.NODE_ENV === 'development'
? 'http://localhost:3000/'
: process.env.REACT_APP_DEPLOY_URL,
},
cache: {
cacheLocation: 'sessionStorage',
storeAuthStateInCookie: false,
},
system: {
loggerOptions: {
loggerCallback: (level, message, containsPii) => {
if (containsPii) {
return;
}
switch (level) {
case LogLevel.Error:
console.error(message);
return;
case LogLevel.Info:
console.info(message);
return;
case LogLevel.Verbose:
console.debug(message);
return;
case LogLevel.Warning:
console.warn(message);
return;
default:
console.error(message);
}
},
},
},
};
Main app component
const msalInstance = new PublicClientApplication(msalConfig);
<MsalProvider instance={msalInstance}>
{!isAuthenticated && <UnauthenticatedHomePage />}
{isAuthenticated && <Protected />}
</MsalProvider>
Unauthenticated component
const signInClickHandler = (instance: IPublicClientApplication) => {
instance.loginRedirect(loginRequest).catch((e) => {
console.log(e);
});
};
<UnauthenticatedTemplate>
<Button onClick={() => signInClickHandler(instance)}>Sign in</Button>
</UnauthenticatedTemplate>
Protected component
<MsalAuthenticationTemplate
interactionType={InteractionType.Redirect}
errorComponent={ErrorComponent}
loadingComponent={LoadingComponent}
>
<SampleComponentUnderTest />
</MsalAuthenticationTemplate>
I had the same issue as you regarding component's test under msal-react.
It took me a couple of days to figure out how to implement a correct auth mock.
That's why I've created a package you will find here, that encapsulates all the boilerplate code : https://github.com/Mimetis/msal-react-tester
Basically, you can do multiple scenaris (user is already logged, user is not logged, user must log in etc ...) in a couple of lines, without having to configure anything and of course without having to reach Azure AD in any cases:
describe('Home page', () => {
let msalTester: MsalReactTester;
beforeEach(() => {
// new instance of msal tester for each test
msalTester = new MsalReactTester();
// spy all required msal things
msalTester.spyMsal();
});
afterEach(() => {
msalTester.resetSpyMsal();
});
test('Home page render correctly when user is logged in', async () => {
msalTester.isLogged();
render(
<MsalProvider instance={msalTester.client}>
<MemoryRouter>
<Layout>
<HomePage />
</Layout>
</MemoryRouter>
</MsalProvider>,
);
await msalTester.waitForRedirect();
let allLoggedInButtons = await screen.findAllByRole('button', { name: `${msalTester.activeAccount.name}` });
expect(allLoggedInButtons).toHaveLength(2);
});
test('Home page render correctly when user logs in using redirect', async () => {
msalTester.isNotLogged();
render(
<MsalProvider instance={msalTester.client}>
<MemoryRouter>
<Layout>
<HomePage />
</Layout>
</MemoryRouter>
</MsalProvider>,
);
await msalTester.waitForRedirect();
let signin = screen.getByRole('button', { name: 'Sign In - Redirect' });
userEvent.click(signin);
await msalTester.waitForLogin();
let allLoggedInButtons = await screen.findAllByRole('button', { name: `${msalTester.activeAccount.name}` });
expect(allLoggedInButtons).toHaveLength(2);
});
I am also curious about this, but from a slightly different perspective. I am trying to avoid littering the code base with components directly from msal in case we want to swap out identity providers at some point. The primary way to do this is to use a hook as an abstraction layer such as exposing isAuthenticated through that hook rather than the msal component library itself.
The useAuth hook would use the MSAL package directly. For the wrapper component however, I think we have to just create a separate component that either returns the MsalProvider OR a mocked auth provider of your choice. Since MsalProvider uses useContext beneath the hood I don't think you need to wrap it in another context provider.
Hope these ideas help while you are thinking through ways to do this. Know this isn't a direct answer to your question.

Next js Strapi integration not displaying data

I am trying to build a simple task website to get familiar with full stack development. I am using Next js and Strapi. I have tried all I can think of, but the data from the server just will not display on the frontend. It seems to me that the page loads too soon, before the data has been loaded in. However, I am not a full stack dev and am therefore not sure.
import axios from 'axios';
const Tasks = ({ tasks }) => {
return (
<ul>
{tasks && tasks.map(task => (
<li key={task.id}>{task.name}</li>
))}
</ul>
);
};
export async function getStaticProps() {
const res = await axios.get('http://localhost:1337/tasks');
const data = await res.data;
if (!data) {
return {
notFound: true,
}
} else {
console.log(data)
}
return {
props: { tasks: data },
};
};
export default Tasks;
I had the same issue. You need to call the api from the pages files in the pages folder. I don't know why this is but that's how it works.

Firestack: firestack.auth.signInWithProvider('facebook', token, '') not firing

I wouldn't consider myself a bad developer, but 2 days into trying to get facebook login working with react native and firebase is making me think otherwise!
I have started again from scratch and am trying to implement react-native-firestack. Unfortunately I have hit a road block just trying to get the package setup.
The firestack events seem to work fine (e.g. console.logs from firestack.auth.getCurrentUser() seem to work fine - in that they fire).
Unfortunately the signInWithProvider event doesn't seem to fire. the console.log()s just before it fire, with the correct data being passed. I am hoping I have just missed some little detail, I have tried to copy all the code directly from the relevant tutorials from react-native-firestack and react-native-facebook-login (the login one looks to be running well).
See below for my code:
import React, { Component } from 'react';
import {FBLogin, FBLoginManager} from 'react-native-facebook-login';
import {
AppRegistry,
StyleSheet,
Text,
View
} from 'react-native';
import Firestack from 'react-native-firestack';
import FBLoginView from './src/FBLoginView';
export default class appTest extends Component {
render() {
const firestack = new Firestack();
firestack.auth.listenForAuth((evt) => {
if (!evt.authenticated) {
// There was an error or there is no user
console.log(evt.error);
} else {
// evt.user contains the user details
console.log('User details', evt.user);
}
})
.then(() => console.log('Listening for authentication changes'));
return (
<View style={styles.container}>
<FBLogin
buttonView={<FBLoginView />}
ref={(fbLogin) => { this.fbLogin = fbLogin }}
loginBehavior={FBLoginManager.LoginBehaviors.Native}
permissions={["email","user_friends"]}
onLogin={function(data){
console.log("Logged in!");
let token = data.credentials.token;
firestack.auth.signInWithProvider('facebook', token, '')
.then((user) => {
console.log(user)
}).catch((err) => {
console.error('User signin error', err);
});
}}
onLoginFound={function(e){console.log(e)}}
onLoginNotFound={function(e){console.log(e)}}
onLogout={function(e){console.log(e)}}
onCancel={function(e){console.log(e)}}
onPermissionsMissing={function(e){console.log(e)}}
/>
</View>
);
}
}
AppRegistry.registerComponent('appTest', () => appTest);

react-router > redirect does not work

I have searched on the internet for this topic and I have found many different answer but they just do not work.
I want to make a real redirect with react-router to the '/' path from code. The browserHistory.push('/') code only changes the url in the web browser but the view is not refreshed by browser. I need to hit a refresh manually to see the requested content.
'window.location = 'http://web.example.com:8080/myapp/'' works perfectly but i do not want to hardcode the full uri in my javascript code.
Could you please provide me a working solution?
I use react ^15.1.0 and react-router ^2.4.1.
My full example:
export default class Logout extends React.Component {
handleLogoutClick() {
console.info('Logging off...');
auth.logout(this.doRedirect());
};
doRedirect() {
console.info('redirecting...');
//window.location = 'http://web.example.com:8080/myapp/';
browserHistory.push('/')
}
render() {
return (
<div style={style.text}>
<h3>Are you sure that you want to log off?</h3>
<Button bsStyle="primary" onClick={this.handleLogoutClick.bind(this)}>Yes</Button>
</div>
);
}
}
You can use router.push() instead of using the history. To do so, you can use the context or the withRouter HoC, which is better than using the context directly:
import { withRouter } from 'react-router';
class Logout extends React.Component {
handleLogoutClick() {
console.info('Logging off...');
auth.logout(this.doRedirect());
};
doRedirect() {
this.props.router.push('/') // use the router's push to redirect
}
render() {
return (
<div style={style.text}>
<h3>Are you sure that you want to log off?</h3>
<Button bsStyle="primary" onClick={this.handleLogoutClick.bind(this)}>Yes</Button>
</div>
);
}
}
export default withRouter(Logout); // wrap with the withRouter HoC to inject router to the props, instead of using context
Solution:
AppHistory.js
import { createHashHistory } from 'history';
import { useRouterHistory } from 'react-router';
const appHistory = useRouterHistory(createHashHistory)({
queryKey: false
});
export default appHistory;
Then you can use appHistory from everywhere in your app.
App.js
import appHistory from './AppHistory';
...
ReactDom.render(
<Router history={appHistory} onUpdate={() => window.scrollTo(0, 0)}>
...
</Router>,
document.getElementById('root')
);
Logout.js
import React from 'react';
import appHistory from '../../AppHistory';
import auth from '../auth/Auth';
import Button from "react-bootstrap/lib/Button";
export default class Logout extends React.Component {
handleLogoutClick() {
auth.logout(this.doRedirect());
}
doRedirect() {
appHistory.push('/');
}
render() {
return (
<div style={style.text}>
<h3>Are you sure that you want to log off?</h3>
<Button bsStyle="primary" onClick={this.handleLogoutClick.bind(this)}>Yes</Button>
</div>
);
}
}
this topic helped me a lot:
Programmatically navigate using react router