diff --git a/docs/local_dev_remote_kratos.md b/docs/local_dev_remote_kratos.md
index 496a6880d147448e6b8a53ba087e0ca210910610..9bf9b0856262a4d55eba31b15a3ea27cdb374789 100644
--- a/docs/local_dev_remote_kratos.md
+++ b/docs/local_dev_remote_kratos.md
@@ -2,23 +2,43 @@
 
 # Introduction
 
-kratos manage the user database. It has profiles of all users and keeps track
-of lost password policies, welcome e-mails, TOTP (future), names.
-
-Kratos is a flexible identity manager where our own "schema" can be defined with
-the information we want for Stackspin.
-
-Kratos has a public API, which should be accessible for the world, and an admin API
-which is ONLY accessible for our panel/board to manage users.
-
-At the point of writing BOTH end-points are not public yet. We can use SSH port
-forwards during development.
+The main role for this repo is provide Single-Sign-On. The architecture to make
+this happen has a lot of moving components. A quick overview:
+
+ - Hydra: Hydra is an Identity Provider, or IdP for short. It means connected
+   applications connect to Hydra to start a session with a user. Hydra provides
+   the application with the username and other roles/claims for the application. 
+   This is done using the OIDC protocol. Hydra is developed by Ory and has
+   security as one of their top priorities. Also it is fully OpenSource.
+
+ - Login application: If hydra hits a new session/user, it has to know if this
+   user has access. To do so, the user has to login. Hydra does not support
+   this, so it will redirect to a login application. This is developed by the
+   Stackspin team (Greenhost) and part of this repository. It is a Python Flask
+   application. 
+   Because the security decisions made by kratos (see below), a lot of the 
+   interaction is done in the web-browser, rather then server-side. 
+   This means the login application has an UI component which relies heavily on 
+   JavaScript. As this is a relatively small application, it is based on
+   traditional Bootstrap + Jquery. This elements the requirement for yet an
+   other build environment.
+
+ - Kratos: This is Identity Manager and contains all the user profiles and
+   secrets (passwords). Kratos is designed to work mostly between web-browser
+   and kratos directly, however, it only provides an API and not UI itself.
+   Kratos provides a Admin API, which is only used from the server-side flask
+   app to create/delete users.
+
+ - Postgres: All three components need to store data. This is done in a postgres
+   database server. There is once instance, with three databases. As all
+   databases are very small this will not lead to resource limitation problems.
 
 # Installation
 
-The current kratos and the login panel is not yet installed available in released 
-versions of Stackspin. However, this does not prevent us from developing already with
-kratos. To use / add the kratos backend, the following needs to be done:
+The current login panel is not yet installed available in released  versions 
+of Stackspin. However, this does not prevent us from developing already on the
+login panel. Experience with `helm` and `kubernetes` is expected when you follow
+this manual.
 
 On your provisioning machine, make sure to checkout:
 
@@ -30,9 +50,9 @@ want to test / install (optional) improvements of login panel.
 Once this is all fetched, installation can be done with the following steps:
 
 1. Suspend the automatic updating:
-   As we are gonna use a non-release version, the flux application management system will rollback
-   changes to follow the released versions. However, during development we want
-   to prevent this. We can suspend the service with:
+   As we are gonna use a non-release version, the flux application management 
+   system will rollback changes to follow the released versions. However, during 
+   development we want to prevent this. We can suspend the service with:
 
 
 ```
@@ -46,13 +66,14 @@ flux suspend source chart stackspin-single-sign-on
 helm get values single-sign-on -n stackspin > /to/a/path/my_cluster_values.yaml
 ```
 
-Keep this file on a safe place. It contains important password and also we need
-to add settings to this file to make local development work.
+Keep this file on a safe place. It contains important passwords and also we need
+to add extra settings to this file once we switch to local development for the
+login panel.
 
 3. Install all helm dependencies
 
 Before we install the dependencies, you can remove the `charts` folder, just to
-be sure there is no other conflicting helm chart in that folder which can lead
+be sure there are no other conflicting helm charts in that folder which can lead
 to unexpected results
 
 ```
@@ -71,8 +92,8 @@ It is not advices to change `values.yaml` directly, but use your
 You can change the default passwords before installation. Please make sure it
 in sync with the dsn settings for Hydra and Kratos. Note that the databases are
 only created once, and passwords are set at creation time. If you want to change
-the passwords later, you have to this manually in the Postgress database and use
-your variables file to modify the settings for kratos/hydra.
+the passwords later, you have to do this manually in the Postgres database and 
+use your variables file to modify the settings for kratos/hydra.
 
 The database passwords are set here:
 
@@ -105,29 +126,30 @@ hydra:
 ```
 
 For local development, we have to configure the endpoint of the application to
-be pointing to our development system. In this example, we use localhost on
+be pointing to our development system. In this example, we use `localhost` on
 http.
 
 Because of CORS and strict configuration, all needs to end up on the same
 system. With modern browser, it even have to run on the same port (at least with
-firefox)
+firefox). As we want to mimic the real life setup as much as possible as well, 
+we will do this by running a local proxy. In production this will be handled by
+kubernetes ingress configuration.
 
-To do this, we will override the default paths and will run a local proxied to
-let requests end up at the right service.
+First we will tell kratos and hydra where to find the right endpoints. An 
+overview of all relevant end-points:
 
-An overview of all relevant end-points;
-
-The endponints used by the browser are:
+The endpoints used by the browser are:
 
  - `localhost/api` -> kratos public API
  - `localhost/login` -> login flask app
 
-The endpoint used by the login app are:
+The endpoint used by the login app/API are:
  - `localhost:8000` -> kratos Admin API
+ - `localhost/api`  -> kratos Public API
  - `localhost:4445` -> hydra Admin API
  - `localhost:5432` -> PostgreSQL
 
-To reflext those public endpoints in your cluster, we have to override the 
+To reflect those public endpoints in your cluster, we have to override the 
 default URLs from `values.yaml` in our `my-cluster-values.yaml` file. Also we
  set the SMTP settings as well, as for a proper development experience it is 
 required to be able to send out e-mails.
@@ -183,7 +205,7 @@ hydra:
 
 5. Install the single-sign-on helmchart
 
-So all if configured for local development, and we are good to go to configure
+So all is configured for local development, and we are good to go to configure
 our modified setup on our cluster:
 
 ```
@@ -191,15 +213,15 @@ cd helmchart/single-sign-on
 helm upgrade -f /to/a/path/my_cluster_values.yaml single-sign-on . -n stackspin --debug
 ```
 
-# Development 
+# Development
 
-1. Setup port redirects 
+1. Setup port redirects
 
 To be able to work on the Login panel, we have to configure our development
-system to access all the remote services and end/points. 
+system to access all the remote services and end/points.
 
 A helper script is available in this directory to setup and redirect the
-relevant ports localy. It will open porst 8000, 8080, 4445, 5432 to get access
+relevant ports locally. It will open ports 8000, 8080, 4445, 5432 to get access
 to all APIs:
 
 ```
@@ -207,10 +229,6 @@ to all APIs:
 ```
 
 (the tunnel goes to the kubernetes node, so *not* to your provisioning machine.
-kratos API is specified on their website:
-
-https://www.ory.sh/kratos/docs/reference/api/
-
 
 2. Configure a local proxy
 
@@ -219,9 +237,9 @@ app which we will run locally, with a local proxy.
 
 This can be done with any proxy server, for example with NGINX. Be sure you have
 NGINX installed and listing on port 80 locally (`sudo apt-get install nginx`)
-sould be enough. 
+should be enough. 
 
-Now configure NGINX with this confit in `/etc/nginx/sites-enabled/default`
+Now configure NGINX with this configuration in `/etc/nginx/sites-enabled/default`
 
 ```
 
@@ -258,7 +276,7 @@ server {
 }
 ```
 
-Reload your nginx:
+Reload your NGINX:
 
 ```
 sudo systemctl reload nginx.service
@@ -266,9 +284,10 @@ sudo systemctl reload nginx.service
 
 3. Run FLASK app
 
-Now it is time to start our flask app. Ofcourse you can use a `virtualenv`, but
-it is not needed of your system has a modern package manager and python as the
-app is designed to be compatible with those.
+Now it is time to start our flask app. Of course you can use a `virtualenv`, but
+it is not needed if your system has a modern package manager and as the
+app is designed to be compatible with those systems defaults, we can use OS
+based flask and python. Other requirements are still installed with PIP
 
 Lets install the requirements:
 
@@ -316,7 +335,7 @@ And now it is time to start the app:
 ./run.sh
 ```
 
-If this starts smoothy, you should be ready to go.
+If this starts smoothly, you should be ready to go.
 
 # Test you setup
 
@@ -337,13 +356,10 @@ following:
   step and you account is ready.
 
 At this point, we started the flow with trying to reach nextcloud. Because we
-did a password recovery inbetween, this information is logs. If you go again to
-nextcloud manually, you should now be logged in automatically. 
+did a password recovery in between, this information is lost. If you go again to
+nextcloud manually, you should now be logged in automatically.
 
 If you retry this, but now with a password (for example in a privacy window or
 by removing you cookies), you should be redirect automatically after login.
 
 
-
-
-
diff --git a/login/app.py b/login/app.py
index 7e324bd12bf34b9f6ee20947112b2294fb5da0b3..e6934be9cf53700a19c3539b9bffce2b33121ede 100644
--- a/login/app.py
+++ b/login/app.py
@@ -255,7 +255,7 @@ def recover_user(email):
 
 
     if not obj:
-        pp.logger.info("Not found")
+        app.logger.info("Not found")
         return
 
     if not obj.kratos_id:
@@ -292,11 +292,14 @@ app.cli.add_command(user_cli)
 # WEB ROUTES                                                                 #
 ##############################################################################
 
-# -> recovery
-
-# TODO: Create webroutes
 @app.route('/recovery', methods=['GET', 'POST'])
 def recovery():
+    """Start recovery flow
+    If no active flow, redirect to kratos to create a flow, otherwise render the
+    recovery template.
+    :param flow: flow as given by Kratos
+    :return: redirect or recovery page
+    """
 
     flow = request.args.get("flow")
     if not flow:
@@ -310,6 +313,12 @@ def recovery():
 
 @app.route('/settings', methods=['GET', 'POST'])
 def settings():
+    """Start settings flow
+    If no active flow, redirect to kratos to create a flow, otherwise render the
+    settings template.
+    :param flow: flow as given by Kratos
+    :return: redirect or settings page
+    """
 
     flow = request.args.get("flow")
     if not flow:
@@ -323,6 +332,13 @@ def settings():
 
 @app.route('/login', methods=['GET', 'POST'])
 def login():
+    """Start login flow
+    If already logged in, showd the loggedin template. Otherwise creates a login 
+    flow, if no active flow will redirect to kratos to create a flow.
+
+    :param flow: flow as given by Kratos
+    :return: redirect or login page
+    """
 
     # Check if we are logged in:
     profile = getAuth()
@@ -333,9 +349,7 @@ def login():
             api_url = app.config["KRATOS_PUBLIC_URL"],
             id = id)
 
-
     flow = request.args.get("flow")
-    auth =  request.args.get("auth")
 
     # If we do not have a flow, get one. 
     if not flow:
@@ -424,36 +438,47 @@ def auth():
 
 @app.route('/consent', methods=['GET', 'POST'])
 def consent():
-    """
+    """Get consent
+    For now, it just allows every user. Eventually this function should check
+    the roles and settings of a user and provide that information to the
+    application.
+    :param consent_challenge: challenge as given by Hydra
+    :return: redirect to login or render error
     """
 
     challenge = request.args.get("consent_challenge")
     if not challenge:
+        # TODO: Better error handling/display?
         abort(403)
     try:
         consent_request = HYDRA.consent_request(challenge)
     except hydra_client.exceptions.NotFound:
         app.logger.error("Not Found. Consent request not found. challenge={0}".format(challenge))
+        # TODO: Better error handling/display?
         abort(404)
     except hydra_client.exceptions.HTTPError:
         app.logger.error("Conflict. Consent request has been used already. challenge={0}".format(challenge))
+        # TODO: Better error handling/display?
         abort(503)
 
+    # Get information about this consent request:
     app_name = consent_request.client.client_name
     username = consent_request.subject
 
-    app.logger.info("Providing consent to %s for %s" % (app_name, username))
-
-
-    obj = User.query.filter_by(email=username).first()
-    if not obj:
+    # Get the related database object to get roles/access rights
+    user = User.query.filter_by(email=username).first()
+    if not user:
         app.logger.error("User not found in database: {0}".format(username))
+        # TODO: Better error handling?
         abort(401)
 
-    claims = obj.getClaims(app_name)
+    # Get claims for this user, provided the current app
+    claims = user.getClaims(app_name)
 
+    # TODO: Check access / claims?
+
+    app.logger.info("Providing consent to %s for %s" % (app_name, username))
 
-    # TODO: Check permission check and set admin scopes
 
     app.logger.info("{0} was granted access to {1}".format(username, app_name))
 
@@ -471,16 +496,15 @@ def status():
     Show if there is an user is logged in. If not shows: not-auth
     """
 
-    id = getAuth()
+    auth = getAuth()
 
-    if id:
-        return id['email']
+    if auth:
+        return auth['email']
     else:
         return "not-auth"
 
 
 
-
 def getAuth():
     """Checks if user is logged in
     Queries the cookies. If an authentication cookie is found, it
diff --git a/login/static/base.js b/login/static/base.js
index 4af48727b3ce1bcbfb10000f90e2a94f2fe879fd..a44d4ff0dea16ce8734b3faa6d4a3626692ff258 100644
--- a/login/static/base.js
+++ b/login/static/base.js
@@ -1,16 +1,22 @@
 
 
+/* base.js
+   This is the base JS file to render the user interfaces of kratos and provide
+   the end user with flows for login, recovery etc. 
+
+   check_flow_*():
+   These functions check the status of the flow and based on the status do some
+   action to get a better experience for the end user. Usually this is a
+   redirect based on the state
+
+   flow_*():
+   execute / render all UI elements in a flow. Kratos expects you to work on
+   to query kratos which provides you with the UI elements needed to be
+   rendered. This querying and rendering is done exectly by those function.
+   Based on what kratos provides or the state of the flow, elements are maybe
+   hidden or shown
 
-// Helpers
-
-// $.urlParam get parameters from the URI. Example: id =  $.urlParam('id');
-$.urlParam = function(name) {
-    var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(window.location.href);
-    if (results==null) {
-        return null;
-    }
-    return decodeURI(results[1]) || 0;
-};
+*/
 
 
 // Check if there is a auth flow is configured and redirect to auth page in that
@@ -59,7 +65,7 @@ function flow_login() {
             },
             complete: function(obj) {
 
-                // Expired flow, need to refresh
+                // If we get a 410, the flow is expired, need to refresh the flow
                 if (obj.status == 410) {
                     Cookies.set('flow_state','flow_expired');
                     window.location.href = 'location';
@@ -70,7 +76,7 @@ function flow_login() {
 }
 
 // This is called after a POST on settings. It tells if the save was 
-// successful and display / handles based on thtat outcome
+// successful and display / handles based on that outcome
 function flow_settings_validate() {
 
         var flow = $.urlParam('flow');
@@ -165,7 +171,7 @@ function flow_settings() {
             },
             complete: function(obj) {
 
-                // Expired flow, need to refresh
+                // If we get a 410, the flow is expired, need to refresh the flow
                 if (obj.status == 410) {
                     Cookies.set('flow_state','flow_expired');
                     window.location.href = 'settings';
@@ -186,6 +192,7 @@ function flow_recover() {
             url: uri,
             success: function(data) {
 
+                // Render the recover form, method 'link'
                 html = render_form(data, 'link');
                 $("#contentRecover").html(html);
 
@@ -198,6 +205,7 @@ function flow_recover() {
                     var form = $(this);
                     var url = form.attr('action');
 
+                    // keep stat we are in recovery
                     Cookies.set('flow_state', 'recovery');
                     $.ajax({
                         type: "POST",
@@ -205,6 +213,8 @@ function flow_recover() {
                         data: form.serialize(), // serializes the form's elements.
                         success: function(data)
                         {
+
+                            // Show the request is sent out
                             $("#contentRecover").hide();
                             $("#contentRecoverRequested").show();
                         }
@@ -215,7 +225,7 @@ function flow_recover() {
             },
             complete: function(obj) {
 
-                // Expired flow, need to refresh
+                // If we get a 410, the flow is expired, need to refresh the flow
                 if (obj.status == 410) {
                     Cookies.set('flow_state','flow_expired');
                     window.location.href = 'recovery';
@@ -228,7 +238,13 @@ function flow_recover() {
 
 
 
-// Based on Kratos UI data and a group name, get the full form for that group
+// Based on Kratos UI data and a group name, get the full form for that group.
+// kratos groups elements which belongs together in a group and should be posted 
+// at once. The elements in the default group should be part of all other
+// groups.
+//
+// data: data object as returned form the API
+// group: group to render.
 function render_form(data, group) { 
 
     // Create form
@@ -236,22 +252,16 @@ function render_form(data, group) {
     method = data.ui.method;
     form = "<form id='form"+group+"' method='"+method+"' action='"+action+"'>";
 
-    elements = {}
     for (const node of data.ui.nodes) {
 
-        if (!elements[node.group]) {
-            elements[node.group] = {}
-        }
-
         var name = node.attributes.name;
         var type = node.attributes.type;
         var value = node.attributes.value;
-        elm = getFormElement(type, name, value);
 
         if (node.group == 'default' || node.group == group) {
+            elm = getFormElement(type, name, value);
             form += elm;
         }
-        elements[node.group][name] = elm;
     }
     form += "</form>";
     return form;
@@ -259,10 +269,6 @@ function render_form(data, group) {
 }
 
 
-
-
-
-
 // Return form element based on name, including help et
 // Kratos give us forn names and types and specifies what to render. However
 // it does not provide labels or translations. This function returns a HTML
@@ -348,7 +354,14 @@ function getFormElement(type, name, value) {
 
 }
 
-// Based on a lot of vars, get a field
+// Usually called by getFormElement, generic function to generate an
+// input box.
+// param type: type of input, like 'input', 'email', 'password'
+// param name: name of form field, used when posting the form
+// param value: preset value of the field
+// param label: Label to display above field
+// param placeHolder: Label to display in field if empty
+// param help: Additional help tekst, displayed below the field in small font
 function getFormInput(type, name, value, label, placeHolder, help) {
 
     // Id field for help element
@@ -389,6 +402,16 @@ function getFormInput(type, name, value, label, placeHolder, help) {
 
 
 
+// $.urlParam get parameters from the URI. Example: id =  $.urlParam('id');
+$.urlParam = function(name) {
+    var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(window.location.href);
+    if (results==null) {
+        return null;
+    }
+    return decodeURI(results[1]) || 0;
+};
+
+