DEV Community

Nadim Chowdhury
Nadim Chowdhury

Posted on

How to Implement Role-Based Authorization in Next.js Using Redux and Local Storage

Here’s a structured approach to building a role-based authorization system for your Next.js project using Redux, where access tokens and user info are stored in localStorage and retrieved on page visits.

1. Redux Slice for Authentication

This slice will handle the user authentication state, including storing the access token and user role.

import { createSlice } from '@reduxjs/toolkit';

const initialState = {
  accessToken: null,
  userInfo: null,
  isAuthenticated: false,
};

const authSlice = createSlice({
  name: 'auth',
  initialState,
  reducers: {
    loginSuccess: (state, action) => {
      const { accessToken, userInfo } = action.payload;
      state.accessToken = accessToken;
      state.userInfo = userInfo;
      state.isAuthenticated = true;

      // Save token and userInfo to localStorage
      localStorage.setItem('accessToken', JSON.stringify(accessToken));
      localStorage.setItem('userInfo', JSON.stringify(userInfo));

      // Set expiry of 7 days
      const expiresAt = new Date();
      expiresAt.setDate(expiresAt.getDate() + 7);
      localStorage.setItem('expiresAt', expiresAt.toISOString());
    },
    logout: (state) => {
      state.accessToken = null;
      state.userInfo = null;
      state.isAuthenticated = false;

      // Remove from localStorage
      localStorage.removeItem('accessToken');
      localStorage.removeItem('userInfo');
      localStorage.removeItem('expiresAt');
    },
    loadUserFromStorage: (state) => {
      const accessToken = localStorage.getItem('accessToken');
      const userInfo = localStorage.getItem('userInfo');
      const expiresAt = localStorage.getItem('expiresAt');

      if (accessToken && userInfo && new Date() < new Date(expiresAt)) {
        state.accessToken = JSON.parse(accessToken);
        state.userInfo = JSON.parse(userInfo);
        state.isAuthenticated = true;
      } else {
        // Clear if token is expired
        localStorage.removeItem('accessToken');
        localStorage.removeItem('userInfo');
        localStorage.removeItem('expiresAt');
        state.isAuthenticated = false;
      }
    },
  },
});

export const { loginSuccess, logout, loadUserFromStorage } = authSlice.actions;
export default authSlice.reducer;
Enter fullscreen mode Exit fullscreen mode

2. Auth Provider File (Context + Protected Route)

This will provide a ProtectedRoute component to wrap around pages that require authorization based on roles.

import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { loadUserFromStorage, logout } from '../store/slices/authSlice';
import { useRouter } from 'next/router';

export const AuthProvider = ({ children }) => {
  const dispatch = useDispatch();
  const { isAuthenticated, userInfo } = useSelector((state) => state.auth);

  useEffect(() => {
    dispatch(loadUserFromStorage());
  }, [dispatch]);

  return children;
};

export const ProtectedRoute = ({ children, allowedRoles }) => {
  const { isAuthenticated, userInfo } = useSelector((state) => state.auth);
  const router = useRouter();

  useEffect(() => {
    if (!isAuthenticated || (allowedRoles && !allowedRoles.includes(userInfo.role))) {
      router.push('/login');
    }
  }, [isAuthenticated, userInfo, allowedRoles, router]);

  if (!isAuthenticated || (allowedRoles && !allowedRoles.includes(userInfo.role))) {
    return null; // or a loading spinner
  }

  return children;
};
Enter fullscreen mode Exit fullscreen mode

3. Redux Store Setup

Make sure to configure the Redux store in store.js:

import { configureStore } from '@reduxjs/toolkit';
import authReducer from './slices/authSlice';

export const store = configureStore({
  reducer: {
    auth: authReducer,
  },
});
Enter fullscreen mode Exit fullscreen mode

Wrap your application in Provider in pages/_app.js:

import { Provider } from 'react-redux';
import { store } from '../store/store';
import { AuthProvider } from '../components/AuthProvider';

function MyApp({ Component, pageProps }) {
  return (
    <Provider store={store}>
      <AuthProvider>
        <Component {...pageProps} />
      </AuthProvider>
    </Provider>
  );
}

export default MyApp;
Enter fullscreen mode Exit fullscreen mode

4. Protecting Routes in Pages

You can protect specific routes by wrapping them in the ProtectedRoute component and specifying allowed roles:

import { ProtectedRoute } from '../components/AuthProvider';

const AdminPage = () => {
  return (
    <ProtectedRoute allowedRoles={['admin']}>
      <h1>Admin Dashboard</h1>
    </ProtectedRoute>
  );
};

export default AdminPage;
Enter fullscreen mode Exit fullscreen mode

5. Login & Logout Flow

Here’s how you might implement the login and logout functions:

  • Login Action (API Call to Get Token and Set User):
import { loginSuccess } from '../store/slices/authSlice';
import { useDispatch } from 'react-redux';

const login = async (credentials) => {
  const dispatch = useDispatch();

  try {
    const response = await fetch('/api/login', {
      method: 'POST',
      body: JSON.stringify(credentials),
    });

    if (response.ok) {
      const data = await response.json();
      dispatch(loginSuccess({
        accessToken: data.accessToken,
        userInfo: data.user,
      }));
    }
  } catch (error) {
    console.error('Login failed', error);
  }
};
Enter fullscreen mode Exit fullscreen mode
  • Logout:
import { useDispatch } from 'react-redux';
import { logout } from '../store/slices/authSlice';

const logoutUser = () => {
  const dispatch = useDispatch();
  dispatch(logout());
};
Enter fullscreen mode Exit fullscreen mode

This setup gives you a robust structure for managing authentication with token storage in localStorage, and role-based access to routes in your Next.js project.

If you enjoy my content and would like to support my work, you can buy me a coffee. Your support is greatly appreciated!

Disclaimer: This content is generated by AI.

Top comments (0)