diff --git a/administration/tests.py b/administration/tests.py
index 87122190ea2e86da9de75f86f6b27fe0e5c6d39b..5276b460e8f6b99e66cc46326dcbece0122823c0 100644
--- a/administration/tests.py
+++ b/administration/tests.py
@@ -11,6 +11,7 @@ from authentication.models import User
 from bs4 import BeautifulSoup
 
 EDIT_ENDPOINT = "/edit"
+ERROR_403_MESSAGE = 'Kamu harus login untuk mengakses halaman ini'
 
 class VerifikasiMateriTest(TestCase):
     def setUp(self):
@@ -57,6 +58,9 @@ class VerifikasiMateriTest(TestCase):
         # Test
         response = self.client.get(self.url)
         self.assertEqual(response.status_code, 403)
+
+        html = response.content.decode("utf-8")
+        self.assertIn(ERROR_403_MESSAGE, html)
         # Logout
         self.client.logout()
 
@@ -65,6 +69,9 @@ class VerifikasiMateriTest(TestCase):
         response = self.client.get(self.url)
         self.assertEqual(response.status_code, 403)
 
+        html = response.content.decode("utf-8")
+        self.assertIn(ERROR_403_MESSAGE, html)
+
     def test_verifikasi_materi_using_correct_template(self):
         # Login
         self.client.login(**self.admin_credential)
@@ -164,6 +171,9 @@ class DetailVerifikasiMateriTest(TestCase):
         # Test
         response = self.client.get(self.url_materi_1)
         self.assertEqual(response.status_code, 403)
+
+        html = response.content.decode("utf-8")
+        self.assertIn(ERROR_403_MESSAGE, html)
         # Logout
         self.client.logout()
 
@@ -172,6 +182,9 @@ class DetailVerifikasiMateriTest(TestCase):
         response = self.client.get(self.url_materi_1)
         self.assertEqual(response.status_code, 403)
 
+        html = response.content.decode("utf-8")
+        self.assertIn(ERROR_403_MESSAGE, html)
+
     def test_verification_detail_approve(self):
         # Login
         self.client.login(**self.admin_credential)
@@ -339,6 +352,9 @@ class SettingVerifikasiTest(TestCase):
         # Test
         response = self.client.get(self.url)
         self.assertEqual(response.status_code, 403)
+
+        html = response.content.decode("utf-8")
+        self.assertIn(ERROR_403_MESSAGE, html)
         # Logout
         self.client.logout()
 
@@ -347,6 +363,9 @@ class SettingVerifikasiTest(TestCase):
         response = self.client.get(self.url)
         self.assertEqual(response.status_code, 403)
 
+        html = response.content.decode("utf-8")
+        self.assertIn(ERROR_403_MESSAGE, html)
+
     def test_setting_verification_add(self):
         # Login
         self.client.login(**self.admin_credential)
@@ -444,6 +463,9 @@ class SettingCategoriTest(TestCase):
         # Test
         response = self.client.get(self.url)
         self.assertEqual(response.status_code, 403)
+
+        html = response.content.decode("utf-8")
+        self.assertIn(ERROR_403_MESSAGE, html)
         # Logout
         self.client.logout()
 
@@ -452,6 +474,9 @@ class SettingCategoriTest(TestCase):
         response = self.client.get(self.url)
         self.assertEqual(response.status_code, 403)
 
+        html = response.content.decode("utf-8")
+        self.assertIn(ERROR_403_MESSAGE, html)
+
     def test_setting_category_add(self):
         # Login
         self.client.login(**self.admin_credential)
@@ -545,6 +570,9 @@ class KelolaKontributorTest(TestCase):
         # Test
         response = self.client.get(self.url)
         self.assertEqual(response.status_code, 403)
+
+        html = response.content.decode("utf-8")
+        self.assertIn(ERROR_403_MESSAGE, html)
         # Logout
         self.client.logout()
 
@@ -553,6 +581,9 @@ class KelolaKontributorTest(TestCase):
         response = self.client.get(self.url)
         self.assertEqual(response.status_code, 403)
 
+        html = response.content.decode("utf-8")
+        self.assertIn(ERROR_403_MESSAGE, html)
+
     def test_contributor_list_using_correct_template(self):
         # Login
         self.client.login(**self.admin_credential)
@@ -614,6 +645,9 @@ class KelolaAdminTest(TestCase):
         # Test
         response = self.client.get(self.url)
         self.assertEqual(response.status_code, 403)
+
+        html = response.content.decode("utf-8")
+        self.assertIn(ERROR_403_MESSAGE, html)
         # Logout
         self.client.logout()
 
@@ -622,6 +656,9 @@ class KelolaAdminTest(TestCase):
         response = self.client.get(self.url)
         self.assertEqual(response.status_code, 403)
 
+        html = response.content.decode("utf-8")
+        self.assertIn(ERROR_403_MESSAGE, html)
+
     def test_admin_list_using_correct_template(self):
         # Login
         self.client.login(**self.admin_credential)
@@ -673,6 +710,9 @@ class ProfileContributorAdminTest(TestCase):
         response = self.client.get(self.url)
         self.assertEqual(response.status_code, 403)
 
+        html = response.content.decode("utf-8")
+        self.assertIn(ERROR_403_MESSAGE, html)
+
     def test_profile_contributor_admin_using_correct_template(self):
         # Login
         self.client.login(**self.admin_credential)
@@ -717,6 +757,9 @@ class ProfileAdminAdministrationTest(TestCase):
         # Test
         response = self.client.get(self.url)
         self.assertEqual(response.status_code, 403)
+
+        html = response.content.decode("utf-8")
+        self.assertIn(ERROR_403_MESSAGE, html)
         # Logout
         self.client.logout()
 
@@ -725,6 +768,9 @@ class ProfileAdminAdministrationTest(TestCase):
         response = self.client.get(self.url)
         self.assertEqual(response.status_code, 403)
 
+        html = response.content.decode("utf-8")
+        self.assertIn(ERROR_403_MESSAGE, html)
+
     def test_profile_admin_administration_using_correct_template(self):
         # Login
         self.client.login(**self.admin_credential)
@@ -777,6 +823,9 @@ class RegistrasiAdminTest(TestCase):
         # Test
         response = self.client.get(self.url)
         self.assertEqual(response.status_code, 403)
+
+        html = response.content.decode("utf-8")
+        self.assertIn(ERROR_403_MESSAGE, html)
         # Logout
         self.client.logout()
 
@@ -785,6 +834,9 @@ class RegistrasiAdminTest(TestCase):
         response = self.client.get(self.url)
         self.assertEqual(response.status_code, 403)
 
+        html = response.content.decode("utf-8")
+        self.assertIn(ERROR_403_MESSAGE, html)
+
     def test_registrasi_admin_using_correct_template(self):
         # Login
         self.client.login(**self.admin_credential)
@@ -825,6 +877,9 @@ class EditVerificationTest(TestCase):
         self.assertEqual(response.status_code, 403)
         self.assertNotEqual(response.status_code, 200)
 
+        html = response.content.decode("utf-8")
+        self.assertIn(ERROR_403_MESSAGE, html)
+
     def test_edit_verifikasi_using_correct_template(self):
         self.client.login(**self.admin_credential)
         found = resolve(self.url)
@@ -855,6 +910,9 @@ class EditVerificationTest(TestCase):
         response = self.client.get("/administration/delete-verification/" + str(deleteObject.id) + "/")
         self.assertEqual(response.status_code, 403)
 
+        html = response.content.decode("utf-8")
+        self.assertIn(ERROR_403_MESSAGE, html)
+
     def test_edit_verification(self):
         self.client.login(**self.admin_credential)
         response = self.client.get(self.url)
@@ -907,6 +965,9 @@ class EditCategoryTest(TestCase):
         self.assertEqual(response.status_code, 403)
         self.assertNotEqual(response.status_code, 200)
 
+        html = response.content.decode("utf-8")
+        self.assertIn(ERROR_403_MESSAGE, html)
+
     def test_edit_category_using_correct_template(self):
         self.client.login(**self.admin_credential)
         found = resolve(self.url)
@@ -937,6 +998,9 @@ class EditCategoryTest(TestCase):
         response = self.client.get("/administration/delete-category/" + str(deleteObject.id) + "/")
         self.assertEqual(response.status_code, 403)
 
+        html = response.content.decode("utf-8")
+        self.assertIn(ERROR_403_MESSAGE, html)
+
     def test_edit_category(self):
         self.client.login(**self.admin_credential)
         response = self.client.get(self.url)
diff --git a/administration/views.py b/administration/views.py
index cd49c9fae60e3675adf3002d6fc8c3b5011eb88f..507425066c1600ba66d2f494bf18cc594e393e56 100644
--- a/administration/views.py
+++ b/administration/views.py
@@ -12,6 +12,7 @@ from dateutil.relativedelta import relativedelta
 from administration.models import VerificationReport, VerificationSetting, DeletionHistory
 from administration.forms import CategoryForm, VerificationSettingForm, RegistrasiAdminForm, PeriodForm, EditAdminStatusForm
 from app.models import Category, Materi, ViewStatistics, DownloadStatistics, Comment, Like, getRandomColor
+from app.views import permission_denied
 from authentication.models import User
 from datetime import datetime
 
diff --git a/app/templates/error_403.html b/app/templates/error_403.html
new file mode 100644
index 0000000000000000000000000000000000000000..06ad21b3b5df11bf2f72c59653feca98923c566b
--- /dev/null
+++ b/app/templates/error_403.html
@@ -0,0 +1,96 @@
+{% load static %}
+
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+
+  <meta charset="utf-8">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge">
+  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+  <meta name="description" content="">
+  <meta name="author" content="">
+
+  <title>Error 403</title>
+
+  <!-- Custom fonts for this template -->
+  <link href="https://fonts.googleapis.com/css2?family=Poppins&display=swap" rel="stylesheet">
+
+  <!-- Custom styles for this template -->
+  <link rel="icon" type="image/png" href="{% static 'images/icons/logo.ico' %}" />
+  <link href="{% static 'css/sb-admin-2.min.css' %}" rel="stylesheet">
+  <link rel="stylesheet" href="{% static 'css/button.css' %}">
+
+  <!-- Custom styles for this page -->
+  <link href="{% static 'vendor/datatables/dataTables.bootstrap4.min.css' %}" rel="stylesheet">
+
+</head>
+
+<body id="page-top" style="font-family: 'Poppins', sans-serif;">
+
+  <!-- Page Wrapper -->
+  <div id="wrapper">
+
+    <!-- Content Wrapper -->
+    <div id="content-wrapper" class="d-flex flex-column">
+
+      <!-- Main Content -->
+      <div id="content">
+        <!-- Begin Page Content -->
+        <div class="container-fluid page-height">
+            <div class="profile-content white-text">
+                <div class="main-content">
+                    <div class="profile-margin"></div>
+                    <h2>Kamu harus login untuk mengakses halaman ini</h2>
+                    <div class="profile-margin"></div>
+                    <span>
+                      <a class="btn btn-primary main-content" href="/login/">Login sebagai kontributor</a>
+                      <a class="btn btn-primary main-content" href="/login-admin/">Login sebagai admin</a>
+                    </span>
+                </div>
+            </div>
+        </div>
+        <!-- /.container-fluid -->
+
+      </div>
+      <!-- End of Main Content -->
+
+      <!-- Footer -->
+      <footer class="sticky-footer bg-white">
+        <div class="container my-auto">
+          <div class="copyright text-center my-auto">
+            <span>Copyright &copy; Diskominfo Kota Depok 2020</span>
+          </div>
+        </div>
+      </footer>
+      <!-- End of Footer -->
+
+    </div>
+    <!-- End of Content Wrapper -->
+
+  </div>
+  <!-- End of Page Wrapper -->
+
+  <!-- Scroll to Top Button-->
+  <a class="scroll-to-top rounded" href="#page-top">
+    <em class="fas fa-angle-up"></em>
+  </a>
+
+  <!-- Bootstrap core JavaScript-->
+  <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
+    integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
+    crossorigin="anonymous"></script>
+  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"
+    integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1"
+    crossorigin="anonymous"></script>
+  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
+    integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"
+    crossorigin="anonymous"></script>
+
+  <!-- Core plugin JavaScript-->
+  <script src="https://code.jquery.com/jquery-3.5.0.min.js"
+    integrity="sha256-xNzN2a4ltkB44Mc/Jz3pT4iU1cmeR0FkXs4pru/JxaQ=" crossorigin="anonymous"></script>
+
+</body>
+
+</html>
\ No newline at end of file
diff --git a/app/tests.py b/app/tests.py
index b29a6b63e41e9b2053cee187576261cafe5c97da..2b828d3453e63b88a2711f7fac171876054a8423 100644
--- a/app/tests.py
+++ b/app/tests.py
@@ -40,6 +40,8 @@ from datetime import datetime
 import pandas as pd
 
 
+ERROR_403_MESSAGE = 'Kamu harus login untuk mengakses halaman ini'
+
 class DaftarKatalogTest(TestCase):
     def test_daftar_katalog_url_exist(self):
         url = "/"
@@ -335,6 +337,9 @@ class PostsViewTest(TestCase):
         self.assertRaises(PermissionDenied)
         self.assertEqual(response.status_code, 403)
 
+        html = response.content.decode("utf-8")
+        self.assertIn(ERROR_403_MESSAGE, html)
+
     def test_returns_correct_template(self):
         response = self._request_as_user()
         self.assertTemplateUsed(response, "user_uploaded_posts.html")
@@ -718,6 +723,9 @@ class DashboardKontributorViewTest(TestCase):
         # Test
         response = self.client.get(self.url)
         self.assertEqual(response.status_code, 403)
+
+        html = response.content.decode("utf-8")
+        self.assertIn(ERROR_403_MESSAGE, html)
         # Logout
         self.client.logout()
 
@@ -726,6 +734,8 @@ class DashboardKontributorViewTest(TestCase):
         response = self.client.get(self.url)
         self.assertEqual(response.status_code, 403)
 
+        html = response.content.decode("utf-8")
+        self.assertIn(ERROR_403_MESSAGE, html)
 
 class ProfilAdminTest(TestCase):
     def setUp(self):
@@ -819,6 +829,9 @@ class ProfilKontributorTest(TestCase):
         # Test
         response = self.client.get(self.url)
         self.assertEqual(response.status_code, 403)
+
+        html = response.content.decode("utf-8")
+        self.assertIn(ERROR_403_MESSAGE, html)
         # Logout
         self.client.logout()
 
@@ -827,6 +840,9 @@ class ProfilKontributorTest(TestCase):
         response = self.client.get(self.url)
         self.assertEqual(response.status_code, 403)
 
+        html = response.content.decode("utf-8")
+        self.assertIn(ERROR_403_MESSAGE, html)
+
 
 class SuntingProfilTest(TestCase):
     def setUp(self):
diff --git a/app/views.py b/app/views.py
index 576858cff10f62a4878ebd2de81ddbc1b6d4ad59..005803330a0e05477049a9f08fdc13b482043a72 100644
--- a/app/views.py
+++ b/app/views.py
@@ -11,8 +11,10 @@ from django.http import (Http404, HttpResponse, HttpResponseRedirect,
                          JsonResponse)
 from django.shortcuts import get_object_or_404, redirect
 from django.template import loader
+from django.urls import reverse
+from django.views import defaults
+from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
 from django.views.generic import TemplateView
-
 from administration.models import VerificationReport
 from app.forms import SuntingProfilForm, UploadMateriForm, RatingContributorForm
 from app.models import Category, Comment, Materi, Like, ViewStatistics, DownloadStatistics, ReqMaterial, Rating, \
@@ -25,6 +27,9 @@ from io import BytesIO
 from django.contrib import messages
 
 
+def permission_denied(request, exception, template_name = 'error_403.html'):
+    return defaults.permission_denied(request, exception, template_name)
+
 class DaftarKatalog(TemplateView):
     paginate_by = 2
     template_name = "app/katalog_materi.html"
diff --git a/digipus/urls.py b/digipus/urls.py
index 6e5439787704ed9f8af95acd2b03022271f24779..a58c0fb1b8eb9156a99cf3b5c069d84ad380e40a 100644
--- a/digipus/urls.py
+++ b/digipus/urls.py
@@ -28,3 +28,5 @@ urlpatterns = [
     path("statistics/", include("traffic_statistics.urls")),
     path("forum/", include("forum.urls")),
 ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
+
+handler403 = 'app.views.permission_denied'
diff --git a/requirements.txt b/requirements.txt
index 53c5c5b600a8c86f539445e70a5b8bf8f0c9f620..0689c836624c4f257828d7875e5c29b94f277c80 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,7 +6,6 @@ atomicwrites==1.3.0
 attrs==19.3.0
 autopep8==1.5
 beautifulsoup4==4.9.0
-bitarray==1.2.1
 black==19.10b0
 bs4==0.0.1
 cachetools==4.1.0
@@ -61,7 +60,6 @@ pycparser==2.20
 pyflakes==2.1.1
 pylint==2.4.4
 pylint-django==2.0.15
-pylint-exit==1.1.0
 pylint-plugin-utils==0.6
 pyparsing==2.4.6
 PySocks==1.7.1
@@ -73,6 +71,7 @@ PyYAML==5.3.1
 regex==2020.4.4
 requests==2.23.0
 rsa==4.0
+simplejson==3.7.3
 six==1.14.0
 soupsieve==2.0
 sqlparse==0.3.1