diff --git a/backend/app.py b/backend/app.py
index 97f6ffbbe18701768f8e96a8958883d047587c0d..9d5aec625a14e7cf570894c18eb0d5a2d665b205 100644
--- a/backend/app.py
+++ b/backend/app.py
@@ -35,6 +35,24 @@ from helpers import (
 from config import *
 import logging
 
+# Configure logging.
+from logging.config import dictConfig
+dictConfig({
+    'version': 1,
+    'formatters': {'default': {
+        'format': '[%(asctime)s] %(levelname)s in %(module)s: %(message)s',
+    }},
+    'handlers': {'wsgi': {
+        'class': 'logging.StreamHandler',
+        'stream': 'ext://flask.logging.wsgi_errors_stream',
+        'formatter': 'default',
+    }},
+    'root': {
+        'level': 'INFO',
+        'handlers': ['wsgi'],
+    }
+})
+
 app = Flask(__name__)
 
 app.config["SECRET_KEY"] = SECRET_KEY
@@ -47,6 +65,7 @@ db.init_app(app)
 
 
 app.logger.setLevel(logging.INFO)
+app.logger.info("Starting dashboard backend.")
 
 app.register_blueprint(api_v1)
 app.register_blueprint(web)
diff --git a/backend/areas/apps/apps.py b/backend/areas/apps/apps.py
index b677411827ee220f198c2b66fe3c0733ff7463f5..1bd55644f0a7b985062e8897423ed430318b247c 100644
--- a/backend/areas/apps/apps.py
+++ b/backend/areas/apps/apps.py
@@ -23,7 +23,7 @@ CONFIG_DATA = [
 @cross_origin()
 def get_apps():
     """Return data about all apps"""
-    apps = AppsService.get_all_apps()
+    apps = AppsService.get_accessible_apps()
     return jsonify(apps)
 
 
diff --git a/backend/areas/apps/apps_service.py b/backend/areas/apps/apps_service.py
index 665b4fed6fdae2a98b48b871344135767bc240bb..289529de75435c538e4796a36945a9ddaeab9d83 100644
--- a/backend/areas/apps/apps_service.py
+++ b/backend/areas/apps/apps_service.py
@@ -1,4 +1,12 @@
+from flask import current_app
+from flask_jwt_extended import get_jwt
+import ory_kratos_client
+from ory_kratos_client.api import v0alpha2_api as kratos_api
+
 from .models import App, AppRole
+from config import *
+from helpers.access_control import user_has_access
+from helpers.kratos_user import KratosUser
 
 class AppsService:
     @staticmethod
@@ -6,6 +14,24 @@ class AppsService:
         apps = App.query.all()
         return [app.to_dict() for app in apps]
 
+    @staticmethod
+    def get_accessible_apps():
+        apps = App.query.all()
+
+        kratos_admin_api_configuration = ory_kratos_client.Configuration(host=KRATOS_ADMIN_URL, discard_unknown_keys=True)
+        KRATOS_ADMIN = kratos_api.V0alpha2Api(ory_kratos_client.ApiClient(kratos_admin_api_configuration))
+
+        user_id = get_jwt()['user_id']
+        current_app.logger.info(f"user_id: {user_id}")
+        # Get the related user object
+        current_app.logger.info(f"Info: Getting user from admin {user_id}")
+        user = KratosUser(KRATOS_ADMIN, user_id)
+        if not user:
+            current_app.logger.error(f"User not found in database: {user_id}")
+            return []
+
+        return [app.to_dict() for app in apps if user_has_access(user, app)]
+
     @staticmethod
     def get_app(slug):
         app = App.query.filter_by(slug=slug).first()
diff --git a/backend/areas/roles/models.py b/backend/areas/roles/models.py
index d822901c933568dd62e575675e0afa68565aa29f..b761bb579ebc9133b86c8822c724f1eda2112a97 100644
--- a/backend/areas/roles/models.py
+++ b/backend/areas/roles/models.py
@@ -3,6 +3,7 @@ from database import db
 
 
 class Role(db.Model):
+    ADMIN_ROLE_ID = 1
     NO_ACCESS_ROLE_ID = 3
 
     id = db.Column(Integer, primary_key=True)
diff --git a/backend/areas/roles/role_service.py b/backend/areas/roles/role_service.py
index 90ad064f40f5149054bfd5287ef918a2d719911a..3520b273e75026410d095ccbf7df26a6b097728b 100644
--- a/backend/areas/roles/role_service.py
+++ b/backend/areas/roles/role_service.py
@@ -15,4 +15,4 @@ class RoleService:
     @staticmethod
     def is_user_admin(userId):
         dashboard_role_id = AppRole.query.filter_by(user_id=userId, app_id=1).first().role_id
-        return dashboard_role_id == 1
\ No newline at end of file
+        return dashboard_role_id == 1
diff --git a/backend/helpers/access_control.py b/backend/helpers/access_control.py
new file mode 100644
index 0000000000000000000000000000000000000000..347230d5904f633364eb76621f672847fc3362d4
--- /dev/null
+++ b/backend/helpers/access_control.py
@@ -0,0 +1,43 @@
+from flask import current_app
+
+from areas.apps.models import App, AppRole
+from areas.roles.models import Role
+from config import *
+from database import db
+
+def user_has_access(user, app):
+    # Get role on dashboard
+    dashboard_app = db.session.query(App).filter(
+        App.slug == 'dashboard').first()
+    if not dashboard_app:
+        current_app.logger.error("Dashboard app not found in database.")
+        return False
+    role_object = (
+        db.session.query(AppRole)
+        .filter(AppRole.app_id == dashboard_app.id)
+        .filter(AppRole.user_id == user.uuid)
+        .first()
+    )
+    if role_object is None:
+        current_app.logger.info(f"No dashboard role set for user {user.uuid}.")
+        return False
+
+    # If the user is dashboard admin, they have access to everything.
+    if role_object.role_id == Role.ADMIN_ROLE_ID:
+        current_app.logger.info(f"User {user.uuid} has admin dashboard role")
+        return True
+
+    # Get role for app.
+    role_object = (
+        db.session.query(AppRole)
+        .filter(AppRole.app_id == app.id)
+        .filter(AppRole.user_id == user.uuid)
+        .first()
+    )
+    # Role ID 3 is always "No access" due to migration b514cca2d47b
+    if role_object is None or role_object.role_id is None or role_object.role_id == Role.NO_ACCESS_ROLE_ID:
+        current_app.logger.info(f"User {user.uuid} has no access for: {app.name}")
+        return False
+
+    # In all other cases, access is granted.
+    return True
diff --git a/backend/helpers/kratos_user.py b/backend/helpers/kratos_user.py
index 523f67b0f6736a274c2dfae910b0ff7a4eea7471..dee31b4c229a513154c55eb12d72c0c1d7a65766 100644
--- a/backend/helpers/kratos_user.py
+++ b/backend/helpers/kratos_user.py
@@ -9,8 +9,6 @@ import urllib.request
 from typing import Dict
 from urllib.request import Request
 
-# Some imports commented out to satisfy pylint. They will be used once more
-# functions are migrated to this model
 from ory_kratos_client.model.admin_create_identity_body import AdminCreateIdentityBody
 from ory_kratos_client.model.admin_create_self_service_recovery_link_body \
     import AdminCreateSelfServiceRecoveryLinkBody
diff --git a/backend/web/login/login.py b/backend/web/login/login.py
index acd61288c56146acb907648c3583949ee6ae991d..94ee4c37036fce38e47dcfe7c70e8ba87f4aac2d 100644
--- a/backend/web/login/login.py
+++ b/backend/web/login/login.py
@@ -289,8 +289,9 @@ def consent():
             )
 
     # Resolve to which app the client_id belongs.
-    app_obj = db.session.query(OAuthClientApp).filter(OAuthClientApp.oauthclient_id == client_id).first().app
-    if not app_obj:
+    try:
+        app_obj = db.session.query(OAuthClientApp).filter(OAuthClientApp.oauthclient_id == client_id).first().app
+    except AttributeError:
         current_app.logger.error(f"Could not find app for client {client_id}")
         return redirect(
             consent_request.reject(
diff --git a/backend/web/templates/settings.html b/backend/web/templates/settings.html
index 60ad59534bd36e20ca2579a60e6cf0550b1ccda4..8cb290afa9f0d5e09b34177b1e0ec74529e2b2cd 100644
--- a/backend/web/templates/settings.html
+++ b/backend/web/templates/settings.html
@@ -19,7 +19,7 @@
     <div id="contentMessages"></div>
     <div id="contentProfileSaved" 
         class='alert alert-success' 
-        style='display:none'>Successfuly saved new settings.</div>
+        style='display:none'>Successfully saved new settings.</div>
     <div id="contentProfileSaveFailed" 
         class='alert alert-danger' 
         style='display:none'>Your changes are not saved. Please check the fields for errors.</div>
diff --git a/frontend/src/modules/login/LoginCallback.tsx b/frontend/src/modules/login/LoginCallback.tsx
index aa3d992094b2261ceff91c8854f2639e1839d4e7..54a55edfb7132a5825e9949bd6b89c88901bce0f 100644
--- a/frontend/src/modules/login/LoginCallback.tsx
+++ b/frontend/src/modules/login/LoginCallback.tsx
@@ -50,7 +50,7 @@ export function LoginCallback() {
               />
             </svg>
           </div>
-          <p className="text-lg text-primary-600 mt-2">Logging You in, just a moment.</p>
+          <p className="text-lg text-primary-600 mt-2">Logging you in, just a moment.</p>
         </div>
       </div>
     </div>