B. Authentication & Authorization
Tutorial Authentication & Authorization
1. Backend Authentication
1.1 Setup Auth Middleware (src/middleware/auth.js)
const jwt = require('jsonwebtoken');
const auth = (req, res, next) => {
try {
const token = req.headers.authorization.split(' ')[1];
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
res.status(401).json({ message: 'Auth failed' });
}
};
// Role middleware akan diimplementasikan nanti
/*
const checkRole = (roles) => {
return (req, res, next) => {...}
};
*/
module.exports = { auth };
1.2 User Model (src/models/User.js)
const db = require('../config/database');
const bcrypt = require('bcryptjs');
const User = {
create: async (userData) => {
const { username, email, password, role = 'pengguna' } = userData;
const hashedPassword = await bcrypt.hash(password, 10);
const [result] = await db.execute(
'INSERT INTO users (username, email, password, role) VALUES (?, ?, ?, ?)',
[username, email, hashedPassword, role]
);
return result;
},
findByEmail: async (email) => {
const [rows] = await db.execute('SELECT * FROM users WHERE email = ?', [email]);
return rows[0];
},
// Metode lain akan diimplementasikan nanti
/*
updateProfile: async (userId, data) => {...},
delete: async (userId) => {...},
findById: async (userId) => {...}
*/
};
module.exports = User;
1.3 Auth Controller (src/controllers/authController.js)
const User = require('../models/User');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const register = async (req, res) => {
try {
const { username, email, password } = req.body;
const existingUser = await User.findByEmail(email);
if (existingUser) {
return res.status(400).json({ message: 'Email already exists' });
}
await User.create({ username, email, password });
res.status(201).json({ message: 'User created successfully' });
} catch (error) {
res.status(500).json({ message: error.message });
}
};
const login = async (req, res) => {
try {
const { email, password } = req.body;
const user = await User.findByEmail(email);
if (!user) {
return res.status(401).json({ message: 'Auth failed' });
}
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
return res.status(401).json({ message: 'Auth failed' });
}
const token = jwt.sign(
{ userId: user.id, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: process.env.JWT_EXPIRES_IN }
);
res.json({
token,
user: {
id: user.id,
username: user.username,
email: user.email,
role: user.role
}
});
} catch (error) {
res.status(500).json({ message: error.message });
}
};
// Controller lain akan diimplementasikan nanti
/*
const getProfile = async (req, res) => {...};
const updateProfile = async (req, res) => {...};
const changePassword = async (req, res) => {...};
*/
module.exports = {
register,
login
};
1.4 Auth Routes (src/routes/auth.routes.js)
const express = require('express');
const router = express.Router();
const authController = require('../controllers/authController');
const { auth } = require('../middleware/auth');
router.post('/register', authController.register);
router.post('/login', authController.login);
// Routes lain akan diimplementasikan nanti
/*
router.get('/profile', auth, authController.getProfile);
router.put('/profile', auth, authController.updateProfile);
router.put('/change-password', auth, authController.changePassword);
*/
module.exports = router;
2. Frontend Authentication
2.1 Auth Slice (src/store/slices/authSlice.js)
import { createSlice } from '@reduxjs/toolkit';
const initialState = {
user: null,
token: localStorage.getItem('token'),
loading: false,
error: null,
};
const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
setCredentials: (state, action) => {
state.user = action.payload.user;
state.token = action.payload.token;
localStorage.setItem('token', action.payload.token);
},
logout: (state) => {
state.user = null;
state.token = null;
localStorage.removeItem('token');
},
setError: (state, action) => {
state.error = action.payload;
},
},
});
export const { setCredentials, logout, setError } = authSlice.actions;
export default authSlice.reducer;
2.2 Auth Service (src/services/auth.service.js)
import api from './api';
const AuthService = {
login: async (credentials) => {
const response = await api.post('/auth/login', credentials);
return response.data;
},
register: async (userData) => {
const response = await api.post('/auth/register', userData);
return response.data;
},
// Service lain akan diimplementasikan nanti
/*
getProfile: async () => {...},
updateProfile: async (data) => {...},
changePassword: async (data) => {...}
*/
};
export default AuthService;
2.3 Protected Route Component (src/components/common/ProtectedRoute.js)
import { Navigate } from 'react-router-dom';
import { useSelector } from 'react-redux';
const ProtectedRoute = ({ children, roles }) => {
const { user, token } = useSelector((state) => state.auth);
if (!token) {
return <Navigate to="/login" />;
}
if (roles && !roles.includes(user?.role)) {
return <Navigate to="/unauthorized" />;
}
return children;
};
export default ProtectedRoute;
2.4 Login Page (src/pages/Login.js)
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import { setCredentials } from '../store/slices/authSlice';
import AuthService from '../services/auth.service';
const Login = () => {
const [formData, setFormData] = useState({
email: '',
password: ''
});
const dispatch = useDispatch();
const navigate = useNavigate();
const handleSubmit = async (e) => {
e.preventDefault();
try {
const data = await AuthService.login(formData);
dispatch(setCredentials(data));
navigate('/dashboard');
} catch (error) {
console.error('Login failed:', error);
}
};
// Template akan diimplementasikan nanti
return (
<div>
<h1>Login Page</h1>
{/* Form login akan diimplementasikan nanti */}
</div>
);
};
export default Login;
2.5 Update App.js dengan Protected Routes
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import { Provider } from 'react-redux';
import store from './store';
import ProtectedRoute from './components/common/ProtectedRoute';
// Pages dan components akan diimplementasikan nanti
/*
import Login from './pages/Login';
import Register from './pages/Register';
import Dashboard from './pages/Dashboard';
import AdminDashboard from './pages/AdminDashboard';
*/
function App() {
return (
<Provider store={store}>
<Router>
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
<Route
path="/admin"
element={
<ProtectedRoute roles={['admin']}>
<AdminDashboard />
</ProtectedRoute>
}
/>
</Routes>
</Router>
</Provider>
);
}
export default App;
Catatan Penting:
Implementasikan validasi form sebelum mengirim request ke server
Tambahkan error handling yang proper di semua endpoints
Implementasikan logout functionality
Tambahkan loading states untuk feedback user
Implementasikan refresh token jika diperlukan
Pastikan semua routes terlindungi dengan proper authorization
Implementasikan remember me functionality jika diperlukan
Untuk testing:
Register user baru
Login dengan credentials
Akses protected route
Test role-based access
Test token expiration
Test unauthorized access
Last updated