Skip to content

Instantly share code, notes, and snippets.

@whoisryosuke
Created June 26, 2018 22:24
Show Gist options
  • Star 41 You must be signed in to star a gist
  • Fork 10 You must be signed in to fork a gist
  • Save whoisryosuke/d034d3eaa0556e86349fb2634788a7a1 to your computer and use it in GitHub Desktop.
Save whoisryosuke/d034d3eaa0556e86349fb2634788a7a1 to your computer and use it in GitHub Desktop.
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>
)
}
}
}
@mtoranzoskw
Copy link

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
Copy link

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
Copy link

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
Copy link

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

@thuyit1996
Copy link

Thank a lot

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