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:

  1. Register user baru

  2. Login dengan credentials

  3. Akses protected route

  4. Test role-based access

  5. Test token expiration

  6. Test unauthorized access

Last updated