diff --git a/authentication/tests.py b/authentication/tests.py
index 66be76191b4e0e79803b115c0a735d722b0090ec..ae7a2f85b99d7f095ed7b0ceaae84984113965fc 100644
--- a/authentication/tests.py
+++ b/authentication/tests.py
@@ -1,5 +1,6 @@
 from django.test import Client, TestCase
 from django.urls import resolve
+from rest_framework_simplejwt import views as jwt_views
 
 from authentication.models import User
 from authentication.views import Login
@@ -75,7 +76,7 @@ class LoginPageContributorTest(TestCase):
     def setUp(self):
         self.client = Client()
         self.kontributor = User.objects.create_contributor(email="kontributor@gov.id",
-                                                     password="kontributor")
+                                                           password="kontributor")
         self.url = "/login/"
         self.view = Login
         self.template_name = "login.html"
@@ -250,7 +251,6 @@ class LoginPageAdminTest(TestCase):
         self.assertEqual(302, response.status_code)
         self.assertEqual(response.url, expected_redirect_url)
 
-
     def test_admin_visit_login_after_auth(self):
         # 302 meaning successful login and redirected
         expected_redirect_url = "/sukses-admin/"
@@ -263,3 +263,67 @@ class LoginPageAdminTest(TestCase):
         )
         self.assertEqual(302, response.status_code)
         self.assertEqual(response.url, expected_redirect_url)
+
+
+class TokenLoginTest(TestCase):
+    def setUp(self):
+        self.client = Client()
+        self.admin = User.objects.create_admin(email="admin@gov.id",
+                                               password="admin")
+        self.url = "/api/token/"
+        self.view = jwt_views.TokenObtainPairView
+        self.login_credential = {"email": "admin@gov.id", "password": "admin"}
+
+    def test_token_login_view(self):
+        found = resolve(self.url)
+        self.assertEqual(found.func.cls, self.view)
+
+    def test_token_login_get_method(self):
+        # Test
+        response = self.client.get(self.url)
+        self.assertEqual(response.status_code, 405)
+
+    def test_token_login_valid_data(self):
+        # Test
+        response = self.client.post(self.url, data=self.login_credential)
+        self.assertEqual(response.status_code, 200)
+
+    def test_token_login_invalid_data(self):
+        # Test
+        response = self.client.post(
+            self.url, data={"email": "admin@gov.id", "password": "admin1"})
+        self.assertEqual(response.status_code, 401)
+
+
+class TokenRefreshTest(TestCase):
+    def setUp(self):
+        self.client = Client()
+        self.admin = User.objects.create_admin(email="admin@gov.id",
+                                               password="admin")
+        self.token_url = "/api/token/"
+        self.refresh_url = "/api/token/refresh/"
+        self.view = jwt_views.TokenRefreshView
+        self.login_credential = {"email": "admin@gov.id", "password": "admin"}
+
+    def test_token_refresh_view(self):
+        found = resolve(self.refresh_url)
+        self.assertEqual(found.func.cls, self.view)
+
+    def test_token_refresh_get_method(self):
+        # Test
+        response = self.client.get(self.refresh_url)
+        self.assertEqual(response.status_code, 405)
+
+    def test_token_refresh_valid_data(self):
+        # Test
+        response = self.client.post(self.token_url, data=self.login_credential)
+        refresh_token = response.json()["refresh"]
+        response = self.client.post(
+            self.refresh_url, data={"refresh": refresh_token})
+        self.assertEqual(response.status_code, 200)
+
+    def test_token_refresh_invalid_data(self):
+        # Test
+        response = self.client.post(
+            self.refresh_url, data={"refresh": "aaaAAAA.BBbbbb.cCcCcCc"})
+        self.assertEqual(response.status_code, 401)
diff --git a/authentication/urls.py b/authentication/urls.py
index c4d31dc4ad8d010fd1e02105561c9c67edb78432..590a95ee49d2980fd96c5f3d7357f1123f78f090 100644
--- a/authentication/urls.py
+++ b/authentication/urls.py
@@ -1,5 +1,6 @@
 from django.contrib.auth.views import LogoutView
 from django.urls import path
+from rest_framework_simplejwt import views as jwt_views
 
 from authentication.views import Login
 
@@ -7,4 +8,6 @@ urlpatterns = [
     path("login/", Login.as_view(), name="login"),
     path("login_admin/", Login.as_view(), name="login_admin"),
     path("logout/", LogoutView.as_view()),
+    path('api/token/', jwt_views.TokenObtainPairView.as_view(), name='token_obtain_pair'),
+    path('api/token/refresh/', jwt_views.TokenRefreshView.as_view(), name='token_refresh'),
 ]
diff --git a/digipus/settings.py b/digipus/settings.py
index d5f32c78246e7d4b8caaedcbef66ba9dca186448..6f3add23133069c7552d793518bc22cee7e4e116 100644
--- a/digipus/settings.py
+++ b/digipus/settings.py
@@ -50,6 +50,7 @@ INSTALLED_APPS = [
     "news.apps.NewsConfig",
     "traffic_statistics",
     "forum",
+    "rest_framework",
 ]
 
 MIDDLEWARE = [
@@ -175,3 +176,9 @@ MEDIA_URL = "/media/"
 LOGOUT_REDIRECT_URL = "/"
 
 CRISPY_TEMPLATE_PACK = 'bootstrap4'
+
+REST_FRAMEWORK = {
+    'DEFAULT_AUTHENTICATION_CLASSES': [
+        'rest_framework_simplejwt.authentication.JWTAuthentication',
+    ],
+}
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index f3b2e12e332740426925ee03b625d0e64be99f14..30fd8032d8b3be87ddcb85a8443175668af3dc5f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -25,6 +25,9 @@ Django==3.1
 django-crispy-forms==1.9.1
 django-heroku==0.3.1
 django-storages==1.9.1
+djangorestframework==3.12.1
+djangorestframework-jwt==1.11.0
+djangorestframework-simplejwt==4.4.0
 entrypoints==0.3
 filelock==3.0.12
 flake8==3.7.9
@@ -67,6 +70,7 @@ pycodestyle==2.5.0
 pycparser==2.20
 PyDrive==1.3.1
 pyflakes==2.1.1
+PyJWT==1.7.1
 pylint==2.4.4
 pylint-django==2.0.15
 pylint-plugin-utils==0.6