MKing Admin uses a JWT-based authentication system with granular role-based permissions to secure your admin dashboard.
Overview
The authentication system provides:
JWT Token Authentication - Secure token-based authentication with Bearer tokens
Role-Based Access Control (RBAC) - Fine-grained permission management
Protected Routes - Automatic redirection for unauthorized access
Persistent Sessions - Token storage in localStorage for session persistence
Login Workflow
User enters credentials
The login form validates email and password with real-time validation using react-hook-form.
API authentication
Credentials are sent to the /login endpoint. The server validates and returns a JWT token.
Fetch user data
After successful login, the app fetches the user’s profile and permissions using the /user endpoint.
Store authentication state
The JWT token is stored in localStorage, and user data with permissions are stored in Zustand state management.
Implementation
Login Component
The login page (src/auth/pages/LoginPage.tsx:11) uses Material-UI and react-hook-form for a robust authentication experience:
import { useForm } from "react-hook-form" ;
import { LoginService , getInfoUser } from "../../services/service" ;
import { useAuthStore } from "../../store/authStore" ;
const { register , handleSubmit , formState : { errors , isValid } } = useForm ({
mode: "all" ,
});
const onSubmit = ( data ) => {
if ( isValid ) {
const toastId = toast . loading ( "Iniciando sesión..." );
LoginService ( data )
. then ( async ( res ) => {
const token = res . data . token ;
localStorage . setItem ( "token" , token );
// Fetch user profile and permissions
const { data : userData } = await getInfoUser ();
const perms = userData . roles . flatMap (( role ) =>
role . permissions . map (( p ) => p . slug )
);
setAuth ( userData , perms );
window . location . href = "/home" ;
})
. catch (( err ) => {
toast . error ( "Credenciales incorrectas o error de servidor" );
});
}
};
The login form includes comprehensive validation rules (src/auth/pages/LoginPage.tsx:76-105):
< TextField
label = "Correo"
type = "email"
placeholder = "[email protected] "
fullWidth
{ ... register ( "email" , {
required: "El correo es obligatorio" ,
pattern: {
value: / ^ [ ^ <>()[ \]\\ .,;:\s@ \" ] + ( \. [ ^ <>()[ \]\\ .,;:\s@ \" ] + ) * @ ( \[ [ 0-9 ] {1,3} \. [ 0-9 ] {1,3} \. [ 0-9 ] {1,3} \. [ 0-9 ] {1,3} \] ) | (( [ a-zA-Z\-0-9 ] + \. ) + [ a-zA-Z ] {2,} ) $ / ,
message: "El correo debe ser válido" ,
},
}) }
/>
< TextField
label = "Contraseña"
type = "password"
placeholder = "Contraseña"
fullWidth
{ ... register ( "password" , {
required: "La contraseña es obligatoria" ,
minLength: {
value: 4 ,
message: "La contraseña debe de ser más de 4 caracteres" ,
},
}) }
/>
API Integration
Authentication Service
The authentication service (src/services/service.tsx:46) handles API requests with axios:
import axios from "axios" ;
const apiUrl = import . meta . env . VITE_BASE_URL ;
// Login endpoint
export const LoginService = ( body ) =>
axios . post ( ` ${ apiUrl } /login` , body );
// Get authenticated user info
export const getInfoUser = () =>
axios . get ( ` ${ apiUrl } /user` );
Axios Interceptors
All API requests automatically include the JWT token via axios interceptors (src/services/service.tsx:7-27):
axios . interceptors . request . use (
( config ) => {
const token = localStorage . getItem ( "token" );
if ( token ) {
config . headers [ "Authorization" ] = "Bearer " + token ;
}
return config ;
},
function ( error ) {
return Promise . reject ( error );
}
);
Response Error Handling
Global error handling for authentication failures (src/services/service.tsx:35-44):
axios . interceptors . response . use (
( res ) => res ,
( err ) => {
const status = err . response . status ;
Response ( status ); // Custom error handler
return Promise . reject ( err );
}
);
State Management
Auth Slice (Redux)
The application uses Redux Toolkit for authentication state (src/store/auth/authSlice.tsx:3-29):
import { createSlice } from "@reduxjs/toolkit" ;
export const authSlice = createSlice ({
name: "auth" ,
initialState: {
status: "not-authenticated" , // 'checking', 'not-authenticated', 'authenticated'
fullname: null ,
permissions: [],
},
reducers: {
login : ( state , { payload }) => {
state . status = "authenticated" ;
state . fullname = payload . name ;
state . permissions = payload . permisos ;
},
logout : ( state ) => {
state . status = "not-authenticated" ;
state . fullname = null ;
state . permissions = [];
},
checkingCredentials : ( state ) => {
state . status = "checking" ;
},
},
});
export const { login , logout , checkingCredentials } = authSlice . actions ;
Role-Based Permissions
Permission Structure
Permissions are extracted from user roles and stored as slugs:
const { data : userData } = await getInfoUser ();
// Extract permission slugs from all assigned roles
const perms = userData . roles . flatMap (( role ) =>
role . permissions . map (( p ) => p . slug )
);
setAuth ( userData , perms );
Example Permission Check
const userPermissions = useAuthStore (( state ) => state . permissions );
const canEditProducts = userPermissions . includes ( 'products.edit' );
const canDeleteProducts = userPermissions . includes ( 'products.delete' );
if ( canEditProducts ) {
// Show edit button
}
Security Best Practices
Never store sensitive data in localStorage in production. Consider using httpOnly cookies for enhanced security.
The JWT token includes an expiration time. Implement token refresh logic for long-running sessions.
Token Storage
// Store token
localStorage . setItem ( "token" , token );
// Retrieve token
const token = localStorage . getItem ( "token" );
// Clear on logout
localStorage . removeItem ( "token" );
Logout Implementation
const handleLogout = () => {
// Clear token
localStorage . removeItem ( "token" );
// Reset auth state
dispatch ( logout ());
// Redirect to login
navigate ( "/login" );
};
User Feedback
The authentication flow provides real-time user feedback using react-toastify:
import { toast } from "react-toastify" ;
// Loading state
const toastId = toast . loading ( "Iniciando sesión..." );
// Success
toast . update ( toastId , {
render: "¡Bienvenido!" ,
type: "success" ,
isLoading: false ,
autoClose: 2000 ,
});
// Error
toast . update ( toastId , {
render: "Credenciales incorrectas o error de servidor" ,
type: "error" ,
isLoading: false ,
autoClose: 3000 ,
});
Environment Configuration
Configure your API endpoint in .env:
VITE_BASE_URL = https://api.yourdomain.com
Next Steps
Employee Management Learn how to manage employee accounts with role assignments
Roles & Permissions Configure roles and granular permissions