Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
ReactJS - NextJS - A HOC for wrapping NextJS pages in an authentication check. Checks for getInitialProps on the child component and runs it, so you still get SSR from the page. Also includes a user agent for Material UI.
import React, {Component} from 'react'
import Router from 'next/router'
import AuthService from './AuthService'
export default function withAuth(AuthComponent) {
const Auth = new AuthService('http://localhost')
return class Authenticated extends Component {
static async getInitialProps(ctx) {
// Ensures material-ui renders the correct css prefixes server-side
let userAgent
if (process.browser) {
userAgent = navigator.userAgent
} else {
userAgent = ctx.req.headers['user-agent']
}
// Check if Page has a `getInitialProps`; if so, call it.
const pageProps = AuthComponent.getInitialProps && await AuthComponent.getInitialProps(ctx);
// Return props.
return { ...pageProps, userAgent }
}
constructor(props) {
super(props)
this.state = {
isLoading: true
};
}
componentDidMount () {
if (!Auth.loggedIn()) {
Router.push('/')
}
this.setState({ isLoading: false })
}
render() {
return (
<div>
{this.state.isLoading ? (
<div>LOADING....</div>
) : (
<AuthComponent {...this.props} auth={Auth} />
)}
</div>
)
}
}
}
@th0th

This comment has been minimized.

Copy link

@th0th th0th commented May 13, 2019

Thanks a lot!

@uneak

This comment has been minimized.

Copy link

@uneak uneak commented Aug 9, 2019

Great, thanks !

@patyiscoding

This comment has been minimized.

Copy link

@patyiscoding patyiscoding commented Oct 26, 2019

Thanks, helped me a lot!

@tangnguyenv

This comment has been minimized.

Copy link

@tangnguyenv tangnguyenv commented Nov 4, 2019

hi @whoisryosuke, what if we don't return a React class, we just return a function but we still want to call getInitialProps, any solutions for this case?
In some cases, we want to use React hooks inside returned function

@MrRhodes

This comment has been minimized.

Copy link

@MrRhodes MrRhodes commented Feb 13, 2020

I'm a bit late to the party @tangnguyenv, but for future travellers.... I've done this before.

export default Component => {

    const withThing = props => {
        return <Component {...props} />;
    };

    withThing.getInitialProps = async ctx => {

        return { thing: true };
    };

    return withThing;
};
@orifmilod

This comment has been minimized.

Copy link

@orifmilod orifmilod commented Mar 24, 2020

I'm a bit late to the party @tangnguyenv, but for future travellers.... I've done this before.

export default Component => {

    const withThing = props => {
        return <Component {...props} />;
    };

    withThing.getInitialProps = async ctx => {

        return { thing: true };
    };

    return withThing;
};

where does the props { thing: true } goes to?

@MrRhodes

This comment has been minimized.

Copy link

@MrRhodes MrRhodes commented Mar 25, 2020

where does the props { thing: true } goes to?

const withThing = props => { // << it's the props here...
    return <Component {...props} />;
};
@if1999615

This comment has been minimized.

Copy link

@if1999615 if1999615 commented Apr 30, 2020

Thanks for this! Do you have an example that uses the new getServerSideProps() implementation?

@Pasquariello

This comment has been minimized.

Copy link

@Pasquariello Pasquariello commented May 12, 2020

Ive been having trouble getting a HOC to work like this for pages that use getStaticProps. Any suggestions ?

@nhanpdnguyen

This comment has been minimized.

Copy link

@nhanpdnguyen nhanpdnguyen commented Jun 21, 2020

Thanks man, it helped me alot.

@nhanpdnguyen

This comment has been minimized.

Copy link

@nhanpdnguyen nhanpdnguyen commented Jun 21, 2020

Ive been having trouble getting a HOC to work like this for pages that use getStaticProps. Any suggestions ?

Thanks for this! Do you have an example that uses the new getServerSideProps() implementation?

Haven't tested this yet but would it be just like dealing with getInitialProps ?

static async getServerSideProps(ctx) {
  // Check if Page has a `getServerSideProps`; if so, call it.
  const pageProps = AuthComponent.getServerSideProps && await AuthComponent.getServerSideProps(ctx);
  // Return props.
  return { ...pageProps }
}
@othneildrew

This comment has been minimized.

Copy link

@othneildrew othneildrew commented Jun 26, 2020

Ive been having trouble getting a HOC to work like this for pages that use getStaticProps. Any suggestions ?

Thanks for this! Do you have an example that uses the new getServerSideProps() implementation?

Haven't tested this yet but would it be just like dealing with getInitialProps ?

static async getServerSideProps(ctx) {
  // Check if Page has a `getServerSideProps`; if so, call it.
  const pageProps = AuthComponent.getServerSideProps && await AuthComponent.getServerSideProps(ctx);
  // Return props.
  return { ...pageProps }
}

Unfortunately, this doesn't work because new versions of next now require that getServerSideProps and getStaticProps be exported like so:

export const getStaticProps = () => ({
  props: {
    hello: 'world',
  },
})

The solution proposed would result in an error similar to this -> Error: page /profile getServerSideProps can not be attached to a page's component and must be exported from the page. See more info here: https://err.sh/next.js/gssp-component-member. I'm also in need of a solution and am working on one. Will let you'll know if I come up with something.

@ImranAhmed

This comment has been minimized.

Copy link

@ImranAhmed ImranAhmed commented Aug 5, 2020

@othneildrew Did you ever resolve this? I have hit a similar problem here. auth0/nextjs-auth0#148

@nhanpdnguyen

This comment has been minimized.

Copy link

@nhanpdnguyen nhanpdnguyen commented Aug 6, 2020

Hi, I've just realized, why don't we use this withAuth as a HOC on the component that the page renders, not on the page itself. This way we could use getStaticProps and getServerSideProps as normal.
The withAuth HOC will then only be responsible for authentication check and redirect stuff, no need for getIntitalProps inside since we could declare that on the page.
For example, a page at pages/products.js

import React from 'react'
import ProductList from '@components/ProductList'
import withAuth from '@utils/withAuth'

function ProductsPage(props) {
  const AuthenticatedProductList = withAuth(ProductList);
  return <AuthenticatedProductList {...props} />
}

export async function getStaticProps(context) {
 // getStaticProps stuff
}

export async function getServerSideProps(context) {
 // getServerSideProps stuff
}

ProductsPage.getInitialProps = async (ctx) => {
  // getInitialProps stuff
}

export default ProductsPage;
@moelashmawy

This comment has been minimized.

Copy link

@moelashmawy moelashmawy commented Aug 15, 2020

Hi, I've just realized, why don't we use this withAuth as a HOC on the component that the page renders, not on the page itself. This way we could use getStaticProps and getServerSideProps as normal.
The withAuth HOC will then only be responsible for authentication check and redirect stuff, no need for getIntitalProps inside since we could declare that on the page.
For example, a page at pages/products.js

import React from 'react'
import ProductList from '@components/ProductList'
import withAuth from '@utils/withAuth'

function ProductsPage(props) {
  const AuthenticatedProductList = withAuth(ProductList);
  return <AuthenticatedProductList {...props} />
}

export async function getStaticProps(context) {
 // getStaticProps stuff
}

export async function getServerSideProps(context) {
 // getServerSideProps stuff
}

ProductsPage.getInitialProps = async (ctx) => {
  // getInitialProps stuff
}

export default ProductsPage;

can you provide a full implementation please?

@Sergioamjr

This comment has been minimized.

Copy link

@Sergioamjr Sergioamjr commented Apr 1, 2021

I couldn't use the getServerSideProps method in a HOC, because it is only available on pages. Neither use getInitialProps inside a getServerSideProps, I got a warning in my console.

In my case. I needed a generic function to validate the user's access to a private page with an async call. I solved this by creating a function that receives the context and a callback.

//util.js
const validateRoute = (context) => async (callback) => {
  const { id, pathname } = context.query;
  const paths = await getAllowedPaths(id);
  if (!paths.includes(pathname)) {
    return {
      redirect: "/home",
      permanente: false,
    };
  }
  return callback();
};

In all my private pages.

export const getServerSideProps = (context) => {
  return validateRoute(context)(async () => {
    // my custom callback for each page.
    const { id } = context.query;
    const props = await getUserData(id);
    return {
      props,
    };
  });
};
@mtoranzoskw

This comment has been minimized.

Copy link

@mtoranzoskw mtoranzoskw commented Apr 19, 2021

I couldn't use the getServerSideProps method in a HOC, because it is only available on pages. Neither use getInitialProps inside a getServerSideProps, I got a warning in my console.

In my case. I needed a generic function to validate the user's access to a private page with an async call. I solved this by creating a function that receives the context and a callback.

//util.js
const validateRoute = (context) => async (callback) => {
  const { id, pathname } = context.query;
  const paths = await getAllowedPaths(id);
  if (!paths.includes(pathname)) {
    return {
      redirect: "/home",
      permanente: false,
    };
  }
  return callback();
};

In all my private pages.

export const getServerSideProps = (context) => {
  return validateRoute(context)(async () => {
    // my custom callback for each page.
    const { id } = context.query;
    const props = await getUserData(id);
    return {
      props,
    };
  });
};

would you help port this to typescript?

I was able to cast it like this:

import { GetServerSidePropsContext, GetServerSidePropsResult } from 'next'
import Cookies from 'cookies'

type EmptyProps = {
  props: Record<string, unknown>
}

// HOC to only be executed when user is not logged in, otherwise redirect to portal
const withoutAuth = (context: GetServerSidePropsContext) => async (
  callback: () => any
): Promise<GetServerSidePropsResult<EmptyProps>> => {
  const cookies = new Cookies(context.req, context.res)

  if (cookies.get('user')) {
    return {
      redirect: {
        destination: '/protected-source',
        permanent: false,
      },
    }
  }

  return callback()
}

export default withoutAuth

I still can't figure the typescript definition for the callback... I've ended up doing callback: () => any

@Sergioamjr

This comment has been minimized.

Copy link

@Sergioamjr Sergioamjr commented Apr 19, 2021

I couldn't use the getServerSideProps method in a HOC, because it is only available on pages. Neither use getInitialProps inside a getServerSideProps, I got a warning in my console.
In my case. I needed a generic function to validate the user's access to a private page with an async call. I solved this by creating a function that receives the context and a callback.

//util.js
const validateRoute = (context) => async (callback) => {
  const { id, pathname } = context.query;
  const paths = await getAllowedPaths(id);
  if (!paths.includes(pathname)) {
    return {
      redirect: "/home",
      permanente: false,
    };
  }
  return callback();
};

In all my private pages.

export const getServerSideProps = (context) => {
  return validateRoute(context)(async () => {
    // my custom callback for each page.
    const { id } = context.query;
    const props = await getUserData(id);
    return {
      props,
    };
  });
};

would you help port this to typescript?

I was able to cast it like this:

import { GetServerSidePropsContext, GetServerSidePropsResult } from 'next'
import Cookies from 'cookies'

type EmptyProps = {
  props: Record<string, unknown>
}

// HOC to only be executed when user is not logged in, otherwise redirect to portal
const withoutAuth = (context: GetServerSidePropsContext) => async (
  callback: () => any
): Promise<GetServerSidePropsResult<EmptyProps>> => {
  const cookies = new Cookies(context.req, context.res)

  if (cookies.get('user')) {
    return {
      redirect: {
        destination: '/protected-source',
        permanent: false,
      },
    }
  }

  return callback()
}

export default withoutAuth

I still can figure the typescript definition for the callback... I've ended up doing callback: () => any

@mtoranzoskw callback has no parameter and might return the same values of withoutAuth. Try this out, let me know if it works.

const withoutAuth = (context: GetServerSidePropsContext) => async (
  callback: () => GetServerSidePropsResult<EmptyProps>
): Promise<GetServerSidePropsResult<EmptyProps>> => {
@mtoranzoskw

This comment has been minimized.

Copy link

@mtoranzoskw mtoranzoskw commented Apr 19, 2021

I couldn't use the getServerSideProps method in a HOC, because it is only available on pages. Neither use getInitialProps inside a getServerSideProps, I got a warning in my console.
In my case. I needed a generic function to validate the user's access to a private page with an async call. I solved this by creating a function that receives the context and a callback.

//util.js
const validateRoute = (context) => async (callback) => {
  const { id, pathname } = context.query;
  const paths = await getAllowedPaths(id);
  if (!paths.includes(pathname)) {
    return {
      redirect: "/home",
      permanente: false,
    };
  }
  return callback();
};

In all my private pages.

export const getServerSideProps = (context) => {
  return validateRoute(context)(async () => {
    // my custom callback for each page.
    const { id } = context.query;
    const props = await getUserData(id);
    return {
      props,
    };
  });
};

would you help port this to typescript?
I was able to cast it like this:

import { GetServerSidePropsContext, GetServerSidePropsResult } from 'next'
import Cookies from 'cookies'

type EmptyProps = {
  props: Record<string, unknown>
}

// HOC to only be executed when user is not logged in, otherwise redirect to portal
const withoutAuth = (context: GetServerSidePropsContext) => async (
  callback: () => any
): Promise<GetServerSidePropsResult<EmptyProps>> => {
  const cookies = new Cookies(context.req, context.res)

  if (cookies.get('user')) {
    return {
      redirect: {
        destination: '/protected-source',
        permanent: false,
      },
    }
  }

  return callback()
}

export default withoutAuth

I still can figure the typescript definition for the callback... I've ended up doing callback: () => any

@mtoranzoskw callback has no parameter and might return the same values of withoutAuth. Try this out, let me know if it works.

const withoutAuth = (context: GetServerSidePropsContext) => async (
  callback: () => GetServerSidePropsResult<EmptyProps>
): Promise<GetServerSidePropsResult<EmptyProps>> => {

Not working... I've tried that but I have an error when implementing it
image

Got an error using Promise as well (callback: () => Promise<GetServerSidePropsResult<EmptyProps>>)
image

@Sergioamjr

This comment has been minimized.

Copy link

@Sergioamjr Sergioamjr commented Apr 20, 2021

I couldn't use the getServerSideProps method in a HOC, because it is only available on pages. Neither use getInitialProps inside a getServerSideProps, I got a warning in my console.
In my case. I needed a generic function to validate the user's access to a private page with an async call. I solved this by creating a function that receives the context and a callback.

//util.js
const validateRoute = (context) => async (callback) => {
  const { id, pathname } = context.query;
  const paths = await getAllowedPaths(id);
  if (!paths.includes(pathname)) {
    return {
      redirect: "/home",
      permanente: false,
    };
  }
  return callback();
};

In all my private pages.

export const getServerSideProps = (context) => {
  return validateRoute(context)(async () => {
    // my custom callback for each page.
    const { id } = context.query;
    const props = await getUserData(id);
    return {
      props,
    };
  });
};

would you help port this to typescript?
I was able to cast it like this:

import { GetServerSidePropsContext, GetServerSidePropsResult } from 'next'
import Cookies from 'cookies'

type EmptyProps = {
  props: Record<string, unknown>
}

// HOC to only be executed when user is not logged in, otherwise redirect to portal
const withoutAuth = (context: GetServerSidePropsContext) => async (
  callback: () => any
): Promise<GetServerSidePropsResult<EmptyProps>> => {
  const cookies = new Cookies(context.req, context.res)

  if (cookies.get('user')) {
    return {
      redirect: {
        destination: '/protected-source',
        permanent: false,
      },
    }
  }

  return callback()
}

export default withoutAuth

I still can figure the typescript definition for the callback... I've ended up doing callback: () => any

@mtoranzoskw callback has no parameter and might return the same values of withoutAuth. Try this out, let me know if it works.

const withoutAuth = (context: GetServerSidePropsContext) => async (
  callback: () => GetServerSidePropsResult<EmptyProps>
): Promise<GetServerSidePropsResult<EmptyProps>> => {

Not working... I've tried that but I have an error when implementing it
image

Got an error using Promise as well (callback: () => Promise<GetServerSidePropsResult<EmptyProps>>)
image

@mtoranzoskw try to make your props optional.

type EmptyProps = {
  props?: Record<string, unknown>
}

// or

type EmptyProps = {
  props: Partial<Record<string, unknown>>
}
@mtoranzoskw

This comment has been minimized.

Copy link

@mtoranzoskw mtoranzoskw commented Apr 20, 2021

Thanks for the help @Sergioamjr

I've tried both, but making props optional was the only one that worked! I guess Partial would go 1 level up so that props become optional.

type EmptyProps = {
  props?: Record<string, unknown>
}

💪

@Mood-al

This comment has been minimized.

Copy link

@Mood-al Mood-al commented Aug 4, 2021

anyone done this without getInialProps ? currently im using next 11 and i want to add auth to my app but so far i couldnt understand the logic around protected routes in next js

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment