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;
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;
};
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,
},
});
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;
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;
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);
}
};
- Logout:
import { useDispatch } from 'react-redux';
import { logout } from '../store/slices/authSlice';
const logoutUser = () => {
const dispatch = useDispatch();
dispatch(logout());
};
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.
Support My Work ❤️
If you enjoy my content and find it valuable, consider supporting me by buying me a coffee. Your support helps me continue creating and sharing useful resources. Thank you!
Connect with Me 🌍
Let’s stay connected! You can follow me or reach out on these platforms:
🔹 YouTube – Tutorials, insights & tech content
🔹 LinkedIn – Professional updates & networking
🔹 GitHub – My open-source projects & contributions
🔹 Instagram – Behind-the-scenes & personal updates
🔹 X (formerly Twitter) – Quick thoughts & tech discussions
I’d love to hear from you—whether it’s feedback, collaboration ideas, or just a friendly hello!
Disclaimer
This content has been generated with the assistance of AI. While I strive for accuracy and quality, please verify critical information independently.
Top comments (0)