diff --git a/README.md b/README.md
index 06982f6c059a5ba618042154d628821a6fb5c828..81ceacc918feeb275b6dd2db82a442fc9a40f447 100644
--- a/README.md
+++ b/README.md
@@ -1,48 +1,30 @@
 # Install
 
-> THIS IS JUST A DEMO REPO. IT WORKS ON LOCALHOST ONLY
-
-Clone the repo and make sure to also fetch the submodules.
-```
-git submodule init
-git submodule update
-```
-Refer to the [hydra install guide](https://www.ory.sh/docs/next/hydra/5min-tutorial) to get the demo running. Change the following values in the quickstart.yml file before starting hydra with docker-compose.
-
-```
-   - URLS_SELF_ISSUER=http://oas.example.net:4444
-      - URLS_CONSENT=http://127.0.0.1:5001/consent
-      - URLS_LOGIN=http://127.0.0.1:5000/login
-      - URLS_LOGOUT=http://127.0.0.1:5000/logout
-      - DSN=memory
-      - SECRETS_SYSTEM=youReallyNeedToChangeThis
-      - OIDC_SUBJECT_TYPES_SUPPORTED=public,pairwise
-      - OIDC_SUBJECT_TYPE_PAIRWISE_SALT=youReallyNeedToChangeThis
-    restart: unless-stopped
-
-  consent:
-    environment:
-      - HYDRA_ADMIN_URL=http://127.0.0.1:4445
-```
-
-After Hydra is up and running, start the login provider
-```
-cd login_provider
-source .env/bin/activate
-pip3 install -r requirements.txt
-flask run --port 5000
-```
-and the consent provider
-```
-cd consent_provider
-source .env/bin/activate
-pip3 install -r requirements.txt
-flask run --port 5001
-```
+Installation should be done via the helm using the helmchart contained in `./helmchart`.
+Make sure to edit the values in `./helmchart/values.yaml` according to your needs
 
 # Using SSO
-To use SSO configure your oAuth client (for example netxtcloud) and create a new oAuth client object. To create the client object, please also refer to the ORY Hydra documentation.
 
-To configure your client refer to  `127.0.0.1:4444/.well-known/openid-configuration`.
+To use OpenID Connect or oAuth you need to set up an oAuth Client for every application that
+needs to authenticate it's users. You can leverage the Hydra Admin API to create oAuth clients.
+As a starting point, you can have a look at the script provided in `test/`.
 
-The username and password of the demo user can be found in the login_provider app.py file.
+To use SSO configure your oAuth client (for example netxtcloud) and create a new oAuth client object.
+Refer to `https://sso.oas.example.net/.well-known/openid-configuration` as a reference on how to configure your openID Connect or oAuth client
+
+# Testing
+
+In order to run tests locally, you can start the environment via `docker-compose`.
+To make the test setup work on your machine, install docker and docker-compose and edit
+the `docker-compose.yml` file. Make sure all of the URLS listed in the environment of the hydra
+service are accessible by your agent (usually your browser) and the application that will act
+as an oAuth / OpenID Connect client (for example nextcloud).
+
+Notice that you need to create users and applications before being able to login.
+You can use the scripts located in `user-panel/utils` to create users for testing.
+
+If you don't have a test application yourself, you can use the small OpeinID Connect
+test application located at `test/login_logout/`
+
+Also refer to `.gitlab-ci.yaml` to get an idea on how to run all of the tests that are
+contained in this repository.
diff --git a/consent_provider/app.py b/consent_provider/app.py
index 177ca4ea39e26df64b5f216498a1d55cf6141af5..8cc51ab3e3381c8314226ba5b160e1d04b484d34 100644
--- a/consent_provider/app.py
+++ b/consent_provider/app.py
@@ -10,6 +10,20 @@ app = Flask(__name__)
 
 @app.route('/', methods=['GET'])
 def home():
+    """Checks user app permission
+
+    Checks user app permission, by loading a consent object via the Hydra admin API and
+    validating, that the user triggering the request has sufficient permissions by querying
+    the GraphQL API. If the user is allowed to use the app the request is accepted and openID
+    claims are sent to Hydra.
+
+    Args:
+        consent_challenge: Reference to a consent challenge object that can be requested via the Hydra
+            Admin API (GET)
+
+    Returns:
+        Redirect to the url that is provided by the consent challenge object.
+    """
     hydra = HydraAdmin(HYDRA_ADMIN_URL)
     challenge = request.args.get("consent_challenge")
     if not challenge:
diff --git a/login_provider/app.py b/login_provider/app.py
index ba85dcde002050cfeeb980d0366d86450c5dcb5c..3d60993bfd7395f7252af546765bec6960c863b9 100644
--- a/login_provider/app.py
+++ b/login_provider/app.py
@@ -26,6 +26,20 @@ def user_loader(username):
 @app.route('/', methods=['GET'])
 @login_required
 def home():
+    """Accepts hydra login challenge or renders welcome page
+
+    Connects to the Hydra admin API to accept the login challenge.
+    A reference to this challenge object is passed with via args.login_challenge (GET)
+
+    Args:
+        login_challenge: A alphanumeric id generated by Hydra, that references a login
+            challenge object. The login challenge object can be rejected or accepted via the
+            hydra admin API
+
+    Returns:
+        Redirect that is saved in the challenge object
+        If no challenge reference was passed this function renders a welcome page
+    """
     logout_form = LogoutForm()
     challenge = request.args.get("login_challenge")
     if not challenge:
@@ -36,6 +50,16 @@ def home():
 
 @app.route('/login', methods=['GET', 'POST'])
 def login():
+    """Provides login form and handles Login attempt
+
+    Args:
+        login_form: contains login data submitted by a user (POST)
+        next_url: url that this function redirects to after logging in the user
+
+    Returns:
+        Error page in case the login was unsuccessful
+        Redirect to home page, forwarding the login_challenge in case login was successful
+    """
     login_form = LoginForm()
     if login_form.validate_on_submit():
         user = User(login_form.username.data)
@@ -49,7 +73,6 @@ def login():
     return render_template('login.html', login_form=login_form)
 
 def is_safe_url(url):
-    print(url)
     safe = True if url == "" else False
     safe = True if url == "/" or safe else False
     safe = True if url[:18] == "/?login_challenge=" \
diff --git a/login_provider/db.py b/login_provider/db.py
index 7a0d456c559095dd6b49a4913fa75549a9a34139..3eff8b649b4e93f3888e1aba31949e8518df9326 100644
--- a/login_provider/db.py
+++ b/login_provider/db.py
@@ -15,6 +15,7 @@ class User(UserMixin):
         self._load_remote_user_info()
 
     def _load_remote_user_info(self):
+        """Loads userdata from remote GraphQL API"""
         querystring = '''{{
         getUser(username: "{0}"){{
             email,
@@ -26,6 +27,18 @@ class User(UserMixin):
             self.email = result["data"]["getUser"]["email"]
 
     def _verify_password(self, password):
+        """Verifies cleartext password
+
+        Sends the cleartext password provided to the function to the GraphQL API
+        which verifies the password by hashing it and comparing it to a stored password
+        hash.
+
+        Args:
+            password: cleartext password
+
+        Returns:
+            Boolean result of password verification
+        """
         querystring = '''{{
         verifyPassword(
             username: "{0}",