How to redirect to login page for restricted pages in next.js?

Viewed 24238

In the udemy tutorial The Complete React Developer Course the additional package history was used to get the router outside a component for redirecting when the user is not authenticated:

/* AppRouter.js */
import createHistory from "history/createBrowserHistory";

const AppRouter = () => (
    <Router history={history}>
      <div>
        <Switch>
            <PublicRoute path="/" component={LoginPage} exact={true} />
            <PrivateRoute
                path="/dashboard"
                component={ExpenseDashboardPage}
            />
            <PrivateRoute path="/create" component={AddExpensePage} />
            <PrivateRoute path="/edit/:id" component={EditExpensePage} />
            <Route component={NotFoundPage} />
        </Switch>
      </div>
    </Router>
);

export default AppRouter;



/* PrivateRouter.js */
import React from "react";
import { connect } from "react-redux";
import { Route, Redirect } from "react-router-dom";
import Header from "../components/Header";

export const PrivateRoute = ({
    isAuthenticated,
    component: Component,
    ...rest
}) => (
    <Route
        {...rest}
        component={(props) =>
            isAuthenticated ? (
                <div>
                    <Header />
                    <Component {...props} />
                </div>
            ) : (
                <Redirect to="/" />
            )
        }
    />
);

const mapStateToProps = (state) => ({
    isAuthenticated: !!state.auth.uid
});

export default connect(mapStateToProps)(PrivateRoute);

The code for app.js is as follows:

/* app.js */
import AppRouter, { history } from "./routers/AppRouter";

const store = configureStore();

const jsx = (
    <Provider store={store}>
        <AppRouter />
    </Provider>
);

firebase.auth().onAuthStateChanged((user) => {
    if (user) {
        store.dispatch(login(user.uid));
        store.dispatch(startSetExpenses()).then(() => {
            if (history.location.pathname === "/") {
                history.push("/dashboard");
            }
        });
    } else {
        store.dispatch(logout());
        history.push("/");
    }
});

ReactDOM.render(jsx, document.getElementById("app"));

My question now is, how can I achieve this in my next.js project? I have tried a kind of similar way in the _app.js file. Can I accomplish this behavior somehow with the getInitialProps routine? In the zeit/next example with-firebase-authentication I have seen a combination of getInitialProps and componentDidMount, but was not successful to implement it in this way.

import withRedux from 'next-redux-wrapper';
import App from 'next/app';
import React from 'react';
import { Provider } from 'react-redux';
import { firebase } from '../src/firebase/firebase';
import reduxStore from '../src/redux/store';

class MyApp extends App {
  static async getInitialProps({ Component, ctx }) {
    firebase.auth().onAuthStateChanged((user) => {
      if (user) {
        console.log('log in');
      } else {
        if (ctx.isServer && ctx.req.url !== '/login') {
          ctx.res.writeHead(302, { Location: '/login' });
          ctx.res.end();
        }
        console.log('log out');
      }
    });

    return {
      pageProps: Component.getInitialProps
        ? await Component.getInitialProps(ctx)
        : {}
    };
  }

  render() {
    const { Component, pageProps, store } = this.props;

    return (
      <Provider store={store}>
        <Component {...pageProps} />
      </Provider>
    );
  }
}

// firebase.auth().onAuthStateChanged((user) => {
//   if (user) {
//     console.log('log in');
//   } else {
//     // router instance not accessible!
//     Router.push("/login");
//     console.log('log out');
//   }
// });

export default withRedux(reduxStore, { debug: false })(MyApp);
1 Answers
import Router from "next/router";

export function redirectUser(ctx, location) {
  if (ctx.req) {
    ctx.res.writeHead(302, { Location: location });
    ctx.res.end();
  } else {
    Router.push(location);
  }
}

class MyApp extends App {
  static async getInitialProps({ Component, ctx }) {
    const { token } = parseCookies(ctx);

    let pageProps = {};

    if (Component.getInitialProps) {
      pageProps = await Component.getInitialProps(ctx);
    }

    // Restrict unauthorized user
    if (!token) {
      const isProtectedRoute =
        ctx.pathname === "/account" || ctx.pathname === "/create";

      if (isProtectedRoute) {
        redirectUser(ctx, "/signin");
      }
    }
    else {
      try {
         // Restrict user based on role on certain page
        const user;
        const isRoot = user.role === "root";
        const isAdmin = user.role === "admin";
        const isNotPermitted =
          !(isRoot || isAdmin) && ctx.pathname === "/create";

        if (isNotPermitted) {
          redirectUser(ctx, "/");
        }

        pageProps.user = user;
      }
      catch (err) {
        console.error("Error getting current user", err);

        destroyCookie(ctx, "token");
        redirectUser(ctx, "/signin");
      }
    }

    return { ...pageProps };
  }
}
Related