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.
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)