diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py
index 79edccffa..6647d4b10 100644
--- a/monkey/monkey_island/cc/app.py
+++ b/monkey/monkey_island/cc/app.py
@@ -10,7 +10,8 @@ from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH
 from monkey_island.cc.database import database, mongo
 from monkey_island.cc.resources.attack.attack_config import AttackConfiguration
 from monkey_island.cc.resources.attack.attack_report import AttackReport
-from monkey_island.cc.resources.auth.auth import init_jwt
+from monkey_island.cc.resources.auth.auth import Authenticate, init_jwt
+from monkey_island.cc.resources.auth.registration import Registration
 from monkey_island.cc.resources.bootloader import Bootloader
 from monkey_island.cc.resources.client_run import ClientRun
 from monkey_island.cc.resources.edge import Edge
@@ -31,7 +32,6 @@ from monkey_island.cc.resources.node import Node
 from monkey_island.cc.resources.node_states import NodeStates
 from monkey_island.cc.resources.pba_file_download import PBAFileDownload
 from monkey_island.cc.resources.pba_file_upload import FileUpload
-from monkey_island.cc.resources.registration import Registration
 from monkey_island.cc.resources.remote_run import RemoteRun
 from monkey_island.cc.resources.reporting.report import Report
 from monkey_island.cc.resources.root import Root
@@ -71,9 +71,12 @@ def serve_home():
 
 def init_app_config(app, mongo_url):
     app.config['MONGO_URI'] = mongo_url
-    app.config['SECRET_KEY'] = str(uuid.getnode())
-    app.config['JWT_AUTH_URL_RULE'] = '/api/auth'
-    app.config['JWT_EXPIRATION_DELTA'] = env_singleton.env.get_auth_expiration_time()
+
+    # See https://flask-jwt-extended.readthedocs.io/en/stable/options
+    app.config['JWT_ACCESS_TOKEN_EXPIRES'] = env_singleton.env.get_auth_expiration_time()
+    # Invalidate the signature of JWTs if the server process restarts. This avoids the edge case of getting a JWT,
+    # deciding to reset credentials and then still logging in with the old JWT.
+    app.config['JWT_SECRET_KEY'] = str(uuid.uuid4())
 
 
 def init_app_services(app):
@@ -96,6 +99,7 @@ def init_app_url_rules(app):
 def init_api_resources(api):
     api.add_resource(Root, '/api')
     api.add_resource(Registration, '/api/registration')
+    api.add_resource(Authenticate, '/api/auth')
     api.add_resource(Environment, '/api/environment')
     api.add_resource(Monkey, '/api/monkey', '/api/monkey/', '/api/monkey/<string:guid>')
     api.add_resource(Bootloader, '/api/bootloader/<string:os>')
diff --git a/monkey/monkey_island/cc/environment/__init__.py b/monkey/monkey_island/cc/environment/__init__.py
index e35233c69..fcaa4e156 100644
--- a/monkey/monkey_island/cc/environment/__init__.py
+++ b/monkey/monkey_island/cc/environment/__init__.py
@@ -23,7 +23,7 @@ class Environment(object, metaclass=ABCMeta):
     _MONGO_URL = os.environ.get("MONKEY_MONGO_URL",
                                 "mongodb://{0}:{1}/{2}".format(_MONGO_DB_HOST, _MONGO_DB_PORT, str(_MONGO_DB_NAME)))
     _DEBUG_SERVER = False
-    _AUTH_EXPIRATION_TIME = timedelta(hours=1)
+    _AUTH_EXPIRATION_TIME = timedelta(minutes=30)
 
     _testing = False
 
diff --git a/monkey/monkey_island/cc/resources/attack/attack_config.py b/monkey/monkey_island/cc/resources/attack/attack_config.py
index e8889a487..532b1fb4f 100644
--- a/monkey/monkey_island/cc/resources/attack/attack_config.py
+++ b/monkey/monkey_island/cc/resources/attack/attack_config.py
@@ -8,7 +8,7 @@ __author__ = "VakarisZ"
 
 
 class AttackConfiguration(flask_restful.Resource):
-    @jwt_required()
+    @jwt_required
     def get(self):
         return current_app.response_class(json.dumps({"configuration": AttackConfig.get_config()},
                                                      indent=None,
@@ -16,7 +16,7 @@ class AttackConfiguration(flask_restful.Resource):
                                                      sort_keys=False) + "\n",
                                           mimetype=current_app.config['JSONIFY_MIMETYPE'])
 
-    @jwt_required()
+    @jwt_required
     def post(self):
         """
         Based on request content this endpoint either resets ATT&CK configuration or updates it.
diff --git a/monkey/monkey_island/cc/resources/attack/attack_report.py b/monkey/monkey_island/cc/resources/attack/attack_report.py
index e113dfa76..779c436c5 100644
--- a/monkey/monkey_island/cc/resources/attack/attack_report.py
+++ b/monkey/monkey_island/cc/resources/attack/attack_report.py
@@ -10,7 +10,7 @@ __author__ = "VakarisZ"
 
 class AttackReport(flask_restful.Resource):
 
-    @jwt_required()
+    @jwt_required
     def get(self):
         response_content = {'techniques': AttackReportService.get_latest_report()['techniques'], 'schema': SCHEMA}
         return current_app.response_class(json.dumps(response_content,
diff --git a/monkey/monkey_island/cc/resources/auth/auth.py b/monkey/monkey_island/cc/resources/auth/auth.py
index 24176cdf6..71611221c 100644
--- a/monkey/monkey_island/cc/resources/auth/auth.py
+++ b/monkey/monkey_island/cc/resources/auth/auth.py
@@ -1,40 +1,67 @@
+import json
+import logging
 from functools import wraps
 
-from flask import abort, current_app
-from flask_jwt import JWT, JWTError, _jwt_required
+import flask_jwt_extended
+import flask_restful
+from flask import make_response, request
+from flask_jwt_extended.exceptions import JWTExtendedException
+from jwt import PyJWTError
 from werkzeug.security import safe_str_cmp
 
 import monkey_island.cc.environment.environment_singleton as env_singleton
 import monkey_island.cc.resources.auth.user_store as user_store
 
-__author__ = 'itay.mizeretz'
+logger = logging.getLogger(__name__)
 
 
 def init_jwt(app):
     user_store.UserStore.set_users(env_singleton.env.get_auth_users())
+    _ = flask_jwt_extended.JWTManager(app)
+    logger.debug("Initialized JWT with secret key that started with " + app.config["JWT_SECRET_KEY"][:4])
 
-    def authenticate(username, secret):
+
+class Authenticate(flask_restful.Resource):
+    """
+    Resource for user authentication. The user provides the username and hashed password and we give them a JWT.
+    See `AuthService.js` file for the frontend counterpart for this code.
+    """
+    @staticmethod
+    def _authenticate(username, secret):
         user = user_store.UserStore.username_table.get(username, None)
         if user and safe_str_cmp(user.secret.encode('utf-8'), secret.encode('utf-8')):
             return user
 
-    def identity(payload):
-        user_id = payload['identity']
-        return user_store.UserStore.user_id_table.get(user_id, None)
-
-    JWT(app, authenticate, identity)
+    def post(self):
+        """
+        Example request:
+        {
+            "username": "my_user",
+            "password": "343bb87e553b05430e5c44baf99569d4b66..."
+        }
+        """
+        credentials = json.loads(request.data)
+        # Unpack auth info from request
+        username = credentials["username"]
+        secret = credentials["password"]
+        # If the user and password have been previously registered
+        if self._authenticate(username, secret):
+            access_token = flask_jwt_extended.create_access_token(identity=user_store.UserStore.username_table[username].id)
+            logger.debug(f"Created access token for user {username} that begins with {access_token[:4]}")
+            return make_response({"access_token": access_token, "error": ""}, 200)
+        else:
+            return make_response({"error": "Invalid credentials"}, 401)
 
 
-def jwt_required(realm=None):
-    def wrapper(fn):
-        @wraps(fn)
-        def decorator(*args, **kwargs):
-            try:
-                _jwt_required(realm or current_app.config['JWT_DEFAULT_REALM'])
-                return fn(*args, **kwargs)
-            except JWTError:
-                abort(401)
-
-        return decorator
+# See https://flask-jwt-extended.readthedocs.io/en/stable/custom_decorators/
+def jwt_required(fn):
+    @wraps(fn)
+    def wrapper(*args, **kwargs):
+        try:
+            flask_jwt_extended.verify_jwt_in_request()
+            return fn(*args, **kwargs)
+        # Catch authentication related errors in the verification or inside the called function. All other exceptions propagate
+        except (JWTExtendedException, PyJWTError) as e:
+            return make_response({"error": f"Authentication error: {str(e)}"}, 401)
 
     return wrapper
diff --git a/monkey/monkey_island/cc/resources/registration.py b/monkey/monkey_island/cc/resources/auth/registration.py
similarity index 100%
rename from monkey/monkey_island/cc/resources/registration.py
rename to monkey/monkey_island/cc/resources/auth/registration.py
diff --git a/monkey/monkey_island/cc/resources/island_configuration.py b/monkey/monkey_island/cc/resources/island_configuration.py
index deda3e251..b8a556016 100644
--- a/monkey/monkey_island/cc/resources/island_configuration.py
+++ b/monkey/monkey_island/cc/resources/island_configuration.py
@@ -8,12 +8,12 @@ from monkey_island.cc.services.config import ConfigService
 
 
 class IslandConfiguration(flask_restful.Resource):
-    @jwt_required()
+    @jwt_required
     def get(self):
         return jsonify(schema=ConfigService.get_config_schema(),
                        configuration=ConfigService.get_config(False, True, True))
 
-    @jwt_required()
+    @jwt_required
     def post(self):
         config_json = json.loads(request.data)
         if 'reset' in config_json:
diff --git a/monkey/monkey_island/cc/resources/island_logs.py b/monkey/monkey_island/cc/resources/island_logs.py
index 5ef64789b..5d1d6d276 100644
--- a/monkey/monkey_island/cc/resources/island_logs.py
+++ b/monkey/monkey_island/cc/resources/island_logs.py
@@ -11,7 +11,7 @@ logger = logging.getLogger(__name__)
 
 
 class IslandLog(flask_restful.Resource):
-    @jwt_required()
+    @jwt_required
     def get(self):
         try:
             return IslandLogService.get_log_file()
diff --git a/monkey/monkey_island/cc/resources/log.py b/monkey/monkey_island/cc/resources/log.py
index 67f4e5e47..0d437d174 100644
--- a/monkey/monkey_island/cc/resources/log.py
+++ b/monkey/monkey_island/cc/resources/log.py
@@ -14,7 +14,7 @@ __author__ = "itay.mizeretz"
 
 
 class Log(flask_restful.Resource):
-    @jwt_required()
+    @jwt_required
     def get(self):
         monkey_id = request.args.get('id')
         exists_monkey_id = request.args.get('exists')
diff --git a/monkey/monkey_island/cc/resources/monkey_configuration.py b/monkey/monkey_island/cc/resources/monkey_configuration.py
index d692b8690..e6b94cf81 100644
--- a/monkey/monkey_island/cc/resources/monkey_configuration.py
+++ b/monkey/monkey_island/cc/resources/monkey_configuration.py
@@ -10,11 +10,11 @@ __author__ = 'Barak'
 
 
 class MonkeyConfiguration(flask_restful.Resource):
-    @jwt_required()
+    @jwt_required
     def get(self):
         return jsonify(schema=ConfigService.get_config_schema(), configuration=ConfigService.get_config(False, True))
 
-    @jwt_required()
+    @jwt_required
     def post(self):
         config_json = json.loads(request.data)
         if 'reset' in config_json:
diff --git a/monkey/monkey_island/cc/resources/netmap.py b/monkey/monkey_island/cc/resources/netmap.py
index d9aad5bcc..899dc478c 100644
--- a/monkey/monkey_island/cc/resources/netmap.py
+++ b/monkey/monkey_island/cc/resources/netmap.py
@@ -8,7 +8,7 @@ __author__ = 'Barak'
 
 
 class NetMap(flask_restful.Resource):
-    @jwt_required()
+    @jwt_required
     def get(self, **kw):
         net_nodes = NetNodeService.get_all_net_nodes()
         net_edges = NetEdgeService.get_all_net_edges()
diff --git a/monkey/monkey_island/cc/resources/node.py b/monkey/monkey_island/cc/resources/node.py
index 6816e7142..ff630b9a4 100644
--- a/monkey/monkey_island/cc/resources/node.py
+++ b/monkey/monkey_island/cc/resources/node.py
@@ -8,7 +8,7 @@ __author__ = 'Barak'
 
 
 class Node(flask_restful.Resource):
-    @jwt_required()
+    @jwt_required
     def get(self):
         node_id = request.args.get('id')
         if node_id:
diff --git a/monkey/monkey_island/cc/resources/node_states.py b/monkey/monkey_island/cc/resources/node_states.py
index a75eb3ec7..0b50ac34c 100644
--- a/monkey/monkey_island/cc/resources/node_states.py
+++ b/monkey/monkey_island/cc/resources/node_states.py
@@ -6,6 +6,6 @@ from monkey_island.cc.services.utils.node_states import \
 
 
 class NodeStates(flask_restful.Resource):
-    @jwt_required()
+    @jwt_required
     def get(self):
         return {'node_states': [state.value for state in NodeStateList]}
diff --git a/monkey/monkey_island/cc/resources/pba_file_upload.py b/monkey/monkey_island/cc/resources/pba_file_upload.py
index 1b63d3a7b..b18fd7b2f 100644
--- a/monkey/monkey_island/cc/resources/pba_file_upload.py
+++ b/monkey/monkey_island/cc/resources/pba_file_upload.py
@@ -27,7 +27,7 @@ class FileUpload(flask_restful.Resource):
         # Create all directories on the way if they don't exist
         UPLOADS_DIR.mkdir(parents=True, exist_ok=True)
 
-    @jwt_required()
+    @jwt_required
     def get(self, file_type):
         """
         Sends file to filepond
@@ -41,7 +41,7 @@ class FileUpload(flask_restful.Resource):
             filename = ConfigService.get_config_value(copy.deepcopy(PBA_WINDOWS_FILENAME_PATH))
         return send_from_directory(UPLOADS_DIR, filename)
 
-    @jwt_required()
+    @jwt_required
     def post(self, file_type):
         """
         Receives user's uploaded file from filepond
@@ -55,7 +55,7 @@ class FileUpload(flask_restful.Resource):
             status=200, mimetype='text/plain')
         return response
 
-    @jwt_required()
+    @jwt_required
     def delete(self, file_type):
         """
         Deletes file that has been deleted on the front end
diff --git a/monkey/monkey_island/cc/resources/remote_run.py b/monkey/monkey_island/cc/resources/remote_run.py
index fce91098a..0e80f25c0 100644
--- a/monkey/monkey_island/cc/resources/remote_run.py
+++ b/monkey/monkey_island/cc/resources/remote_run.py
@@ -24,7 +24,7 @@ class RemoteRun(flask_restful.Resource):
         island_ip = request_body.get('island_ip')
         return RemoteRunAwsService.run_aws_monkeys(instances, island_ip)
 
-    @jwt_required()
+    @jwt_required
     def get(self):
         action = request.args.get('action')
         if action == 'list_aws':
@@ -43,7 +43,7 @@ class RemoteRun(flask_restful.Resource):
 
         return {}
 
-    @jwt_required()
+    @jwt_required
     def post(self):
         body = json.loads(request.data)
         resp = {}
diff --git a/monkey/monkey_island/cc/resources/reporting/report.py b/monkey/monkey_island/cc/resources/reporting/report.py
index ca1ce395f..a0ea8b0b9 100644
--- a/monkey/monkey_island/cc/resources/reporting/report.py
+++ b/monkey/monkey_island/cc/resources/reporting/report.py
@@ -21,7 +21,7 @@ __author__ = ["itay.mizeretz", "shay.nehmad"]
 
 class Report(flask_restful.Resource):
 
-    @jwt_required()
+    @jwt_required
     def get(self, report_type=SECURITY_REPORT_TYPE, report_data=None):
         if report_type == SECURITY_REPORT_TYPE:
             return ReportService.get_report()
diff --git a/monkey/monkey_island/cc/resources/root.py b/monkey/monkey_island/cc/resources/root.py
index d3a374454..7463a9857 100644
--- a/monkey/monkey_island/cc/resources/root.py
+++ b/monkey/monkey_island/cc/resources/root.py
@@ -26,15 +26,15 @@ class Root(flask_restful.Resource):
         if not action:
             return self.get_server_info()
         elif action == "reset":
-            return jwt_required()(Database.reset_db)()
+            return jwt_required(Database.reset_db)()
         elif action == "killall":
-            return jwt_required()(InfectionLifecycle.kill_all)()
+            return jwt_required(InfectionLifecycle.kill_all)()
         elif action == "is-up":
             return {'is-up': True}
         else:
             return make_response(400, {'error': 'unknown action'})
 
-    @jwt_required()
+    @jwt_required
     def get_server_info(self):
         return jsonify(
             ip_addresses=local_ip_addresses(),
diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py
index f6c58af40..efdeb34b3 100644
--- a/monkey/monkey_island/cc/resources/telemetry.py
+++ b/monkey/monkey_island/cc/resources/telemetry.py
@@ -20,7 +20,7 @@ logger = logging.getLogger(__name__)
 
 
 class Telemetry(flask_restful.Resource):
-    @jwt_required()
+    @jwt_required
     def get(self, **kw):
         monkey_guid = request.args.get('monkey_guid')
         telem_category = request.args.get('telem_category')
diff --git a/monkey/monkey_island/cc/resources/telemetry_feed.py b/monkey/monkey_island/cc/resources/telemetry_feed.py
index 3814c841a..c278d2f36 100644
--- a/monkey/monkey_island/cc/resources/telemetry_feed.py
+++ b/monkey/monkey_island/cc/resources/telemetry_feed.py
@@ -16,7 +16,7 @@ __author__ = 'itay.mizeretz'
 
 
 class TelemetryFeed(flask_restful.Resource):
-    @jwt_required()
+    @jwt_required
     def get(self, **kw):
         timestamp = request.args.get('timestamp')
         if "null" == timestamp or timestamp is None:  # special case to avoid ugly JS code...
diff --git a/monkey/monkey_island/cc/resources/test/clear_caches.py b/monkey/monkey_island/cc/resources/test/clear_caches.py
index b2f23de0a..34401b318 100644
--- a/monkey/monkey_island/cc/resources/test/clear_caches.py
+++ b/monkey/monkey_island/cc/resources/test/clear_caches.py
@@ -17,7 +17,7 @@ class ClearCaches(flask_restful.Resource):
     so we use this to clear the caches.
     :note: DO NOT CALL THIS IN PRODUCTION CODE as this will slow down the user experience.
     """
-    @jwt_required()
+    @jwt_required
     def get(self, **kw):
         try:
             logger.warning("Trying to clear caches! Make sure this is not production")
diff --git a/monkey/monkey_island/cc/resources/test/log_test.py b/monkey/monkey_island/cc/resources/test/log_test.py
index 79f82f5c9..a9c4f8b62 100644
--- a/monkey/monkey_island/cc/resources/test/log_test.py
+++ b/monkey/monkey_island/cc/resources/test/log_test.py
@@ -7,7 +7,7 @@ from monkey_island.cc.resources.auth.auth import jwt_required
 
 
 class LogTest(flask_restful.Resource):
-    @jwt_required()
+    @jwt_required
     def get(self):
         find_query = json_util.loads(request.args.get('find_query'))
         log = mongo.db.log.find_one(find_query)
diff --git a/monkey/monkey_island/cc/resources/test/monkey_test.py b/monkey/monkey_island/cc/resources/test/monkey_test.py
index b97589d24..da8333479 100644
--- a/monkey/monkey_island/cc/resources/test/monkey_test.py
+++ b/monkey/monkey_island/cc/resources/test/monkey_test.py
@@ -7,7 +7,7 @@ from monkey_island.cc.resources.auth.auth import jwt_required
 
 
 class MonkeyTest(flask_restful.Resource):
-    @jwt_required()
+    @jwt_required
     def get(self, **kw):
         find_query = json_util.loads(request.args.get('find_query'))
         return {'results': list(mongo.db.monkey.find(find_query))}
diff --git a/monkey/monkey_island/cc/resources/zero_trust/finding_event.py b/monkey/monkey_island/cc/resources/zero_trust/finding_event.py
index 0725723d5..8a1879c9c 100644
--- a/monkey/monkey_island/cc/resources/zero_trust/finding_event.py
+++ b/monkey/monkey_island/cc/resources/zero_trust/finding_event.py
@@ -9,6 +9,6 @@ from monkey_island.cc.services.reporting.zero_trust_service import \
 
 class ZeroTrustFindingEvent(flask_restful.Resource):
 
-    @jwt_required()
+    @jwt_required
     def get(self, finding_id: str):
         return {'events_json': json.dumps(ZeroTrustService.get_events_by_finding(finding_id), default=str)}
diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RegisterPage.js b/monkey/monkey_island/cc/ui/src/components/pages/RegisterPage.js
index 62ff0e170..88905d805 100644
--- a/monkey/monkey_island/cc/ui/src/components/pages/RegisterPage.js
+++ b/monkey/monkey_island/cc/ui/src/components/pages/RegisterPage.js
@@ -26,13 +26,13 @@ class RegisterPageComponent extends React.Component {
   };
 
   setNoAuth = () => {
-    let options = {}
+    let options = {};
     options['headers'] = {
       'Accept': 'application/json',
       'Content-Type': 'application/json'
     };
-    options['method'] = 'PATCH'
-    options['body'] = JSON.stringify({'server_config': 'standard'})
+    options['method'] = 'PATCH';
+    options['body'] = JSON.stringify({'server_config': 'standard'});
 
     return fetch(this.NO_AUTH_API_ENDPOINT, options)
       .then(res => {
diff --git a/monkey/monkey_island/cc/ui/src/services/AuthService.js b/monkey/monkey_island/cc/ui/src/services/AuthService.js
index d6ecb24ef..e1db4186c 100644
--- a/monkey/monkey_island/cc/ui/src/services/AuthService.js
+++ b/monkey/monkey_island/cc/ui/src/services/AuthService.js
@@ -83,7 +83,7 @@ export default class AuthService {
     };
 
     if (this._loggedIn()) {
-      headers['Authorization'] = 'JWT ' + this._getToken();
+      headers['Authorization'] = 'Bearer ' + this._getToken();
     }
 
     if (options.hasOwnProperty('headers')) {
@@ -97,6 +97,9 @@ export default class AuthService {
     return fetch(url, options)
       .then(res => {
         if (res.status === 401) {
+          res.clone().json().then(res_json => {
+            console.log('Got 401 from server while trying to authFetch: ' + JSON.stringify(res_json));
+          });
           this._removeToken();
         }
         return res;
@@ -156,6 +159,4 @@ export default class AuthService {
   _toHexStr(byteArr) {
     return byteArr.reduce((acc, x) => (acc + ('0' + x.toString(0x10)).slice(-2)), '');
   }
-
-
 }
diff --git a/monkey/monkey_island/requirements.txt b/monkey/monkey_island/requirements.txt
index 59428bd0d..88af6bad0 100644
--- a/monkey/monkey_island/requirements.txt
+++ b/monkey/monkey_island/requirements.txt
@@ -1,4 +1,4 @@
-Flask-JWT>=0.3.2
+Flask-JWT-Extended==3.24.1
 Flask-Pymongo>=2.3.0
 Flask-Restful>=0.3.8
 PyInstaller==3.6
@@ -25,3 +25,5 @@ tqdm>=4.47
 virtualenv>=20.0.26
 werkzeug>=1.0.1
 wheel>=0.34.2
+
+pyjwt>=1.5.1 # not directly required, pinned by Snyk to avoid a vulnerability
\ No newline at end of file