diff --git a/requirements.txt b/requirements.txt
index cb089eea7d140094d61e6c9a7f63484a036cadea..8ad28d986aed3517475c9fe3e63130ff614de6cc 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,10 +1,21 @@
+aniso8601==7.0.0
 asgiref==3.2.10
 coverage==5.2.1
 Django==3.0.7
 django-cors-headers==3.4.0
-django-graphql-jwt==0.3.1
+django-filter==2.3.0
+django-graphql-auth==0.3.11
+django-graphql-jwt==0.3.0
+graphene==2.1.8
 graphene-django==2.10.1
+graphql-core==2.3.2
+graphql-relay==2.0.1
 gunicorn==20.0.4
+promise==2.3
 psycopg2-binary==2.8.5
+PyJWT==1.7.1
 pytz==2020.1
+Rx==1.6.1
+singledispatch==3.4.0.3
+six==1.15.0
 sqlparse==0.3.1
diff --git a/sizakat/account/email.py b/sizakat/account/email.py
deleted file mode 100644
index d4a8152950708ed5ee69f04ed66024b626606714..0000000000000000000000000000000000000000
--- a/sizakat/account/email.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from django.core.mail import send_mail
-from django.conf import settings
-
-
-def send_reset_password_token(receiver_email, url, user_id, token):
-    subject = 'Reset password akun sizakat'
-    reset_password_url = '{}?userId={}&token={}'.format(
-        url,
-        user_id,
-        token
-    )
-    message = '{}\n{}'.format(
-        'Silahkan buka link berikut untuk mengganti password:',
-        reset_password_url
-    )
-    email_from = settings.EMAIL_HOST_USER
-    recipient_list = [receiver_email]
-    send_mail(subject, message, email_from, recipient_list)
diff --git a/sizakat/account/mutations.py b/sizakat/account/mutations.py
new file mode 100644
index 0000000000000000000000000000000000000000..6659c67173c8f9899b810fa45571531d73edaafe
--- /dev/null
+++ b/sizakat/account/mutations.py
@@ -0,0 +1,23 @@
+import graphene
+
+from graphql_auth import mutations
+from django.db.models import signals
+from graphql_auth.models import UserStatus
+
+
+def verify_user_status(sender, instance, created, **kwargs):
+    if created:
+        instance.verified = True
+        instance.save(update_fields=["verified"])
+
+
+signals.post_save.connect(receiver=verify_user_status, sender=UserStatus)
+
+
+class AccountMutation(graphene.ObjectType):
+    register = mutations.Register.Field()
+    token_auth = mutations.ObtainJSONWebToken.Field()
+    verify_token = mutations.VerifyToken.Field()
+    refresh_token = mutations.RefreshToken.Field()
+    send_password_reset_email = mutations.SendPasswordResetEmail.Field()
+    password_reset = mutations.PasswordReset.Field()
diff --git a/sizakat/account/query.py b/sizakat/account/query.py
new file mode 100644
index 0000000000000000000000000000000000000000..ef7b2ff356279cab3f721cdd40c3b23432a150ad
--- /dev/null
+++ b/sizakat/account/query.py
@@ -0,0 +1,7 @@
+import graphene
+
+from graphql_auth.schema import UserQuery, MeQuery
+
+
+class AccountQuery(UserQuery, MeQuery, graphene.ObjectType):
+    pass
diff --git a/sizakat/account/templates/login.html b/sizakat/account/templates/login.html
deleted file mode 100644
index 0b7344c1166c323c4216565f034ed40afb963534..0000000000000000000000000000000000000000
--- a/sizakat/account/templates/login.html
+++ /dev/null
@@ -1,29 +0,0 @@
-{% if form.errors %}
-<p>Username atau password yang Anda masukkan salah. Harap periksa ulang dan coba lagi.</p>
-{% endif %}
-
-{% if next %}
-{% if user.is_authenticated %}
-<p>Akun Anda tidak memiliki akses ke halaman ini. Silakan
-  gunakan akun yang memiliki akses.</p>
-{% else %}
-<p>Silakan login untuk melanjutkan.</p>
-{% endif %}
-{% endif %}
-
-<form method="post" action="{% url 'login' %}">
-  {% csrf_token %}
-  <table>
-    <tr>
-      <td>{{ form.username.label_tag }}</td>
-      <td>{{ form.username }}</td>
-    </tr>
-    <tr>
-      <td>{{ form.password.label_tag }}</td>
-      <td>{{ form.password }}</td>
-    </tr>
-  </table>
-
-  <input type="submit" value="login">
-  <input type="hidden" name="next" value="/graphql">
-</form>
\ No newline at end of file
diff --git a/sizakat/account/tests.py b/sizakat/account/tests.py
deleted file mode 100644
index d0ec120d57afde2ba8e564f0fb7bdec76f052444..0000000000000000000000000000000000000000
--- a/sizakat/account/tests.py
+++ /dev/null
@@ -1,100 +0,0 @@
-import json
-
-from django.test import Client, TestCase
-from django.contrib.auth import get_user_model
-from django.core import mail
-
-User = get_user_model()
-
-
-class AccountTestCase(TestCase):
-    def setUp(self):
-        user = User.objects.create(username='testuser', email='test@mail.com')
-        user.set_password('12345')
-        user.save()
-
-    def test_user_can_login_via_post_login(self):
-        c = Client()
-        response = c.post(
-            '/login/',
-            json.dumps({'username': 'testuser', 'password': '12345'}),
-            'text/json')
-        logged_in = json.loads(response.content).get('loggedIn', None)
-
-        self.assertEqual(response.status_code, 200)
-        self.assertTrue(logged_in)
-
-    def test_user_get_fail_message_when_login_failed(self):
-        c = Client()
-        response = c.post(
-            '/login/',
-            json.dumps({'username': 'thisuser', 'password': 'willfailed'}),
-            'text/json')
-        logged_in = json.loads(response.content).get('loggedIn', None)
-        self.assertFalse(logged_in)
-
-    def test_user_can_logout_via_post_logout(self):
-        c = Client()
-        log_in = c.post(
-            '/login/',
-            json.dumps({'username': 'testuser', 'password': '12345'}),
-            'text/json')
-        response = c.post('/logout/')
-        logged_out = json.loads(response.content).get('loggedOut', None)
-        self.assertTrue(logged_out)
-
-    def test_user_can_reset_password_and_change_with_valid_token(self):
-        c = Client()
-        response = c.post(
-            '/reset-password/',
-            json.dumps({
-                'email': 'test@mail.com',
-                'changePasswordUrl': 'localhost/reset-password'}),
-            'text/json'
-        )
-        success = json.loads(response.content).get('success', None)
-        self.assertTrue(success)
-
-        # Test that one message has been sent.
-        self.assertEqual(len(mail.outbox), 1)
-
-        # Verify that the subject of the first message is correct.
-        self.assertEqual(mail.outbox[0].subject, 'Reset password akun sizakat')
-
-        email_msg = str(mail.outbox[0].message())
-        user_id = int(email_msg.split('userId=')[1].split('&')[0])
-        token = email_msg.split('token=')[1].split('&')[0]
-
-        new_pass = '54321'
-        change_reset_password = c.post(
-            '/change-reset-password/',
-            json.dumps({'userId': user_id, 'token': token,
-                        'newPassword': new_pass}),
-            'text/json'
-        )
-        self.assertTrue(json.loads(
-            change_reset_password.content).get('success'))
-
-        login_response = c.post(
-            '/login/',
-            json.dumps({'username': 'testuser', 'password': new_pass}),
-            'text/json')
-        self.assertTrue(json.loads(login_response.content).get('loggedIn'))
-
-    def test_session_is_valid_after_logged_in(self):
-        c = Client()
-        response = c.post(
-            '/login/',
-            json.dumps({'username': 'testuser', 'password': '12345'}),
-            'text/json')
-        logged_in = json.loads(response.content).get('loggedIn', None)
-
-        self.assertTrue(logged_in)
-
-        session = json.loads(response.content).get('session', None)
-        auth_headers = {
-            'HTTP_AUTHORIZATION': session,
-        }
-        verify_response = c.get('/verify-session/', **auth_headers)
-        self.assertTrue(json.loads(
-            verify_response.content).get('active', None))
diff --git a/sizakat/account/urls.py b/sizakat/account/urls.py
new file mode 100644
index 0000000000000000000000000000000000000000..948b5b8123c3718a8a6a250e72601a345409f610
--- /dev/null
+++ b/sizakat/account/urls.py
@@ -0,0 +1,7 @@
+from django.urls import path
+
+from .views import reset_password
+
+urlpatterns = [
+    path('reset-password/', reset_password),
+]
diff --git a/sizakat/account/views.py b/sizakat/account/views.py
index 01e0f31994f3be25391b753ff328621bf8e8a312..205fe14fb97f8639da92e1a885f698dba676e0e4 100644
--- a/sizakat/account/views.py
+++ b/sizakat/account/views.py
@@ -1,84 +1,8 @@
-import json
+from django.shortcuts import redirect
+from django.conf import settings
 
-from django.http import JsonResponse
-from django.contrib.auth import authenticate, login, get_user_model, logout
-from django.contrib.auth.tokens import default_token_generator as token_generator
-from django.contrib.sessions.models import Session
-from django.utils import timezone
-from .models import User
-from .email import send_reset_password_token
-import logging
 
-UserModel = get_user_model()
-
-def login_session(request):
-    if request.method == 'POST':
-        request_body = json.loads(request.body.decode('utf-8'))
-        username = request_body.get('username', None)
-        password = request_body.get('password', None)
-        user = authenticate(request, username=username, password=password)
-        if user:
-            login(request, user)
-            session = request.session.session_key
-            return JsonResponse({'loggedIn': True, 'session': session})
-    if request.session.session_key == None:
-        return JsonResponse({'loggedIn': False}, status=404)
-    else:
-        return JsonResponse({'loggedIn': True})
-
-
-def logout_session(request):
-    logout(request)
-    return JsonResponse({'loggedOut': True})
-
-
- 
 def reset_password(request):
-    if request.method == 'POST':
-        request_body = json.loads(request.body.decode('utf-8'))
-        email = request_body.get('email', None)
-        change_password_url = request_body.get('changePasswordUrl', None)
-        try:
-            user = UserModel.objects.get(email=email)
-            token = token_generator.make_token(user)
-            send_reset_password_token(
-                email, change_password_url, user.pk, token)
-            return JsonResponse({'success': True})
-
-        except UserModel.DoesNotExist:
-            return JsonResponse({'success': False}, status=404)
-
-    return JsonResponse({'success': False}, status=405)
-
-
-def change_reset_password(request):
-    if request.method == 'POST':
-        request_body = json.loads(request.body.decode('utf-8'))
-        user_id = request_body.get('userId', None)
-        token = request_body.get('token', None)
-        try:
-            user = UserModel.objects.get(pk=user_id)
-            if token_generator.check_token(user, token):
-                new_pass = request_body.get('newPassword')
-                user.set_password(new_pass)
-                user.save()
-                return JsonResponse({'success': True})
-            else:
-                return JsonResponse({'success': False}, status=401)
-
-        except UserModel.DoesNotExist:
-            return JsonResponse({'success': False}, status=404)
-
-    return JsonResponse({'success': False}, status=405)
-
-
-def verify_session(request):
-    session_key = request.headers.get('Authorization', None)
-    session = Session.objects.filter(session_key=session_key)
-    if session.exists():
-        expire_time = session.get().expire_date
-        if timezone.now() < expire_time:
-            return JsonResponse({'active': True})
-        else:
-            session.get().delete()
-    return JsonResponse({'active': False})
+    token = request.GET.get('token', '')
+    reset_password_url = settings.RESET_PASSWORD_URL
+    return redirect(f'{reset_password_url}?token={token}')
diff --git a/sizakat/schema.py b/sizakat/schema.py
index fabda0e87beb71398c11d415f0a1b7e9d3ed27f8..94d057397b839d6f83e07f8060f7fe3f48df5ec4 100644
--- a/sizakat/schema.py
+++ b/sizakat/schema.py
@@ -7,6 +7,8 @@ from .mustahik.mutations import (
     DataSourceWargaMutation, DataSourceInstitusiMutation,
     DataSourcePekerjaMutation, DeleteDataSource
 )
+from .account.query import AccountQuery
+from .account.mutations import AccountMutation
 from .mustahik.query import MustahikQuery
 from .transaction.query import TransactionQuery
 from .transaction.mutations import (
@@ -18,14 +20,19 @@ ABOUT = ('Si Zakat merupakan sistem informasi untuk membantu masjid dalam '
          'yang dipimpin oleh Prof. Dr. Wisnu Jatmiko.')
 
 
-class Query(MustahikQuery, TransactionQuery, graphene.ObjectType):
+class Query(
+    AccountQuery,
+    MustahikQuery,
+    TransactionQuery,
+    graphene.ObjectType
+):
     about = graphene.String()
 
     def resolve_about(self, info):
         return ABOUT
 
 
-class Mutation(graphene.ObjectType):
+class Mutation(AccountMutation, graphene.ObjectType):
     mustahik_mutation = MustahikMutation.Field()
     delete_mustahik = DeleteMustahik.Field()
     data_source_mutation = DataSourceMutation.Field()
diff --git a/sizakat/settings.py b/sizakat/settings.py
index 0b094c72c65b10e14f2e1da9aa13a37cda67a1bc..913af7ce65462cfa44b65959236732c490109c66 100644
--- a/sizakat/settings.py
+++ b/sizakat/settings.py
@@ -12,6 +12,8 @@ https://docs.djangoproject.com/en/3.0/ref/settings/
 
 import os
 
+from datetime import timedelta
+
 # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 
@@ -23,7 +25,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 SECRET_KEY = os.environ.get('SECRET_KEY')
 
 # SECURITY WARNING: don't run with debug turned on in production!
-DEBUG = os.environ.get("DEBUG", False)
+DEBUG = os.environ.get('DEBUG', False)
 
 ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS').split()
 
@@ -37,7 +39,9 @@ INSTALLED_APPS = [
     'django.contrib.sessions',
     'django.contrib.messages',
     'django.contrib.staticfiles',
+    'django_filters',
     'graphene_django',
+    'graphql_auth',
     'corsheaders',
     'sizakat.mustahik',
     'sizakat.transaction',
@@ -48,6 +52,9 @@ AUTH_USER_MODEL = 'account.User'
 
 GRAPHENE = {
     'SCHEMA': 'sizakat.schema.schema',
+    'MIDDLEWARE': [
+        'graphql_jwt.middleware.JSONWebTokenMiddleware',
+    ],
 }
 
 MIDDLEWARE = [
@@ -61,6 +68,22 @@ MIDDLEWARE = [
     'django.middleware.clickjacking.XFrameOptionsMiddleware',
 ]
 
+AUTHENTICATION_BACKENDS = [
+    'graphql_auth.backends.GraphQLAuthBackend',
+    'django.contrib.auth.backends.ModelBackend',
+]
+
+GRAPHQL_JWT = {
+    'JWT_VERIFY_EXPIRATION': True,
+    'JWT_EXPIRATION_DELTA': timedelta(hours=1),
+}
+
+GRAPHQL_AUTH = {
+    'LOGIN_ALLOWED_FIELDS': ['email'],
+    'SEND_ACTIVATION_EMAIL': False,
+    'PASSWORD_RESET_PATH_ON_EMAIL': 'reset-password',
+}
+
 SESSION_EXPIRE_AT_BROWSER_CLOSE = False
 SESSION_COOKIE_AGE = 60 * 60
 
@@ -69,10 +92,14 @@ CORS_ORIGIN_WHITELIST = os.environ.get(
 
 ROOT_URLCONF = 'sizakat.urls'
 
+RESET_PASSWORD_URL = os.environ.get(
+    'RESET_PASSWORD_URL', 'http://localhost:3000/reset-password/'
+)
+
 TEMPLATES = [
     {
         'BACKEND': 'django.template.backends.django.DjangoTemplates',
-        'DIRS': [],
+        'DIRS': [os.path.join(BASE_DIR, 'templates')],
         'APP_DIRS': True,
         'OPTIONS': {
             'context_processors': [
@@ -155,5 +182,5 @@ EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
 EMAIL_HOST = 'smtp.gmail.com'
 EMAIL_USE_TLS = True
 EMAIL_PORT = 587
-EMAIL_HOST_USER = os.environ.get('GMAIL_USER').split()
-EMAIL_HOST_PASSWORD = os.environ.get('GMAIL_PASSWORD').split()
+EMAIL_HOST_USER = os.environ.get('GMAIL_USER', 'email@mail.com')
+EMAIL_HOST_PASSWORD = os.environ.get('GMAIL_PASSWORD', 'emailpass')
diff --git a/sizakat/urls.py b/sizakat/urls.py
index 90e84676f1ef8f424f04f5dd544f02ac6f210407..5272be9e7ce20957848ffdbc9eba75cef8ea5b02 100644
--- a/sizakat/urls.py
+++ b/sizakat/urls.py
@@ -16,20 +16,14 @@ Including another URLconf
 from django.conf import settings
 from django.conf.urls.static import static
 from django.contrib import admin
-from django.urls import path
+from django.urls import include, path
 from django.views.decorators.csrf import csrf_exempt
 from graphene_django.views import GraphQLView
 
 urlpatterns = [
     path('admin/', admin.site.urls),
     path('graphql/', csrf_exempt(GraphQLView.as_view(graphiql=True))),
-    path('accounts/login/',
-         auth_views.LoginView.as_view(template_name='login.html'), name='login'),
-    path('login/', csrf_exempt(login_session)),
-    path('logout/', csrf_exempt(logout_session)),
-    path('reset-password/', csrf_exempt(reset_password)),
-    path('change-reset-password/', csrf_exempt(change_reset_password)),
-    path('verify-session/', verify_session),
+    path('account/', include('sizakat.account.urls')),
 ]
 
 if settings.DEBUG:
diff --git a/templates/email/password_reset_email.html b/templates/email/password_reset_email.html
new file mode 100644
index 0000000000000000000000000000000000000000..eebb4ebbf3acadfb83efcbe67f7848de137d3aca
--- /dev/null
+++ b/templates/email/password_reset_email.html
@@ -0,0 +1,7 @@
+<h3>SIZAKAT</h3>
+
+<p>Hello {{ user.username }}!</p>
+
+<p>Reset your password on the link:</p>
+
+<p>{{ protocol }}://{{ domain }}/account/{{ path }}/?token={{ token }}</p>
\ No newline at end of file
diff --git a/templates/email/password_reset_subject.txt b/templates/email/password_reset_subject.txt
new file mode 100644
index 0000000000000000000000000000000000000000..63c6b40b443efd86f2f92066abebca3b0519a03d
--- /dev/null
+++ b/templates/email/password_reset_subject.txt
@@ -0,0 +1 @@
+Reset your password on Sizakat
\ No newline at end of file