How to authenticate user in gatsby - javascript

I have followed this tutorial to implement authentication in my gatsby project. The problem is I have first setup the project and the routing is made from the pages folder and then I have implemented the above auth code but it still taking the routes from the pages folder and not from the app.js file. Could someone please help how can I route my components from the app.js instead of using from pages folder.
This is my gatsby-nodejs file
// Implement the Gatsby API “onCreatePage”. This is
// called after every page is created.
exports.onCreatePage = async ({ page, actions }) => {
const { createPage } = actions
// page.matchPath is a special key that's used for matching pages
// only on the client.
if (page.path.match(/^\/app/)) {
page.matchPath = "/app/*"
// Update the page.
createPage(page)
}
}
here is src/pages.app.js
import React from "react"
import { Router } from "#reach/router"
import Layout from "../components/layout"
import Home from '../components/dashboard/home/container'
import Login from '../components/marketing/home/pulsemetrics'
import { isLoggedIn } from "../services/auth"
console.log('vvvvvvvvvvvvvvvvvvvvv')
const PrivateRoute = ({ component: Component, location, ...rest }) => {
console.log('hjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiii')
if (!isLoggedIn() && location.pathname !== `/app/login`) {
// If the user is not logged in, redirect to the login page.
navigate(`/app/login`)
return null
}
return <Component {...rest} />
}
const App = () => (
<Layout>
<Router>
<PrivateRoute path="/ddddddddddddddddddd" component={Home} />
<Login path="/" />
</Router>
</Layout>
)
export default App

The paths that you have in your App.js should have /app/ prepended in front of them since your PrivateRoute logic uses that to check for a login. Furthermore what your gatsby-node.js file is really saying is that for routes starting with app it should create a new page. Your src/pages/app.js has the task to define how these pages should be created (since they won't be the usual generated static pages by gatsby)
import React from "react"
import { Router } from "#reach/router"
import Layout from "../components/layout"
import Home from '../components/dashboard/home/container'
import Login from '../components/marketing/home/pulsemetrics'
import { isLoggedIn } from "../services/auth"
console.log('vvvvvvvvvvvvvvvvvvvvv')
const PrivateRoute = ({ component: Component, location, ...rest }) => {
console.log('hjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiii')
if (!isLoggedIn() && location.pathname !== `/app/login`) {
// If the user is not logged in, redirect to the login page.
navigate(`/app/login`)
return null
}
return <Component {...rest} />
}
const App = () => (
<Layout>
<Router>
<PrivateRoute path="/app/home" component={Home} />
<Login path="/app/login" />
</Router>
</Layout>
)
export default App
Read the gatsby client-only routes documentation for reference or have a look at this github issue

Related

Solidjs router does not render

I already searched a lot and could not find an answer. In my SolidJs app, the second route is not redered in root element:
import { Routes, Route, useLocation } from "solid-app-router"
import { useNavigate } from 'solid-app-router';
const Login = lazy(() => import("./pages/login"));
const Operation = lazy(() => import("./pages/operation"));
export default function App() {
const navigate = useNavigate();
const location = useLocation();
onMount(() => {
const token = localStorage.getItem('token');
if (!token && location.pathname !== '/') {
navigate("/", { replace: true });
}
if (token && location.pathname === '/') {
navigate("/operations", { replace: true });
}
});
return (
<Routes>
<Route path='/' component={Login} />
<Route path='/operations' component={Operation} />
</Routes>
)
}
Everything looks OK at component Operation and if I call this component in first route like bellow it work:
<Route path='/' component={Operation} />
The root component should be wrapped in Router component.
I struggled with this for a while and realised that the application needs to make reference to the Routes by means of an <A> tag.
If you look at this part of the docs, it mentions that these tags include an active class which seems to be used to tell the application to actually bundle this component on build time otherwise by default it is not included.
So if you put Operations somewhere in your homepage like a Navbar, that page/component should bundle in on deployment.
By default though all routes in Routes in dev environment should work so if it is not working in dev environment I would just check that the module is being resolved correctly.

React PrivateRoute auth route

I am working on a basic react auth app, right now the routes /signup and /login work when I run this repo with my .env.local file that contains firebase auth variables.
https://github.com/MartinBarker/react-auth-app
I am trying to make it so that the '/' route that points to Dashboard will only be accessible for a user who is currently signed in, and if a user is not signed in but tries to access the '/' route they will be redirected to the '/login' page.
But whenever I use the route
<PrivateRoute exact path="/" element={Dashboard} />
my chrome devtools console shows a blank page with error messages:
index.tsx:24 Uncaught Error: [PrivateRoute] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>
my PrivateRoute.js looks like this:
// This is used to determine if a user is authenticated and
// if they are allowed to visit the page they navigated to.
// If they are: they proceed to the page
// If not: they are redirected to the login page.
import React from 'react'
import { Navigate, Route } from 'react-router-dom'
import { useAuth } from '../Contexts/AuthContext'
const PrivateRoute = ({ component: Component, ...rest }) => {
// Add your own authentication on the below line.
//const isLoggedIn = AuthService.isLoggedIn()
const { currentUser } = useAuth()
console.log('PrivateRoute currentUser = ', currentUser)
return (
<Route
{...rest}
render={props =>
currentUser ? (
<Component {...props} />
) : (
//redirect to /login if user is not signed in
<Navigate to={{ pathname: '/login'}} />
)
}
/>
)
}
export default PrivateRoute
Im not sure why this error is occurring, any help is appreciated
This behaviour seems to have changed in ReactRouter V6 here is the solution we came up with for a project.
Private route
*Re-creating the users question code
import React from 'react'
import { Navigate, Route } from 'react-router-dom'
import { useAuth } from '../Contexts/AuthContext'
const PrivateRoute = ({ children }) => {
// Add your own authentication on the below line.
//const isLoggedIn = AuthService.isLoggedIn()
const { currentUser } = useAuth()
console.log('PrivateRoute currentUser = ', currentUser)
return (
<>
{
currentUser ? (
children
) : (
//redirect to /login if user is not signed in
<Navigate to={{ pathname: '/login'}} />
)
}
</>
)
}
export default PrivateRoute
Typescript
*Our actual code implementation of this issue
const PrivateRoute: React.FC = ({ children }) => {
const navigate = useNavigate();
const { isAuthenticated, isAuthLoading } = useAuth();
const { user, isLoadingUser } = useContext(UserContext);
// Handle users which are not authenticated
// For example redirect users to different page
// Show loader if token is still being retrieved
if (isAuthLoading || isLoadingUser) {
// TODO: show full page loader
return (
<div>Loading...</div>
);
}
// Proceed with render if user is authenticated
return (
<>
{children}
</>
);
};
Router
<Router>
<Routes>
<Route
path={routes.user.accountSignup.path}
element={
<PrivateRoute>
<AccountSignup />
</PrivateRoute>
}
/>
</Routes>
</Router>

How do you use react-router-dom to redirect user when token has expired?

I have a React application that accesses a Flask API. To access some API routes, the user needs to log in. I am using Axios to do the requests. Then, he receives a token which is stored in the local storage. When this token expires and the user makes another request, I want to redirect him to the login page. However, I don't know how I would do it.
I am treating API request errors with Axios response interceptor. It removes the token from the local storage and then should redirect the user to the login page. Since I am using functional components, I could not find an example that fits well (besides downloading another package called history).
I have tried to use the 'useHistory' hook and Redirect from react-router-dom (with a proper BrowserRouter set up), but it doesn't work.
api.js
import axios from "axios"
import { RemoveAuth } from "./Auth"
export const api = axios.create({
baseURL: "http://localhost:5000/api/",
timeout: 15000,
})
// more code
api.interceptors.response.use(null, (error) => {
if(error.response.status === 401){
RemoveAuth();
}
return error;
});
Auth.js
import { useHistory } from "react-router-dom"
export const RemoveAuth = () => {
let history = useHistory()
localStorage.clear();
history.push('/login')
}
routes.js
import React from "react";
import { BrowserRouter, Switch, Route } from "react-router-dom";
import PrivateRoutes from "./PrivateRoutes";
import Dashboard from "../pages/dashboard";
import Login from "../pages/login";
import Logout from "../pages/logout";
const Routes = () => (
<BrowserRouter>
<Switch>
<PrivateRoutes exact path="/dashboard" component={Dashboard} />
<PrivateRoutes exact path="/logout" component={Logout} />
<Route exact path="/login" component={Login} />
</Switch>
</BrowserRouter>
);
PrivateRoutes.js
import React from "react";
import { Route, Redirect } from "react-router-dom";
import { AuthLogin } from "../services/Auth";
const PrivateRoutes = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={() => (AuthLogin() ? <Redirect to="/login" /> : <Component />)}
/>
);
export default PrivateRoutes;
Thanks in advance for the help!
The simplest thing to do is to create your own history object. Something like this:
import { createBrowserHistory } from 'history';
const history = createBrowserHistory();
export default history;
Then in your provider pass in your custom history object:
import { Router } from 'react-router-dom'
import history from './utils/history'
ReactDOM.render(
<Router history={history}>
<App />
</Router>
document.getElementById('root')
);
This allows you to utilize your history in non-component code. Just import your history object into your Auth.js file and use it:
import { history } from './utils/history'
export const RemoveAuth = () => {
localStorage.clear();
history.push('/login')
}
As an added bonus, now your history lives in a place that is easily mock-able, so creating testing around it is more straightforward. You can find more information about creating your own custom history object in the docs.

How to integrate dynamic routes in Docusaurus with react-router

I have a website made with Docusaurus v2 that currently contains documentation. However, I would like to add a page of a list of workflows where if a workflow in the list is clicked, the user would be shown a page of additional details of that workflow. For now it seems docusaurus.config seems to be handling most of the routing, but is there a way I can add a dynamic route like /workflows/:id? I made a separate standalone app which had a Router object and it worked if my App.js looks like this:
// App.js
import Navigation from './Navigation'
import {BrowserRouter as Router, Switch, Route} from 'react-router-dom';
function App() {
return (
<Router>
<Navigation />
<Switch>
<Route path="/" exact component={Home}></Route>
<Route path="/workflows" exact component={Workflows}></Route>
<Route path="/workflows/:id" component={WorkflowItem}></Route>
</Switch>
</Router>
)
}
Is it possible to add the Router somewhere in Docusaurus?
Thanks!
I solved this by creating a simple plugin to add my own custom routes. Documentation here.
Let's call the plugin plugin-dynamic-routes.
// {SITE_ROOT_DIR}/plugin-dynamic-routes/index.js
module.exports = function (context, options) {
return {
name: 'plugin-dynamic-routes',
async contentLoaded({ content, actions }) {
const { routes } = options
const { addRoute } = actions
routes.map(route => addRoute(route))
}
}
}
// docusaurus.config.js
const path = require('path')
module.exports = {
// ...
plugins: [
[
path.resolve(__dirname, 'plugin-dynamic-routes'),
{ // this is the options object passed to the plugin
routes: [
{ // using Route schema from react-router
path: '/workflows',
exact: false, // this is needed for sub-routes to match!
component: '#site/path/to/component/App'
}
]
}
],
],
}
You may be able to use the above method to configure sub-routes as well but I haven't tried it. For the custom page, all you need is the Switch component (you are technically using nested routes at this point). The Layout component is there to integrate the page into the rest of the Docusaurus site.
// App.js
import React from 'react'
import Layout from '#theme/Layout'
import { Switch, Route, useRouteMatch } from '#docusaurus/router'
function App() {
let match = useRouteMatch()
return (
<Layout title="Page Title">
<Switch>
<Route path={`${match.path}/:id`} component={WorkflowItem} />
<Route path={match.path} component={Workflows} />
</Switch>
</Layout>
)
}

MSAL authentication and authrization with React.js

I am fairly new to React and trying to implement Single Sign On Authentication in my React App.
Objectives:
Provide a login page where the user can enter their email address
On click of Sign-in user get the SSO popup (based Azure AD) to accept the terms and sign-in
Call graph API to retrieve user details (email ID, etc.)
Retrieve the sign in token and store in browser cache (localStorage) and use it for subsequent URL accesses (React routes).
I have come across MSAL (https://github.com/AzureAD/microsoft-authentication-library-for-js) which seems to be useful for this.
What I have tried:
Based on the MSDN docs: https://learn.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-javascript-spa, I have registered my React SPA app in the Azure and got the client ID.
I have created a single js file (Auth.js) to handle sign-in, token generation and graph API call as mentioned in the docs: https://learn.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-javascript-spa#use-the-microsoft-authentication-library-msal-to-sign-in-the-user
In my index.js I have configured the routes:
ReactDOM.render(<MuiThemeProvider theme={theme}>
<Router>
<Switch>
<Route path="/" exact component={Projects}/>
<Route path="/projects" exact component={Projects}/>
<Route path="/admin" exact component={Admin}/>
<Route path="/projectstages/:projectNumber" exact component={ProjectStages}/>
<Route path="/formspage" exact component={FormsPage}/>
<Route path="/users" exact component={UserManagement}/>
</Switch>
</Router>
</MuiThemeProvider>, document.getElementById('root'));
These routes (components) gets rendered within the main App.jsx component:
class App extends Component {
render(){
return(
<div className="App">
{this.props.children}
</div>
);
}
}
How do I integrate this within my React app so that only authenticated users can access the React routes along with the objectives I mentioned above? Please let me know if I can provide more details or explain more about this.
This is usually achieved using higher-order-components.
The idea is, when you load a page that requires authentication, you call an api to get authentication using access token stored from your cookies or whatever storage you use. Then you need to wrap your protected routes to a HOC that checks the authentication data.
import React, {useState, useContext, useRef, createContext } from 'react'
const AuthContext = createContext(null)
export const withAuth = (requireAuth = true) => (WrappedComponent) => {
function Auth(props) {
const isMounted = useRef(false);
// this is the authentication data i passed from parent component
// im just using
const { loading, error, auth } = useContext(AuthContext);
useEffect(() => {
isMounted.current = true;
}, []);
if (!isMounted.current && loading && requireAuth !== 'optional') {
return (<span>Loading...</span>);
}
if ((!auth || error) && requireAuth === true) {
return (<Redirect to="/login" />);
} if (auth && requireAuth === false) {
return (<Redirect to="/" />);
}
return (
<WrappedComponent {...props} />
);
}
return Auth;
};
export function AuthenticationProvider(props) {
const [auth, setAuth] = useState()
const [error, setErr] = usetState()
const [loading, setLoading] = useState(true)
useEffect(() => {
// get authentication here
api.call('/auth')
.then(data => {
setAuth(data)
setLoading(false)
})
.catch(err => {
setLoading(false)
setErr(err)
})
})
return (
<AuthContext.Provider value={{ auth, error, loading }}>
{children}
</AuthContext.Provider>
)
}
Then you can wrap your App with the Authentication Provider
<AuthenticationProvider>
<App/>
</AuthenticationProvider>
And for each of the pages, you use the HOC like this
function ProtectedPage(props){
// your code here
}
export default withAuth(true)(ProtectedPage)
I'd like to recommend to use package for this:
https://www.npmjs.com/package/react-microsoft-login
Install:
yarn add react-microsoft-login
# or
npm i react-microsoft-login
Import and configure component:
import React from "react";
import MicrosoftLogin from "react-microsoft-login";
export default props => {
const authHandler = (err, data) => {
console.log(err, data);
};
return (
<MicrosoftLogin clientId={YOUR_CLIENT_ID} authCallback={authHandler} />
);
};

Categories