diff --git a/README.md b/README.md
index d00760cf33b6695fa16ecacaea1c64db78dcbcb5..a823e51c4f53583db38e51e216109a723822bbc2 100644
--- a/README.md
+++ b/README.md
@@ -16,31 +16,29 @@
 
 
 #### Development setup
-This is what you normally want during the development.
+
+- If you don't need a fully functional SSO, you don't need to configure anything at all. Use the dev view: `localhost:8000/dev/` (might change depending on your urlconf and settings).
+
+- If you need a fully functional SSO during development, here's an example:
 ```python
 """ settings/dev.py """
 
+from django import urls
 import socket
 import os
 
-DO_YOU_WANT_SSO = False
+IDP_META_URL = "https://idp-test.it.hs-hannover.de/idp/shibboleth"
 
-if DO_YOU_WANT_SSO:
-    SP_HOST = "localhost"
-    SP_PORT = 8000
-    SP_SSL = False
-    SP_FORCE_ENTITY_ID = "dev-auto-id-{0}-{1}".format(socket.gethostname(), os.path.dirname(os.path.dirname(__file__)))
-    IDP_META_URL = "https://idp-test.it.hs-hannover.de/idp/shibboleth"  # development
-else:
-    SSO_DISABLED = True
-```
+SP_KEY = "{project_settings}/cert/sp.key"
+SP_CERT = "{project_settings}/cert/sp.pem"
+SP_FORCE_ENTITY_ID = "dev-auto-id-{0}-{1}".format(socket.gethostname(), os.path.dirname(os.path.dirname(__file__)))
 
-Use `localhost:8000/dev/` to acces the development view.
-
-The code snippet above disables the actual SSO. If you need it:
-  - change `DO_YOU_WANT_SSO` to True
-  - see the SSO configuration section
+SP_HOST = "localhost"
+SP_PORT = 8000
+SP_SSL = False
 
+LOGIN_URL = urls.reverse_lazy("sso-dev")
+```
 
 #### Groups
 To receive groups over SSO you need a mapping. You can manage group mapping with `group_mapping` management command. Example:
@@ -54,10 +52,12 @@ To receive groups over SSO you need a mapping. You can manage group mapping with
 
 ```python
 """ settings/prod.py """
+from django import urls
 
 SP_HOST = "141.71.foo.bar"  # your SP host or IP address
 IDP_META_URL = "https://idp.hs-hannover.de/idp/shibboleth"  # production
 
+LOGIN_URL = urls.reverse_lazy("sso-login")
 ```
 You will also need to configure the SSO
 
diff --git a/ssoauth/app_settings/__init__.py b/ssoauth/app_settings/__init__.py
index 61836c1abc1104f828f0f0c0c305626858a8ac06..b043f659d4f779d78d617a99463d60c8818f7793 100644
--- a/ssoauth/app_settings/__init__.py
+++ b/ssoauth/app_settings/__init__.py
@@ -1,7 +1,8 @@
-from onelogin.saml2 import settings as onelogin_settings
-from .defaults import *
 from django import conf
 from datetime import datetime, timedelta
+from onelogin.saml2 import settings as onelogin_settings
+import sys
+from .defaults import *
 
 
 # merge defaults with customized user settings
@@ -15,43 +16,11 @@ for setting_name in [k for k in globals().keys() if k.isupper()]:
                 pass  # not set
 
 
-# checks
-
-SSO_DISABLED = SSO_DISABLED or getattr(conf.settings, "IDP_IGNORE", False)  # legacy config
-if SSO_DISABLED:
-    assert conf.settings.DEBUG, "Not ignoring IDP on production."
-else:
-    assert SP_HOST and SP_PORT, "Need SP_HOST and SP_PORT configured in settings."
-    assert not SP_HOST.lower().startswith(("http:", "https:",)), "Need host name without protocol and port."
-
-# helpers
-
-_SET_ON_RUNTIME = None and "doesn't make sense to change it"
-
-
-def get_project_settings_path():
-    module = os.environ.get(conf.ENVIRONMENT_VARIABLE)
-    path = os.path.abspath(os.path.join(*module.split(".")))
-    if not os.path.isdir(path):
-        # module, not a package
-        path = os.path.dirname(path)
-    return path
-
-
-def read_key(path):
-    path = path.format(project_settings=get_project_settings_path())
-    try:
-        with open(path, "r") as f:
-            return f.read()
-    except FileNotFoundError:
-        if SSO_DISABLED:
-            return None
-        else:
-            raise FileNotFoundError("SSO requires a key pair. Missing: {path}".format(path=path))
-
 
 # template for OneLogin toolkit settings
 
+_SET_ON_RUNTIME = None and "will be set on runtime"
+
 ONELOGIN_SETTINGS_TEMPLATE = {
     # Don't change it (you cannot in fact)
     # If you really need to adjust something here, please use ONELOGIN_OVERRIDES instead
@@ -61,9 +30,14 @@ ONELOGIN_SETTINGS_TEMPLATE = {
         "entityId": _SET_ON_RUNTIME or str(),
         "assertionConsumerService": {
             "url":  _SET_ON_RUNTIME or str(),
+            "binding": onelogin_settings.OneLogin_Saml2_Constants.BINDING_HTTP_POST,
+        },
+        "singleLogoutService": {
+            "url":  _SET_ON_RUNTIME or str(),
+            "binding": onelogin_settings.OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT,
         },
-        "x509cert": read_key(SP_CERT),
-        "privateKey": read_key(SP_KEY),
+        "x509cert": _SET_ON_RUNTIME,
+        "privateKey": _SET_ON_RUNTIME,
         "NameIDFormat": onelogin_settings.OneLogin_Saml2_Constants.NAMEID_PERSISTENT,  # otherwise Shibboleth shows warnings
     },
     "idp": {
@@ -102,4 +76,14 @@ ONELOGIN_SETTINGS_TEMPLATE = {
 
 ONELOGIN_SETTINGS_TEMPLATE.update(ONELOGIN_OVERRIDES)
 
-ONELOGIN_SETTINGS = _SET_ON_RUNTIME
+ONELOGIN_SETTINGS_OBJECT = _SET_ON_RUNTIME
+
+
+# helpers
+
+SSO_REQUIRED = any([
+    SSO_REQUIRED_IN_DEBUG and conf.settings.DEBUG,
+    SSO_REQUIRED_IN_PRODUCTION and not conf.settings.DEBUG,
+    SSO_REQUIRED_OUTSIDE_MANAGE_PY and not any("manage.py" in arg.lower() for arg in sys.argv),
+])
+
diff --git a/ssoauth/app_settings/defaults.py b/ssoauth/app_settings/defaults.py
index b9055a8229150163f124fa7f6c8dd36f00b88aed..5207242d75c1645941e26cc07d6f0436607ec981 100644
--- a/ssoauth/app_settings/defaults.py
+++ b/ssoauth/app_settings/defaults.py
@@ -13,22 +13,24 @@ Settings you may want to change:
 """
 
 # host and port, not what Django thinks, but what nginx serves
-SP_HOST = None  # e.g. "141.71.foo.bar", for development can use "localhost" and set FORCE_ENTITY_ID
+SP_HOST = "localhost"  # e.g. "141.71.foo.bar", for development can use "localhost" and set FORCE_ENTITY_ID
 SP_PORT = 443
 SP_SSL = True
 
-IDP_META_URL = "https://idp.hs-hannover.de/idp/shibboleth"  # test is "https://idp-test.it.hs-hannover.de/idp/shibboleth"
+IDP_META_URL = None  # e.g. "https://idp-test.hs-hannover.de/idp/shibboleth"
 
 SP_KEY = "{project_settings}/cert/sp.key"
 SP_CERT = "{project_settings}/cert/sp.pem"
 
+SSO_REQUIRED_IN_DEBUG = False
+SSO_REQUIRED_IN_PRODUCTION = False  # disabled because of e.g. collectstatic on the static server
+SSO_REQUIRED_OUTSIDE_MANAGE_PY = True  # enabled to ensure that production (that uses WSGI) has SSO
+
 
 """
 Settings you might want to change on development (don't change them for production):
 """
 
-# development helpers
-SSO_DISABLED = False
 SP_FORCE_ENTITY_ID = None  # do NOT set for production, set to some unique string on development
 
 
@@ -36,11 +38,11 @@ SP_FORCE_ENTITY_ID = None  # do NOT set for production, set to some unique strin
 Settings you DON'T want to change (in fact, you want to avoid even thinking about them):
 """
 
-SP_METADATA_LIFETIME_DAYS = 365 * 20
-
 # if you really really need to add/modify something in OneLogin settings, add it to ONELOGIN_OVERRIDES
 ONELOGIN_OVERRIDES = {}  # e.g.: ONELOGIN_OVERRIDES = { "strict": False }
 
+SP_METADATA_LIFETIME_DAYS = 365 * 20
+
 PROJECT_NAME = os.environ.get('DJANGO_SETTINGS_MODULE').split('.')[0]
 
 SU_GROUP_NAME = "{0}_superusers".format(PROJECT_NAME)
@@ -50,7 +52,7 @@ STAFF_PERM_NAME = "staff"
 
 
 """
-Not settings... just... Eww.
+Not really settings...
 """
 
 SP_CONTACTS = {
diff --git a/ssoauth/apps.py b/ssoauth/apps.py
index d78bff087ce0a5d10cec4a776dcddbfdf83f15dd..29d6e6ad3d334a547080ce170ae79dcf59705c6d 100644
--- a/ssoauth/apps.py
+++ b/ssoauth/apps.py
@@ -2,6 +2,7 @@ from django.apps import AppConfig
 from django.core import management
 from django.db.models.signals import post_migrate
 from django import conf
+import sys
 from . import logger
 from . import sso_utils
 from . import app_settings
@@ -13,14 +14,14 @@ class SSOAuthConfig(AppConfig):
     def ready(self, *args, **kwargs):
         super().ready(*args, **kwargs)
         # OneLogin settings stuff
-        if app_settings.SSO_DISABLED:
-            assert conf.settings.DEBUG
-            logger.debug("SSO is disabled.")
-        else:
-            try:
-                app_settings.ONELOGIN_SETTINGS = sso_utils.create_onelogin_settings(app_settings.ONELOGIN_SETTINGS_TEMPLATE)
-            except Exception as e:
-                raise RuntimeError("SSO failed to start. {ec}: {e}".format(ec=e.__class__.__name__, e=str(e),))
+        try:
+            app_settings.ONELOGIN_SETTINGS_OBJECT = sso_utils.create_onelogin_settings()
+        except Exception as e:
+            msg = "SSO failed to start. {ec}: {e}".format(ec=e.__class__.__name__, e=str(e),)
+            if app_settings.SSO_REQUIRED:
+                raise RuntimeError(msg)
+            else:
+                logger.warning(msg)
         # default groups
         post_migrate.connect(self.post_migrate_callback, sender=self)
 
diff --git a/ssoauth/auth_utils.py b/ssoauth/auth_utils.py
index c6bd5c89ae103961ee82d538f3e8102ebb4d4d74..98b4f7920e01c7733f207a51f93491f663469a67 100644
--- a/ssoauth/auth_utils.py
+++ b/ssoauth/auth_utils.py
@@ -7,7 +7,6 @@ from . import models
 from . import logger
 from . import app_settings
 import functools
-import re
 
 
 def _validate_username(username):
@@ -39,7 +38,7 @@ def get_or_create_user(uuid, username):
             user = get_user(uuid=uuid)
             if user.username != username:
                 # have to do it because users might be renamed
-                logger.warning("Usernames mismatch. Changing from {0} to {1}.".format(user.username, username))
+                logger.warning("Username has changed. Renaming locally from {0} to {1}.".format(user.username, username))
                 user.username = username
                 user.save()
             return user
@@ -125,11 +124,11 @@ def set_user_compat_flags(user):
         group_su = Group.objects.get(name=app_settings.SU_GROUP_NAME)
         group_staff = Group.objects.get(name=app_settings.STAFF_GROUP_NAME)
         if group_su in user.groups.all():
-            logger.info("User {user} is superuser.".format(**locals()))
+            logger.info("User {user} is superuser".format(**locals()))
             user.is_staff = True
             user.is_superuser = True
         if group_staff in user.groups.all():
-            logger.info("User {user} has admin access.".format(**locals()))
+            logger.info("User {user} is staff (has admin access)".format(**locals()))
             user.is_staff = True
     except Group.DoesNotExist:
         logger.error("No compat groups. Migrate or run management command create_compat_groups.")
diff --git a/ssoauth/checks.py b/ssoauth/checks.py
index d7155dee6fe7b006844964a1b339a909cb929b46..991d90c5e08026b39ab7e2db908507ac97213db4 100644
--- a/ssoauth/checks.py
+++ b/ssoauth/checks.py
@@ -50,9 +50,39 @@ def compatible_user_model(app_configs, **kwargs):
     return errors
 
 
+@register(Tags.compatibility)
+def host_without_protocol(app_configs, **kwargs):
+    errors = list()
+    if app_settings.SP_HOST is not None:
+        if app_settings.SP_HOST.lower().startswith(("http:", "https:",)):
+            errors.append(Error("Bad setting SP_HOST. Need host name without protocol and port."))
+    return errors
+
+
+@register(Tags.compatibility)
+def old_settings(app_configs, **kwargs):
+    def settings_have(key):
+        try:
+            getattr(conf.settings, key)
+            return True
+        except AttributeError:
+            return False
+    errors = list()
+    if settings_have("SSO_DISABLED") or settings_have("SSOAUTH_SSO_DISABLED"):
+        errors.append(Warning("SSO_DISABLED is deprecated."))
+    if settings_have("IDP_IGNORE") or settings_have("SSOAUTH_IDP_IGNORE"):
+        errors.append(Warning("IDP_IGNORE is deprecated."))
+    if errors:
+        errors.append(Warning("Please check the README. Alternatively simply remove *all* ssoauth settings from your project settings, "
+                      "you don't need them anymore as long as you don't need a working SSO."))
+    return errors
+
+
 @register(Tags.urls)
 def auth_urls_configured(app_configs, **kwargs):
     errors = list()
+    if not app_settings.SSO_REQUIRED:
+        return errors
 
     def _url_from_setting(setting_name):
         # get url based on django setting name, handles most of cases of something going wrong
@@ -61,7 +91,7 @@ def auth_urls_configured(app_configs, **kwargs):
             assert urls.resolve(setting_value)  # page exists
             return setting_value
         except Exception as e:
-            errors.append(Warning("{ec}: {e}".format(ec=e.__class__.__name__, e=str(e)), obj=conf.settings,))
+            errors.append(Warning("Failed to resolve an URL for {s}. {ec}: {e}".format(ec=e.__class__.__name__, e=str(e),s=setting_name), obj=conf.settings))
             return None
 
     # login
diff --git a/ssoauth/management/commands/group_mapping.py b/ssoauth/management/commands/group_mapping.py
index 5dcb6b014520cfb2aad773f74e44e152d0cfafed..42375fae9cd71f5ce74e57e4939c61be8a7ef83e 100644
--- a/ssoauth/management/commands/group_mapping.py
+++ b/ssoauth/management/commands/group_mapping.py
@@ -94,8 +94,9 @@ class Command(BaseCommand):
             return None
 
 
-                # raise CommandError("Could not ensure that compatibility groups and permissions exist. {0}".format(str(e)))
-
+    ## seems like the following code can create groups and permissions as we had it
+    ## in the old hshauth, based on the project settings
+    #
     # @staticmethod
     # def ensure_group_exists(group_name, permission_names=list()):
     #     """
diff --git a/ssoauth/sso_utils.py b/ssoauth/sso_utils.py
index 767947754fb138f9a136fa24d6bf095fe89d048b..d8786b7861a2441ff1197e6844b4bb4e2c2d4cf9 100644
--- a/ssoauth/sso_utils.py
+++ b/ssoauth/sso_utils.py
@@ -5,6 +5,26 @@ from . import app_settings
 from django import urls
 from onelogin.saml2.settings import OneLogin_Saml2_Settings
 from copy import copy
+import os
+from django import conf
+
+
+def get_project_settings_path():
+    module = os.environ.get(conf.ENVIRONMENT_VARIABLE)
+    path = os.path.abspath(os.path.join(*module.split(".")))
+    if not os.path.isdir(path):
+        # module, not a package
+        path = os.path.dirname(path)
+    return path
+
+
+def read_key(path):
+    path = path.format(project_settings=get_project_settings_path())
+    try:
+        with open(path, "r") as f:
+            return f.read()
+    except FileNotFoundError:
+        raise FileNotFoundError("Could not read the key: {0}".format(path))
 
 
 def get_idp_runtime_info(meta_url):
@@ -53,7 +73,7 @@ def get_idp_runtime_info(meta_url):
     }
 
 
-def create_onelogin_settings(template):
+def create_onelogin_settings(template=app_settings.ONELOGIN_SETTINGS_TEMPLATE):
     """ This function is intended to be run only once, on app startup. Raises exceptions. """
     # get the template
     template = copy(template)
@@ -62,15 +82,19 @@ def create_onelogin_settings(template):
     port_suffix = "" if app_settings.SP_PORT in [80, 443] else ":{0}".format(app_settings.SP_PORT)
     host = app_settings.SP_HOST
     host_full = "{protocol}://{host}{port_suffix}".format(**locals())
-    # SP settings
-    template["sp"]["entityId"] = app_settings.SP_FORCE_ENTITY_ID or (host_full + urls.reverse("sso-saml2-meta"))
-    template["sp"]["assertionConsumerService"]["url"] = host_full + urls.reverse("sso-saml2-acs")
     # IDP settings
+    assert app_settings.IDP_META_URL, "IDP_META_URL is not specified"  # before get_idp_runtime_info starts logging errors
     idp_info = get_idp_runtime_info(app_settings.IDP_META_URL)
     template["idp"]["x509certMulti"]["signing"] = idp_info["certificates"]["signing"]
     template["idp"]["x509certMulti"]["encryption"] = idp_info["certificates"]["encryption"]
     template["idp"]["singleSignOnService"]["url"] = idp_info["bindings"]["sso_redirect"]
     template["idp"]["singleLogoutService"]["url"] = idp_info["bindings"]["slo_redirect"]
+    # SP settings
+    template["sp"]["entityId"] = app_settings.SP_FORCE_ENTITY_ID or (host_full + urls.reverse("sso-saml2-meta"))
+    template["sp"]["assertionConsumerService"]["url"] = host_full + urls.reverse("sso-saml2-acs")
+    template["sp"]["singleLogoutService"]["url"] = host_full + urls.reverse("sso-saml2-sls")
+    template["sp"]["x509cert"] = read_key(app_settings.SP_CERT)
+    template["sp"]["privateKey"] = read_key(app_settings.SP_KEY)
     # done
     return OneLogin_Saml2_Settings(settings=template, sp_validation_only=True)
 
diff --git a/ssoauth/templates/ssoauth/dev.html b/ssoauth/templates/ssoauth/dev.html
index bbf893791b113517e9d2466579c805a3662d2435..cddbd57d82b7999382060e54963d9cf10f4c1c66 100644
--- a/ssoauth/templates/ssoauth/dev.html
+++ b/ssoauth/templates/ssoauth/dev.html
@@ -8,6 +8,9 @@
     <style>
         .hidden {display: none !important;}
         .wrapper>.container {  padding-bottom: 2rem;  padding-top: 2rem; }
+        .disabled {opacity: 0.60; filter: grayscale(100%); pointer-events: none !important;}
+        .button-narrow {padding-left: 1em; padding-right: 1em;}
+        .message {padding: 0.2em 0.5em; border: 1px solid rgba(127,127,127,0.5); border-radius: 4px; background-color: rgba(127,127,127,0.05); display: inline-block;}
     </style>
 </head>
 <body>
@@ -17,6 +20,8 @@
             <h2 class="title">Actions</h2>
             <form action="" method="post">
                 {% csrf_token %}
+                <input type="submit" class="hidden"/> {# as chromium actually works #}
+                <button type="submit" class="hidden"></button> {# according to html5 specs #}
                 <div class="row">
                     <div class="column">
                         <h4 class="title">Log In</h4>
@@ -25,7 +30,7 @@
                     </div>
                     <div class="column">
                         <h4 class="title">Toggle Groups</h4>
-                            <select name="toggle_group" class="disabled" id="id_toggle_group" onchange="this.form.submit()">
+                            <select name="toggle_group" id="id_toggle_group" onchange="this.form.submit()">
                                 {% for value, name in form.fields.toggle_group.choices %}
                                         <option value="{{ value }}">{% if value %}{{ name }}{% endif %}</option>
                                 {% endfor %}
@@ -33,14 +38,21 @@
                         <p><i>Add or remove groups for the logged in user</i></p>
                     </div>
                     <div class="column">
+                        <h4 class="title">Extras</h4>
+                        <button type="submit" name="local-logout" value="local-logout" class="button button-outline button-narrow">Log Out</button>
+                        <p><i>Extra actions for development and debugging</i></p>
+                    </div>
+                    <div class="column {% if not sso_configured %}disabled{% endif %}">
                         <h4 class="title">Production Actions</h4>
-                        <a class="button button-outline button-black" href="{% url "sso-login" %}?next={% url "sso-dev" %}">SSO Log in</a>
-                        <a class="button button-outline button-black" href="{% url "sso-logout" %}?next={% url "sso-dev" %}">Log out</a>
-                        <p><i>These actions are used in production</i></p>
+                        <a class="button button-outline button-narrow" href="{% url "sso-login" %}?next={% url "sso-dev" %}">Log In</a>
+                        <a class="button button-outline button-narrow" href="{% url "sso-logout" %}?next={% url "sso-dev" %}">Log Out</a>
+                        <a class="button button-outline button-narrow" href="{% url "sso-saml2-meta" %}" target="_blank">Meta</a>
+                        <p><i>{% if sso_configured %}These actions are used in production{% else %}Your SSO settings are a potato{% endif %}</i></p>
                     </div>
                 </div>
-                <input class="hidden" type="submit">
             </form>
+            {% if next_url %}<p class="message">You will be redirected to: <strong>{{ next_url }}</strong></p>{% endif %}
+            {% if user.is_superuser %}<p class="message">You are a superuser</p>{% endif %}
         </section>
 
         {% for table_name, table_contents in tables %}
diff --git a/ssoauth/urls.py b/ssoauth/urls.py
index f63ebf522051768d3a59e94a9e3662644d6fecad..9048579403cd3bebca9135b4b7b8fca6d4826a27 100644
--- a/ssoauth/urls.py
+++ b/ssoauth/urls.py
@@ -5,7 +5,8 @@ urlpatterns = (
     url(r"^login/?$", views.LogInView.as_view(), name="sso-login"),
     url(r"^logout/?$", views.LogOutView.as_view(), name="sso-logout"),
     url(r"^saml2/acs/?$", views.ACSAuthNView.as_view(), name="sso-saml2-acs"),
-    url(r"^saml2/meta/?$", views.MetadataView.as_view(), name="sso-saml2-meta"),
+    url(r"^saml2/sls/?$", views.SLSView.as_view(), name="sso-saml2-sls"),
+    url(r"^saml2/meta(?:data)?/?$", views.MetadataView.as_view(), name="sso-saml2-meta"),
     url(r"^dev/?$", views.DevView.as_view(), name="sso-dev"),
 )
 
diff --git a/ssoauth/views.py b/ssoauth/views.py
index c1006318c1de6f372cd9740273bf2c64e15cb365..07e8adc3d9af36553a4bbf09465b18467bd6f045 100644
--- a/ssoauth/views.py
+++ b/ssoauth/views.py
@@ -32,7 +32,7 @@ class SAMLMixin:
     """ Merges Django runtime info and settings for OneLogin toolkit """
 
     def __init__(self, *args, **kwargs):
-        if not app_settings.ONELOGIN_SETTINGS:
+        if not app_settings.ONELOGIN_SETTINGS_OBJECT:
             raise exceptions.ImproperlyConfigured("SSO is not configured.")
         super().__init__(*args, **kwargs)
 
@@ -52,7 +52,7 @@ class SAMLMixin:
     def get_onelogin_auth(cls, request):
         return OneLogin_Saml2_Auth(
             request_data=cls.get_onelogin_request_data(request),
-            old_settings=app_settings.ONELOGIN_SETTINGS
+            old_settings=app_settings.ONELOGIN_SETTINGS_OBJECT
         )
 
 
@@ -148,11 +148,22 @@ class ACSAuthNView(SAMLMixin, View):
         logger.debug("Logged in {user}".format(**locals()))
 
 
+@method_decorator(never_cache, "dispatch")
+@method_decorator(csrf_exempt, "dispatch")
+class SLSView(SAMLMixin, View):
+    """
+    SLS (Single Logout Service) binding.
+    When this view is opened by a user (usually inside of a frame) this user must be unconditionally logged off.
+    """
+    def get(self):
+        return None
+
+
 class MetadataView(SAMLMixin, View):
 
     def get(self, request, *args, **kwargs):
-        meta = app_settings.ONELOGIN_SETTINGS.get_sp_metadata()
-        errors = app_settings.ONELOGIN_SETTINGS.validate_metadata(meta)
+        meta = app_settings.ONELOGIN_SETTINGS_OBJECT.get_sp_metadata()
+        errors = app_settings.ONELOGIN_SETTINGS_OBJECT.validate_metadata(meta)
         if errors:
             for e in errors:
                 logger.error(e)
@@ -178,34 +189,51 @@ class DevView(FormView):
         else:
             super().__init__(*args, **kwargs)
 
+    @property
+    def next_url(self):
+        return self.request.GET.get(REDIRECT_FIELD_NAME, None)
+
     def get_context_data(self, **kwargs):
         assert conf.settings.DEBUG
         context = super().get_context_data(**kwargs)
         user = self.request.user
         groups = list(user.groups.all()) if user.is_authenticated else list()
+        permissions = list(user.get_all_permissions())
         context["tables"] =[
             ["User", OrderedDict([
                 ["user", "{0} ({1})".format(self.request.user.username, self.request.user.__class__.__name__)],
                 ["groups", ", ".join(str(g) for g in groups)],
+                ["permissions", ", ".join(str(p) for p in permissions)],
             ])],
             ["SAML2 Attributes", self.request.session.get("DEBUG_SAML2_ATTRS")],
             ["Session", dict(self.request.session)],
         ]
+        context.update(dict(
+            sso_configured=bool(app_settings.ONELOGIN_SETTINGS_OBJECT),
+            next_url=self.next_url,
+        ))
         return context
 
     def form_valid(self, form):
         assert conf.settings.DEBUG
-        username = form.cleaned_data["username"]
+        log_in_as_username = form.cleaned_data["username"]
         toggle_group = form.cleaned_data["toggle_group"]
-        if username:
+        local_logout = bool(self.request.POST.get("local-logout", None))
+        if local_logout:
+            if log_in_as_username or toggle_group:
+                log_in_as_username, toggle_group = None, None  # single page / single form for everything can cause weird effects
+        # perform an action
+        if log_in_as_username:
+            logger.info("Logging in as {0}".format(log_in_as_username))
             try:
-                user = auth_utils.get_user(username=username)
+                user = auth_utils.get_user(username=log_in_as_username)
             except exceptions.ObjectDoesNotExist:
                 import uuid
-                user = auth_utils.get_or_create_user(username=username, uuid=uuid.uuid4())
+                user = auth_utils.get_or_create_user(username=log_in_as_username, uuid=uuid.uuid4())
             self.request.user = user
             contrib_auth.login(request=self.request, user=user)
         elif toggle_group:
+            logger.info("Toggling group: {0}".format(toggle_group))
             if self.request.user.is_authenticated:
                 if toggle_group in self.request.user.groups.all():
                     self.request.user.groups.remove(toggle_group)
@@ -213,6 +241,12 @@ class DevView(FormView):
                     self.request.user.groups.add(toggle_group)
             else:
                 logger.warning("Too anonymous to join groups.")
-        auth_utils.set_user_compat_flags(self.request.user)
-        return http.HttpResponseRedirect(urls.reverse("sso-dev"))
+        elif local_logout:
+            logger.info("Logging out")
+            contrib_auth.logout(self.request)
+        # update the compat flags, might be needed when user or their groups change
+        if self.request.user.is_authenticated:
+            auth_utils.set_user_compat_flags(self.request.user)
+        # redirect
+        return http.HttpResponseRedirect(self.next_url or urls.reverse("sso-dev"))