Create Role Based Authorization in Auth0 and NextJS

Viewed 1480

I created a nextjs web application with Auth0.

A user can login and logout. I can reach also user information. But I cannot see user roles.

I am getting the profile with Auth0's getSession method.

I want to create 2 different pages in NextJS which are for user and admin roles.

So it will be basic API authorization that I want. If a user is in user role then the user can access only the user page. If a user has admin role then the user can access all pages.

To I achive this I need to get user roles.The getSession method does not return the user roles.

How can solve the problem?

Thanks

2 Answers

I'm sure you've figured it out by now, but I had the same question.

You have to explicitly add the roles to your tokens (Auth and id) if you want them attached to the user object.

You can do this multiple ways but I did this by adding a rule in Auth0:

function setRolesToUser(user, context, callback) {
  const namespace = 'http://my-website-name.com';
  const assignedRoles = (context.authorization || {}).roles;

  let idTokenClaims = context.idToken || {};
  let accessTokenClaims = context.accessToken || {};

  idTokenClaims[`${namespace}/roles`] = assignedRoles;
  accessTokenClaims[`${namespace}/roles`] = assignedRoles;

  context.idToken = idTokenClaims;
  context.accessToken = accessTokenClaims;

  callback(null, user, context);
}

Now your token(s) will have the rule attached as a namespaced array w/ the assigned roles in said array.

Now that your tokens have the roles attached you can use it to gate content on Nextjs like this:

import { getSession } from '@auth0/nextjs-auth0';

export async function getServerSideProps({ req, res }) {
 
  const session = getSession(req, res);
  if (!session?.user['http://my-website-name/roles'].includes('admin')) {
    return { props: { error: 'Forbidden' } };
  } else {
    return {
      props: {},
    };
  }
}

Then in your component, you can return early like so:

if (error) return <div>{error}</div>;

This explains server side but you can do similar client-side w/ useUser:

import { useUser} from '@auth0/nextjs-auth0';

For this you have to add rule in auth0 dashboard: https://auth0.com/docs/customize/rules. I wrote a custom rule function in this post

Then write a HOC:

function withAuth<T>(Component: React.ComponentType<T>) {
  return (role: string) => {
    return (props: T) => {
      // CUSTOM HOOK TO GET THE USER
      const { data, loading } = useGetUser();
      if (loading) {
        return <p>Loading...</p>;
      }

      if (!data) {
        return <Redirect to="/api/v1/login" />;
      } else {
        if (role && !isAuthorized(data, role)) {
          return <Redirect to="/api/v1/login" />;
        }

        return <Component user={data} loading={loading} {...props} />;
      }
    };
  };
}

export default withAuth;

isAuthorized:

import { Claims } from "@auth0/nextjs-auth0/dist/session/session";
export const isAuthorized = (user: Claims, role: string) => {
  return (
    user &&
    // user[process.env.AUTH0_NAMESPACE + "/roles"] &&
    user["https://yourdomain.auth.com" + "/roles"].includes(role)
  );
};

useGetUser Hook

export const useGetUser = () => {
  // "/api/v1/me" is passed to fetcher as url
  const { data, error, ...rest } = useSWR("/api/v1/me", fetcher);
  return { data, error, loading: !data && !error, ...rest };
};

fetcher for useSWR

export const fetcher = (url: string) =>
  fetch(url).then(
    async (res: Response): Promise<any> => {
      const result = await res.json();

      if (res.status !== 200) {
        return Promise.reject(result);
      } else {
        return result;
      }
    }
  );

/api/v1/me api function in pages.

// you should already have set auth) instance
import auth0 from "@/utils/auth0";
import { NextApiRequest, NextApiResponse } from "next";

export default async function me(req: NextApiRequest, res: NextApiResponse) {
  try {
    await auth0.handleProfile(req, res);
  } catch (error) {
    console.log("error in v1/me ", error);
    res.status(error.status || 400).end(error.message);
  }
}

After this set up you can wrap your components with withAuth

const Dashboard: React.FC<DashboardProps> = ({ user, loading }) => {
  return (
    <BaseLayout
    >
        <BasePage >
          {/* YOUR COMPONENT LOGIC HERE */}
        </BasePage>
    </BaseLayout>
  );
};

export default withAuth(Dashboard)("admin");
Related