diff --git a/SAS/TMSS/bin/tmss b/SAS/TMSS/bin/tmss
index d3cc71d890adbfb1117af6eb8d32aa5ccf330ac1..d96fd3b05568f7f6c1017473f25ce0889a564f99 100755
--- a/SAS/TMSS/bin/tmss
+++ b/SAS/TMSS/bin/tmss
@@ -21,7 +21,7 @@
 # Startup script for the development TMSS server
 
 #defaults
-PORT=8000
+PORT=8008
 CREDENTIALS="tmss"
 
 # Parse args:
diff --git a/SAS/TMSS/docker/tmss-testenv/docker-compose.yml b/SAS/TMSS/docker/tmss-testenv/docker-compose.yml
index 8cd3fcfbad06e52252ed2fcaf5f434508dd18564..a0aff9ea3abb8d5d73918ad6015defefb1fe6c47 100644
--- a/SAS/TMSS/docker/tmss-testenv/docker-compose.yml
+++ b/SAS/TMSS/docker/tmss-testenv/docker-compose.yml
@@ -6,13 +6,13 @@ services:
       dockerfile: tmss_testenv_Dockerfile
     container_name: tmss_test
     ports:
-      - "8000:8000"
-      - "3000:3000"
+      - "8008:8008"
+      - "3003:3003"
   tmss_test_nginx:
     build:
       context: .
       dockerfile: tmss_nginx_Dockerfile
     container_name: tmss_test_nginx
     ports:
-      - "5000:5000"
+      - "5005:5005"
 
diff --git a/SAS/TMSS/docker/tmss-testenv/nginx.conf b/SAS/TMSS/docker/tmss-testenv/nginx.conf
index ec3ceff56aeab6b4e93e705cebf7d3e2ca4379cc..66d9a68ca353a00841ade2a9273ac7c607648007 100644
--- a/SAS/TMSS/docker/tmss-testenv/nginx.conf
+++ b/SAS/TMSS/docker/tmss-testenv/nginx.conf
@@ -2,7 +2,7 @@ events {}
 http {
   server {
 
-    listen 5000;
+    listen 5005;
 
     proxy_http_version 1.1;
     proxy_set_header  Host  $http_host;
@@ -18,22 +18,27 @@ http {
     }
 
     location @api_proxy {
-      proxy_pass http://tmss_test:8000$uri;
+      proxy_pass http://tmss_test:8008$uri;
       proxy_intercept_errors on;
 	  recursive_error_pages on;
 	  error_page 404 = @frontend_proxy;
     }
 
     location @frontend_proxy {
-      proxy_pass http://tmss_test:3000$uri;
+      proxy_pass http://tmss_test:3003$uri;
     }
 
     location /api/ {
-      proxy_pass http://tmss_test:8000;
+      proxy_pass http://tmss_test:8008;
     }
 
     location /frontend/ {
-      proxy_pass http://tmss_test:3000;
+      proxy_pass http://tmss_test:3003;
     }
+
+    location /oidc/ {
+      proxy_pass http://tmss_test:8008;
+    }
+
   }
 }
\ No newline at end of file
diff --git a/SAS/TMSS/docker/tmss-testenv/tmss_testenv_Dockerfile b/SAS/TMSS/docker/tmss-testenv/tmss_testenv_Dockerfile
index 99d01469505b5a4bbbc8312e747638e25a03d860..ba62c365dbb69387bafafb05e967faf607e0ac0f 100644
--- a/SAS/TMSS/docker/tmss-testenv/tmss_testenv_Dockerfile
+++ b/SAS/TMSS/docker/tmss-testenv/tmss_testenv_Dockerfile
@@ -14,14 +14,14 @@ RUN yum -y groupinstall 'Development Tools' && \
 RUN echo "Installing packages for TMSS..." && \
     yum -y install https://download.postgresql.org/pub/repos/yum/9.4/redhat/rhel-7-x86_64/pgdg-centos94-9.4-3.noarch.rpm && \
     yum -y install postgresql94-devel openldap-devel postgresql94-server which && \
-    pip3 install django-filter django-auth-ldap coreapi python-ldap-test django-jsonforms django-json-widget "git+git://github.com/nnseva/django-jsoneditor.git" psycopg2-binary markdown ldap3 drf-yasg flex swagger-spec-validator testing.postgresql
+    pip3 install django-filter django-auth-ldap coreapi python-ldap-test django-jsonforms django-json-widget "git+git://github.com/nnseva/django-jsoneditor.git" psycopg2-binary markdown ldap3 drf-yasg flex swagger-spec-validator testing.postgresql mozilla_django_oidc
 
 ENV PATH=$PATH:/usr/pgsql-9.4/bin/
 
 RUN echo "Checking out code base" && \
     git clone https://git.astron.nl/ro/lofar.git && \
     cd lofar && \
-    git checkout master && \
+    git checkout TMSS-134 && \
     . CMake/gen_LofarPackageList_cmake.sh && \
     PACKAGE=TMSS  && \
     VARIANT=gnucxx11_opt  && \
@@ -62,7 +62,7 @@ ENTRYPOINT /bin/bash -c 'export VARIANT=gnucxx11_opt; \
            sleep 15 && \
            tmss & \
            cd /lofar/build/gnucxx11_opt/installed/share/www && \
-           serve -s . -p 3000'
+           serve -s . -p 3003'
 
 # It also works to serve the dev server for interactive development as such:
 # cd /lofar/build/gnucxx11_opt/SAS/TMSS/frontend/frontend_poc && \
diff --git a/SAS/TMSS/frontend/frontend_poc/CMakeLists.txt b/SAS/TMSS/frontend/frontend_poc/CMakeLists.txt
index ea46d4e1f1d4156bcc94b54dbdf356630641f630..8d54d6e333c1ef15dea6f37c22121de0d3327785 100644
--- a/SAS/TMSS/frontend/frontend_poc/CMakeLists.txt
+++ b/SAS/TMSS/frontend/frontend_poc/CMakeLists.txt
@@ -1,4 +1,4 @@
 include(NPMInstall)
 
-message(WARNING "disabled npm_install, because it currently fails to build. FIX THIS, or remove it.")
-#npm_install(package.json PUBLIC public SOURCE src DESTINATION share/www)
+#message(WARNING "disabled npm_install, because it currently fails to build. FIX THIS, or remove it.")
+npm_install(package.json PUBLIC public SOURCE src DESTINATION share/www)
diff --git a/SAS/TMSS/src/remakemigrations.py b/SAS/TMSS/src/remakemigrations.py
index 015794aac00b3b4d6890e23f8cc402ef008d330e..9decd35f5d7e8d193060f2528c332df7bedd8a61 100755
--- a/SAS/TMSS/src/remakemigrations.py
+++ b/SAS/TMSS/src/remakemigrations.py
@@ -41,7 +41,7 @@ def delete_old_migrations():
     for f in [path for path in files if ("auto" in path or "populate" in path)]:
         logger.info('Deleting: %s' % f)
         os.remove(f)
-        execute_and_log('svn rm %s' % f)
+        execute_and_log('git rm %s' % f)
 
 def make_django_migrations():
 
@@ -80,7 +80,7 @@ def put_migrations_under_version_control():
     logger.info('Putting migrations under version control...')
     files = glob_migrations()
     for f in files:
-        execute_and_log('svn add %s' % f)
+        execute_and_log('git add %s' % f)
 
 
 def remake_migrations():
@@ -98,4 +98,4 @@ if __name__ == "__main__":
     handler.setLevel(logging.INFO)
     logger.addHandler(handler)
 
-    remake_migrations()
\ No newline at end of file
+    remake_migrations()
diff --git a/SAS/TMSS/src/templates/rest_framework/api.html b/SAS/TMSS/src/templates/rest_framework/api.html
index 1224623e4a7ba3b2beb17b306cd5dfc0d9a9cf33..36a74512e955026bed46b1785651d0632f2df408 100644
--- a/SAS/TMSS/src/templates/rest_framework/api.html
+++ b/SAS/TMSS/src/templates/rest_framework/api.html
@@ -4,4 +4,15 @@
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootswatch/3.3.7/yeti/bootstrap.min.css" type="text/css">
 {% endblock %}
 
-{% block bootstrap_navbar_variant %}{% endblock %}
\ No newline at end of file
+{% block bootstrap_navbar_variant %}{% endblock %}
+
+
+{% block userlinks %}
+   {% if request.user.is_authenticated %}
+      <p>Current user: {{ request.user.email }}</p>
+    {% else %}
+      <a href="{% url 'oidc_authentication_init' %}">Login</a>
+   {% endif %}
+{% endblock %}
+
+
diff --git a/SAS/TMSS/src/tmss/settings.py b/SAS/TMSS/src/tmss/settings.py
index 046f8de35906fd6d5c5753625ee5a4c04de7f1aa..a734abb34c175c2bcdc7f872045cbd95cf1e6ad0 100644
--- a/SAS/TMSS/src/tmss/settings.py
+++ b/SAS/TMSS/src/tmss/settings.py
@@ -18,8 +18,54 @@ from lofar.common import dbcredentials
 # logger
 logger = logging.getLogger('django_auth_ldap')
 logger.addHandler(logging.StreamHandler())
-logger.setLevel(logging.INFO)
-
+logger.setLevel(logging.DEBUG)
+
+LOGGING = {
+    'version': 1,
+    'disable_existing_loggers': False,
+    'filters': {
+        'require_debug_false': {
+            '()': 'django.utils.log.RequireDebugFalse',
+        },
+        'require_debug_true': {
+            '()': 'django.utils.log.RequireDebugTrue',
+        },
+    },
+    'formatters': {
+        'django.server': {
+            '()': 'django.utils.log.ServerFormatter',
+            'format': '[%(server_time)s] %(message)s',
+        },
+    },
+    'handlers': {
+        'console': {
+            'level': 'DEBUG',
+            'filters': ['require_debug_true'],
+            'class': 'logging.StreamHandler',
+        },
+        'django.server': {
+            'level': 'DEBUG',
+            'class': 'logging.StreamHandler',
+            'formatter': 'django.server',
+        },
+        'mail_admins': {
+            'level': 'ERROR',
+            'filters': ['require_debug_false'],
+            'class': 'django.utils.log.AdminEmailHandler'
+        },
+    },
+    'loggers': {
+        'django': {
+            'handlers': ['console', 'mail_admins'],
+            'level': 'DEBUG',
+        },
+        'django.server': {
+            'handlers': ['django.server'],
+            'level': 'DEBUG',
+            'propagate': False,
+        },
+    }
+}
 
 # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -55,6 +101,7 @@ INSTALLED_APPS = [
     'jsoneditor',
     'drf_yasg',
     'django_filters',
+    'mozilla_django_oidc',  # Load after auth
 ]
 
 MIDDLEWARE = [
@@ -65,6 +112,7 @@ MIDDLEWARE = [
     'django.contrib.auth.middleware.AuthenticationMiddleware',
     'django.contrib.messages.middleware.MessageMiddleware',
     'django.middleware.clickjacking.XFrameOptionsMiddleware',
+    'mozilla_django_oidc.middleware.SessionRefresh',
 ]
 
 ROOT_URLCONF = 'tmss.urls'
@@ -127,8 +175,9 @@ DATABASES = {
 REST_FRAMEWORK = {
     # Authentication
     'DEFAULT_AUTHENTICATION_CLASSES': (
-        'rest_framework.authentication.BasicAuthentication',
+        'mozilla_django_oidc.contrib.drf.OIDCAuthentication',
         'rest_framework.authentication.SessionAuthentication',
+        #'rest_framework.authentication.BasicAuthentication',
     ),
     'DEFAULT_PERMISSION_CLASSES': [
         #'rest_framework.permissions.AllowAny',
@@ -171,8 +220,34 @@ AUTH_LDAP_USER_ATTR_MAP = {
     "email": "mail"
 }
 
+# OPEN-ID CONNECT
+
+OIDC_DRF_AUTH_BACKEND = 'mozilla_django_oidc.auth.OIDCAuthenticationBackend'
+
+# For talking to PYOP Identity Provider:
+#OIDC_VERIFY_SSL = False # TODO: Remove for production!
+#OIDC_RP_CLIENT_ID = os.environ.get('OIDC_RP_CLIENT_ID', 'a0PF52uaIQxu')               # Secret, do not put real credentials on Git
+#OIDC_RP_CLIENT_SECRET = os.environ.get('OIDC_RP_CLIENT_SECRET', '93863d5aad044c0baaf1f0021b2542db')   # Secret, do not put real credentials on Git
+#OIDC_OP_AUTHORIZATION_ENDPOINT = "https://localhost:9090/authentication"
+#OIDC_OP_TOKEN_ENDPOINT = "https://localhost:9090/token"
+#OIDC_OP_USER_ENDPOINT = "https://localhost:9090/userinfo"
+
+
+# For talking to Mozilla Identity Provider:
+OIDC_RP_CLIENT_ID = os.environ.get('OIDC_RP_CLIENT_ID', '1')               # Secret, do not put real credentials on Git
+OIDC_RP_CLIENT_SECRET = os.environ.get('OIDC_RP_CLIENT_SECRET', 'secret')       # Secret, do not put real credentials on Git
+OIDC_OP_AUTHORIZATION_ENDPOINT="http://localhost:8088/openid/authorize"
+OIDC_OP_TOKEN_ENDPOINT="http://localhost:8088/openid/token"
+OIDC_OP_USER_ENDPOINT="http://localhost:8088/openid/userinfo"
+
+LOGIN_REDIRECT_URL = "/api/"
+LOGIN_REDIRECT_URL_FAILURE = "/api/"
+LOGOUT_REDIRECT_URL = "/api/"
+LOGOUT_REDIRECT_URL_FAILURE = "/api/"
+
 AUTHENTICATION_BACKENDS = (
-    'django_auth_ldap.backend.LDAPBackend',
+    #'django_auth_ldap.backend.LDAPBackend',
+    'mozilla_django_oidc.auth.OIDCAuthenticationBackend',
     'django.contrib.auth.backends.ModelBackend',
 )
 
diff --git a/SAS/TMSS/src/tmss/tmssapp/migrations/0002_auto_20190606_1535.py b/SAS/TMSS/src/tmss/tmssapp/migrations/0002_auto_20200123_1057.py
similarity index 98%
rename from SAS/TMSS/src/tmss/tmssapp/migrations/0002_auto_20190606_1535.py
rename to SAS/TMSS/src/tmss/tmssapp/migrations/0002_auto_20200123_1057.py
index 33a161265deb650b743573fd7a853f29be6cb01e..1bccdfe6ac7fbf01c71031f992938c4e25aca38d 100644
--- a/SAS/TMSS/src/tmss/tmssapp/migrations/0002_auto_20190606_1535.py
+++ b/SAS/TMSS/src/tmss/tmssapp/migrations/0002_auto_20200123_1057.py
@@ -1,4 +1,4 @@
-# Generated by Django 2.0.6 on 2019-06-06 15:35
+# Generated by Django 2.0.6 on 2020-01-23 10:57
 
 import django.contrib.postgres.fields
 import django.contrib.postgres.fields.jsonb
@@ -864,70 +864,70 @@ class Migration(migrations.Migration):
         ),
         migrations.AddIndex(
             model_name='taskrelationdraft',
-            index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_tas_tags_0c3200_gin'),
+            index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_tas_tags_aeef84_gin'),
         ),
         migrations.AddIndex(
             model_name='taskrelationblueprint',
-            index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_tas_tags_3c4525_gin'),
+            index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_tas_tags_256437_gin'),
         ),
         migrations.AddIndex(
             model_name='taskconnectors',
-            index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_tas_tags_e164c1_gin'),
+            index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_tas_tags_0ebd6d_gin'),
         ),
         migrations.AddIndex(
             model_name='subtaskoutput',
-            index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_sub_tags_cf41ca_gin'),
+            index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_sub_tags_e25b4c_gin'),
         ),
         migrations.AddIndex(
             model_name='subtaskinput',
-            index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_sub_tags_aca69a_gin'),
+            index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_sub_tags_fb9960_gin'),
         ),
         migrations.AddIndex(
             model_name='subtaskconnector',
-            index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_sub_tags_e6cfc0_gin'),
+            index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_sub_tags_60e299_gin'),
         ),
         migrations.AddIndex(
             model_name='subtask',
-            index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_sub_tags_ffd87d_gin'),
+            index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_sub_tags_d2fc43_gin'),
         ),
         migrations.AddIndex(
             model_name='defaultworkrelationselectiontemplate',
-            index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_def_tags_eef600_gin'),
+            index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_def_tags_b652d9_gin'),
         ),
         migrations.AddIndex(
             model_name='defaulttasktemplate',
-            index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_def_tags_ad763c_gin'),
+            index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_def_tags_c88200_gin'),
         ),
         migrations.AddIndex(
             model_name='defaultsubtasktemplate',
-            index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_def_tags_f7ca32_gin'),
+            index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_def_tags_e9c73d_gin'),
         ),
         migrations.AddIndex(
             model_name='defaultschedulingunittemplate',
-            index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_def_tags_c3916a_gin'),
+            index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_def_tags_3ab2d6_gin'),
         ),
         migrations.AddIndex(
             model_name='defaultgeneratortemplate',
-            index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_def_tags_2b9ef8_gin'),
+            index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_def_tags_89c89d_gin'),
         ),
         migrations.AddIndex(
             model_name='defaultdataproductspecificationstemplate',
-            index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_def_tags_0a7453_gin'),
+            index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_def_tags_269b1f_gin'),
         ),
         migrations.AddIndex(
             model_name='dataproducttransform',
-            index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_dat_tags_e9dc5f_gin'),
+            index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_dat_tags_380c1f_gin'),
         ),
         migrations.AddIndex(
             model_name='dataproducthash',
-            index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_dat_tags_1712df_gin'),
+            index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_dat_tags_dae145_gin'),
         ),
         migrations.AddIndex(
             model_name='dataproductarchiveinfo',
-            index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_dat_tags_7f581b_gin'),
+            index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_dat_tags_ebf2ef_gin'),
         ),
         migrations.AddIndex(
             model_name='dataproduct',
-            index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_dat_tags_567f63_gin'),
+            index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_dat_tags_5932a3_gin'),
         ),
     ]
diff --git a/SAS/TMSS/src/tmss/tmssapp/migrations/0003_populate.py b/SAS/TMSS/src/tmss/tmssapp/migrations/0003_populate.py
index 7dbb6053394aa14a436f508a58abec3dfd5251bd..8fcea39a130ebe08b53939653c6465d3343d71d5 100644
--- a/SAS/TMSS/src/tmss/tmssapp/migrations/0003_populate.py
+++ b/SAS/TMSS/src/tmss/tmssapp/migrations/0003_populate.py
@@ -6,7 +6,7 @@ from ..populate import *
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('tmssapp', '0002_auto_20190606_1535'),
+        ('tmssapp', '0002_auto_20200123_1057'),
     ]
 
     operations = [ migrations.RunPython(populate_choices) ]
diff --git a/SAS/TMSS/src/tmss/urls.py b/SAS/TMSS/src/tmss/urls.py
index c96864991dd29a38fa6b9ed385f1288f6684a14d..fd80e0ea5a09c91653c7fb3148b134a048ae3f9c 100644
--- a/SAS/TMSS/src/tmss/urls.py
+++ b/SAS/TMSS/src/tmss/urls.py
@@ -15,6 +15,7 @@ Including another URLconf
 """
 
 from django.contrib import admin
+from django.contrib.auth.views import LoginView, LogoutView
 from django.urls import path, re_path
 from django.conf.urls import url, include
 from django.views.generic.base import TemplateView
@@ -25,6 +26,7 @@ from rest_framework.documentation import include_docs_urls
 from drf_yasg.views import get_schema_view
 from drf_yasg import openapi
 
+
 #
 # Django style patterns
 #
@@ -46,6 +48,7 @@ swagger_schema_view = get_schema_view(
 
 urlpatterns = [
     path('admin/', admin.site.urls),
+    path('logout/', LogoutView.as_view(), name='logout'),
     path('docs/', include_docs_urls(title='TMSS API')),
     re_path(r'^swagger(?P<format>\.json|\.yaml)$', swagger_schema_view.without_ui(cache_timeout=0), name='schema-json'),
     path('swagger/', swagger_schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
@@ -145,4 +148,4 @@ router.register(r'task_blueprint_C', viewsets.TaskBlueprintViewSetJSONeditorOnli
 urlpatterns.extend(router.urls)
 
 # prefix everything for proxy
-urlpatterns = [url(r'^api/', include(urlpatterns))]
\ No newline at end of file
+urlpatterns = [url(r'^api/', include(urlpatterns)), url(r'^oidc/', include('mozilla_django_oidc.urls')),]
\ No newline at end of file
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/.circleci/config.yml b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/.circleci/config.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7c2b7c3c55992eb980b14c96eaa1a879ef170369
--- /dev/null
+++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/.circleci/config.yml
@@ -0,0 +1,100 @@
+# Python CircleCI 2.0 configuration file
+#
+# Check https://circleci.com/docs/2.0/language-python/ for more details
+#
+version: 2
+jobs:
+  build:
+    docker:
+      - image: mozilla/cidockerbases:docker-latest
+    working_directory: /
+
+    steps:
+      - run:
+          name: Get info
+          command: uname -v
+
+      - setup_remote_docker
+
+      - run:
+          name: Get info 
+          command: docker info
+
+      - run:
+          name: Install essential packages
+          command: apt-get install make
+
+      - checkout:
+          path: /oidc_testprovider
+
+      - run:
+          name: Build Docker images
+          working_directory: /oidc_testprovider
+          command: |
+            make build
+
+      - run:
+          name: Push to Dockerhub on tag
+          working_directory: /oidc_testprovider
+          command: |
+            function retry {
+              set +e
+              local n=0
+              local max=3
+              while true; do
+              "$@" && break || {
+                if [[ $n -lt $max ]]; then
+                  ((n++))
+                  echo "Command failed. Attempt $n/$max:"
+                else
+                  echo "Failed after $n attempts."
+                  exit 1
+                fi
+              }
+              done
+              set -e
+            }
+
+            # Namespace on dockerhub to push:
+            # https://hub.docker.com/u/mozilla/oidc-testprovider
+            export DOCKER_NAMESPACE=mozilla/oidc-testprovider
+            export IMAGES=(oidc_e2e_setup_py2 oidc_e2e_setup_py3 oidc_testprovider oidc_testrp_py2 oidc_testrp_py3 oidc_testrunner)
+
+            # If a tag was pushed to github, push tagged images and latest
+            # images to Dockerhub
+            if [ -n "${CIRCLE_TAG}" ]; then
+              # Log into Dockerhub
+              echo "${DOCKER_PASS}" | docker login -u="${DOCKER_USER}" --password-stdin
+
+              for IMAGE in "${IMAGES[@]}"
+              do
+                echo ""
+                echo ">>> WORKING ON ${IMAGE}..."
+                echo ""
+                # Tag and push tagged image.
+                retry docker tag "${IMAGE}:latest" "${DOCKER_NAMESPACE}:${IMAGE}-${CIRCLE_TAG}"
+                retry docker push "${DOCKER_NAMESPACE}:${IMAGE}-${CIRCLE_TAG}"
+
+                # Tag and push latest image.
+                retry docker tag "${IMAGE}:latest" "${DOCKER_NAMESPACE}:${IMAGE}-latest"
+                retry docker push "${DOCKER_NAMESPACE}:${IMAGE}-latest"
+              done
+            fi
+
+workflows:
+  version: 2
+
+  # workflow jobs are _not_ run in tag builds by default
+  # we use filters to whitelist jobs that should be run for tags
+
+  # workflow jobs are run in _all_ branch builds by default
+  # we use filters to blacklist jobs that shouldn't be run for a branch
+
+  # see: https://circleci.com/docs/2.0/workflows/#git-tag-job-execution
+
+  build-test-push:
+    jobs:
+      - build:
+          filters:
+            tags:
+              only: /.*/
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/HISTORY.md b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/HISTORY.md
new file mode 100644
index 0000000000000000000000000000000000000000..4d8d62422483fd93d04159b64dcc376cec4ae539
--- /dev/null
+++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/HISTORY.md
@@ -0,0 +1,35 @@
+HISTORY
+=======
+
+v0.9.3 (October 23rd, 2019)
+---------------------------
+
+Bugs:
+
+* fix docker push code
+
+
+v0.9.2 (October 22nd, 2019)
+---------------------------
+
+No substantive changes. Doing a new tag so as to push images to dockerhub.
+
+
+v0.9.1 (October 22nd, 2019)
+---------------------------
+
+Bugs:
+
+* fix `build` and `pull` rules in Makefile to use the correct tags
+
+
+v0.9.0 (October 22nd, 2019)
+---------------------------
+
+First tagged release.
+
+Features:
+
+* new `createuser` command in `oidc_testprovider` image
+* redid how images are tagged and we're now pushing them to dockerhub
+  in the `mozilla` user
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/Makefile b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..091744b79538494207e8499c5d2ab1d9a57d49d4
--- /dev/null
+++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/Makefile
@@ -0,0 +1,33 @@
+DEFAULT_GOAL := help
+
+NS ?= mozilla/oidc-testprovider
+IMAGES := oidc_testprovider oidc_testrunner oidc_testrp_py2 oidc_testrp_py3 oidc_e2e_setup_py2 oidc_e2e_setup_py3
+BUILD := $(addprefix build-,${IMAGES})
+PULL := $(addprefix pull-,$(IMAGES))
+CLEAN := $(addprefix clean-,$(IMAGES))
+
+.PHONY: help
+help:
+	@fgrep -h "##" Makefile | fgrep -v fgrep | sed 's/\(.*\):.*##/\1:/'
+
+.PHONY: build
+build: ${BUILD} ## Build all images
+
+.PHONY: pull
+pull: ${PULL} ## Pull all -latest images
+
+.PHONY: clean
+clean: ${CLEAN} ## Clean images and other artifacts
+
+.PHONY: ${BUILD}
+${BUILD}: build-%:
+	docker build -t $* -f dockerfiles/$* .
+
+.PHONY: ${PULL}
+${PULL}: pull-%:
+	docker pull ${NS}:$*-latest
+
+.PHONY: ${CLEAN}
+${CLEAN}: clean-%:
+	docker rmi ${NS}/$(subst _py,:py,$(*))
+	docker rmi $(subst _py,:py,$(*))
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/README.md b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..9037b454463959b2a99f01058e314f7d0d8de937
--- /dev/null
+++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/README.md
@@ -0,0 +1,98 @@
+# docker-test-mozilla-django-oidc
+
+The purpose of these docker images is to setup a local environment to develop and test
+`mozilla-django-oidc`.
+
+
+## oidc-testprovider
+
+https://hub.docker.com/r/mozilla/oidc-testprovider/tags?name=testprovider
+
+* Provides a docker image for an OIDC OP with preconfigured OIDC client IDs and secrets
+* OIDC provider endpoint is exposed in port `8080`
+* Provides a Django management command for creating users
+* Uses `django-oidc-provider`
+
+
+### Usage
+
+In order for this setup to work `testprovider`, `testrp` hostnames should resolve to the
+IP of the docker image (for local development it's `127.0.0.1`).
+
+You can add the resolution to your `/etc/hosts` file.
+
+You can also use [nip.io](http://nip.io/). For example, if you name the service
+"oidcprovider", then you could have these three variables:
+
+```
+OIDC_OP_AUTHORIZATION_ENDPOINT=http://oidcprovider.127.0.0.1.nip.io:8080/openid/authorize
+OIDC_OP_TOKEN_ENDPOINT=http://oidcprovider.127.0.0.1.nip.io:8080/openid/token
+OIDC_OP_USER_ENDPOINT=http://oidcprovider.127.0.0.1.nip.io:8080/openid/userinfo
+```
+
+### Example setup
+
+`docker-compose.yml`
+
+```
+version: '3'
+services:
+  testprovider:
+    image: mozilla/oidc-testprovider:oidc_testprovider-v0.9.3
+    ports:
+      - "8080:8080"
+```
+
+
+### Creating users in the container
+
+The `testprovider` image has a Django management command for creating users in
+the OIDC provider. This lets you create users on the command line.
+
+With an already running `testprovider` container run:
+
+```
+docker-compose exec testprovider manage.py createuser USERNAME PASSWORD EMAIL
+```
+
+
+## Other images
+
+All images are pushed to: https://hub.docker.com/r/mozilla/oidc-testprovider
+
+* `oidc_testprovider` (See above)
+* `oidc_testrunner`
+* `oidc_testrp_py{2,3}`
+    * Test django project preconfigured to work with `testprovider`
+    * Uses `mozilla-django-oidc` as an authentication backend
+    * Test RP is exposed in port `8081`
+    * Builds based in both python 2/3
+    * Environment variables
+        * `TEST_OIDC_ALGO={hs,rs}`
+* `oidc_e2e_setup_py{2,3}`
+    * Dockerized setup for e2e testing of mozilla-django-oidc
+
+
+### Example setup for oidc_testrp
+
+`docker-compose.yml`
+
+```
+version: '3'
+services:
+  testrp:
+    image: mozilla/oidc-testprovider:oidc_testrp_py3-v0.9.3
+    ports:
+      - "8081:8081"
+    environment:
+      - TEST_OIDC_ALGO=hs
+```
+
+## Development
+
+We use `make` to automate the docker image workflow.
+
+For more info run `make help`.
+
+Pushing a tag to GitHub will trigger building images and uploading them
+to Dockerhub.
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/docker-compose.yml b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/docker-compose.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a1022994588976175c6169fee1051c60b7b89cdf
--- /dev/null
+++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/docker-compose.yml
@@ -0,0 +1,6 @@
+version: '3'
+services:
+  testprovider:
+    image: mozilla/oidc-testprovider:oidc_testprovider-v0.9.3
+    ports:
+      - "8080:8080"
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/dockerfiles/oidc_e2e_setup_py2 b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/dockerfiles/oidc_e2e_setup_py2
new file mode 100644
index 0000000000000000000000000000000000000000..ac0e6a7bf389c7c6cb802137e8544f889b863853
--- /dev/null
+++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/dockerfiles/oidc_e2e_setup_py2
@@ -0,0 +1,29 @@
+FROM python:2-stretch
+
+EXPOSE 8080 8081
+
+RUN pip install virtualenv
+RUN virtualenv /testrp_env
+RUN virtualenv /testprovider_env
+
+COPY testprovider /testprovider/
+COPY testrp /testrp/
+
+RUN . /testprovider_env/bin/activate && pip install -r /testprovider/requirements.txt
+RUN . /testrp_env/bin/activate && pip install -r /testrp/requirements.txt
+
+# Install python and python dependencies
+RUN apt-get update && \
+    apt-get install -y wait-for-it
+
+# Install firefox
+RUN apt-get install -y --no-install-recommends firefox-esr && \
+    wget "https://download.mozilla.org/?product=firefox-latest&os=linux64&lang=en-US" -O /tmp/firefox.tar.bz2 && \
+    tar xvf /tmp/firefox.tar.bz2 -C /opt && \
+    rm /usr/bin/firefox && \
+    ln -s /opt/firefox/firefox /usr/bin/firefox
+
+# Install geckodriver
+RUN wget "https://github.com/mozilla/geckodriver/releases/download/v0.23.0/geckodriver-v0.23.0-linux32.tar.gz" -O /tmp/geckodriver.tar.gz && \
+    tar xvf /tmp/geckodriver.tar.gz -C /opt && \
+    ln -s /opt/geckodriver /usr/bin/geckodriver
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/dockerfiles/oidc_e2e_setup_py3 b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/dockerfiles/oidc_e2e_setup_py3
new file mode 100644
index 0000000000000000000000000000000000000000..9ed6e566c6b88dd21d5782d7e1b477290042b501
--- /dev/null
+++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/dockerfiles/oidc_e2e_setup_py3
@@ -0,0 +1,29 @@
+FROM python:3.6-stretch
+
+EXPOSE 8080 8081
+
+RUN pip install virtualenv
+RUN virtualenv /testrp_env
+RUN virtualenv /testprovider_env
+
+COPY testprovider /testprovider/
+COPY testrp /testrp/
+
+RUN . /testprovider_env/bin/activate && pip install -r /testprovider/requirements.txt
+RUN . /testrp_env/bin/activate && pip install -r /testrp/requirements.txt
+
+# Install python and python dependencies
+RUN apt-get update && \
+    apt-get install -y wait-for-it
+
+# Install firefox
+RUN apt-get install -y --no-install-recommends firefox-esr && \
+    wget "https://download.mozilla.org/?product=firefox-latest&os=linux64&lang=en-US" -O /tmp/firefox.tar.bz2 && \
+    tar xvf /tmp/firefox.tar.bz2 -C /opt && \
+    rm /usr/bin/firefox && \
+    ln -s /opt/firefox/firefox /usr/bin/firefox
+
+# Install geckodriver
+RUN wget "https://github.com/mozilla/geckodriver/releases/download/v0.23.0/geckodriver-v0.23.0-linux32.tar.gz" -O /tmp/geckodriver.tar.gz && \
+    tar xvf /tmp/geckodriver.tar.gz -C /opt && \
+    ln -s /opt/geckodriver /usr/bin/geckodriver
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/dockerfiles/oidc_testprovider b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/dockerfiles/oidc_testprovider
new file mode 100644
index 0000000000000000000000000000000000000000..464d90658afcef9c571ddcd4e6fb1f64bad37fe8
--- /dev/null
+++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/dockerfiles/oidc_testprovider
@@ -0,0 +1,7 @@
+FROM python:3.6
+EXPOSE 8088
+COPY testprovider /code/
+WORKDIR /code
+
+RUN pip install -r requirements.txt
+CMD ./bin/run.sh
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/dockerfiles/oidc_testrp_py2 b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/dockerfiles/oidc_testrp_py2
new file mode 100644
index 0000000000000000000000000000000000000000..369b2742d7ca7d06b7fa6e2aa20bc71aad6e089d
--- /dev/null
+++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/dockerfiles/oidc_testrp_py2
@@ -0,0 +1,7 @@
+FROM python:2-stretch
+EXPOSE 8081
+COPY testrp /code/
+WORKDIR /code
+
+RUN pip install -r requirements.txt
+CMD ./bin/run.sh
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/dockerfiles/oidc_testrp_py3 b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/dockerfiles/oidc_testrp_py3
new file mode 100644
index 0000000000000000000000000000000000000000..f4d52e4dfe609d8e4fc6168da02bb518200d6f2d
--- /dev/null
+++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/dockerfiles/oidc_testrp_py3
@@ -0,0 +1,7 @@
+FROM python:3-stretch
+EXPOSE 8081
+COPY testrp /code/
+WORKDIR /code
+
+RUN pip install -r requirements.txt
+CMD ./bin/run.sh
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/dockerfiles/oidc_testrunner b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/dockerfiles/oidc_testrunner
new file mode 100644
index 0000000000000000000000000000000000000000..422da4a032271f4268b88120df86fbd18e2f13f8
--- /dev/null
+++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/dockerfiles/oidc_testrunner
@@ -0,0 +1,18 @@
+FROM python:3-stretch
+
+# Install python and python dependencies
+RUN apt-get update && \
+    apt-get install -y wait-for-it && \
+    pip install six splinter
+
+# Install firefox
+RUN apt-get install -y --no-install-recommends firefox-esr && \
+    wget "https://download.mozilla.org/?product=firefox-latest&os=linux64&lang=en-US" -O /tmp/firefox.tar.bz2 && \
+    tar xvf /tmp/firefox.tar.bz2 -C /opt && \
+    rm /usr/bin/firefox && \
+    ln -s /opt/firefox/firefox /usr/bin/firefox
+
+# Install geckodriver
+RUN wget "https://github.com/mozilla/geckodriver/releases/download/v0.23.0/geckodriver-v0.23.0-linux32.tar.gz" -O /tmp/geckodriver.tar.gz && \
+    tar xvf /tmp/geckodriver.tar.gz -C /opt && \
+    ln -s /opt/geckodriver /usr/bin/geckodriver
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/bin/run.sh b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/bin/run.sh
new file mode 100755
index 0000000000000000000000000000000000000000..7008ec74df50c3a95634872328fa89ff59d98f42
--- /dev/null
+++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/bin/run.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+python manage.py migrate --noinput
+python manage.py loaddata fixtures.json
+python ./manage.py createuser paulus pauluspass paulus@localhost
+python manage.py runserver 0.0.0.0:8088
+
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/fixtures.json b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/fixtures.json
new file mode 100644
index 0000000000000000000000000000000000000000..8682aa8ab4f7fcca32dd6b9d2a26978208efe87b
--- /dev/null
+++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/fixtures.json
@@ -0,0 +1,140 @@
+[
+  {
+    "model": "sites.site",
+    "pk": 1,
+    "fields": {
+      "domain": "testprovider:8088",
+      "name": "testprovider"
+    }
+  },
+  {
+    "model": "oidc_provider.responsetype",
+    "pk": 1,
+    "fields": {
+      "value": "code",
+      "description": "code (Authorization Code Flow)"
+    }
+  },
+  {
+    "model": "oidc_provider.responsetype",
+    "pk": 2,
+    "fields": {
+      "value": "id_token",
+      "description": "id_token (Implicit Flow)"
+    }
+  },
+  {
+    "model": "oidc_provider.responsetype",
+    "pk": 3,
+    "fields": {
+      "value": "id_token token",
+      "description": "id_token token (Implicit Flow)"
+    }
+  },
+  {
+    "model": "oidc_provider.responsetype",
+    "pk": 4,
+    "fields": {
+      "value": "code token",
+      "description": "code token (Hybrid Flow)"
+    }
+  },
+  {
+    "model": "oidc_provider.responsetype",
+    "pk": 5,
+    "fields": {
+      "value": "code id_token",
+      "description": "code id_token (Hybrid Flow)"
+    }
+  },
+  {
+    "model": "oidc_provider.responsetype",
+    "pk": 6,
+    "fields": {
+      "value": "code id_token token",
+      "description": "code id_token token (Hybrid Flow)"
+    }
+  },
+  {
+    "model": "oidc_provider.client",
+    "pk": 1,
+    "fields": {
+      "name": "testrpHS256",
+      "owner": null,
+      "client_type": "confidential",
+      "client_id": "1",
+      "client_secret": "secret",
+      "jwt_alg": "HS256",
+      "date_created": "2017-11-10",
+      "website_url": "",
+      "terms_url": "",
+      "contact_email": "",
+      "logo": "",
+      "reuse_consent": true,
+      "require_consent": true,
+      "_redirect_uris": "http://localhost:5005/oidc/callback/",
+      "_post_logout_redirect_uris": "",
+      "_scope": "",
+      "response_types": [
+        1
+      ]
+    }
+  },
+    {
+    "model": "oidc_provider.client",
+    "pk": 2,
+    "fields": {
+      "name": "testrpHS256",
+      "owner": null,
+      "client_type": "confidential",
+      "client_id": "2",
+      "client_secret": "secret",
+      "jwt_alg": "HS256",
+      "date_created": "2017-11-10",
+      "website_url": "",
+      "terms_url": "",
+      "contact_email": "",
+      "logo": "",
+      "reuse_consent": true,
+      "require_consent": true,
+      "_redirect_uris": "http://localhost:8008/oidc/callback/",
+      "_post_logout_redirect_uris": "",
+      "_scope": "",
+      "response_types": [
+        1
+      ]
+    }
+  },
+  {
+    "model": "oidc_provider.client",
+    "pk": 3,
+    "fields": {
+      "name": "testrpRS256",
+      "owner": null,
+      "client_type": "confidential",
+      "client_id": "3",
+      "client_secret": "secret",
+      "jwt_alg": "RS256",
+      "date_created": "2017-11-10",
+      "website_url": "",
+      "terms_url": "",
+      "contact_email": "",
+      "logo": "",
+      "reuse_consent": true,
+      "require_consent": true,
+      "_redirect_uris": "http://localhost:8008/oidc/callback/",
+      "_post_logout_redirect_uris": "",
+      "_scope": "",
+      "response_types": [
+        1
+      ]
+    }
+  },
+  {
+    "model": "oidc_provider.rsakey",
+    "pk": 3,
+    "fields": {
+      "key": "-----BEGIN RSA PRIVATE KEY-----\nMIICXAIBAAKBgQDAAgiIdiJG7GSMKTRbnGjWpHp1ulJ43/iQjDywWh5MP3in2PK8\nPVI6ItxIFLV81nWZMymA7hjfP7adOlxKY6rI+fExn8cTimI3W/oX6mHrPXm52uj/\nwe839pxxkeD7cmWgaif9Sujuy5AHUuUM1BTlO55POHkmhWyYMKC2P29qgQIDAQAB\nAoGAUHdJri6b1M8yoA6Qk6frw7AwZfAMqf1qxOEQefN6aQfcf7MKntqwAA8l88tB\n96xEokxvo0mlAMJJvIB9tusn4dIHKpmQGacQWVd/KONxPkvyuGgQXX5KCusZTbg7\ni6YQM52RGbExVFWLdGYJRBvzyfRkWX0b4LiderPZUiD6J/UCQQDZIgnLqYyGw3Ro\nnNboWYyOtLhKMF59f/0aSMXLlWdsnFG8kVm/7tw6jcDBalELci/+ExL2JACGwDea\n8DpvWiEDAkEA4mCovWmMDiS8tQCeY5NDic1wMp51+Ya8RX47bvb5F+X7SSE9L87y\n6eU9zVBSY8F+9npkvrxoU9PlKbS3Lzz1KwJAZ5/8BsuS+lnbe3Wmhtr93rlW3mk5\nHzHu7BVg+GkEI+xygcjoiVYImpU+MdB4fzrutpYJzZie+7BOmU4exTfBWwJBAKj+\nN3mO/Xrhee41VAhJuzV4I7XmDXQFXS8TmRKxVCq/COQC6EZ0W2q4M3a964OEw18E\n54hr5gYOPRjxS378JpkCQDjKw2Vyw0S0M8O2hOGuNsUtlGApYKt2iA41jGUf7bvO\nWz/tQuEIXQMd4e9zxNxOzPJOtjR1gyPZyi/FvsgDJDU=\n-----END RSA PRIVATE KEY-----"
+    }
+  }
+]
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/manage.py b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/manage.py
new file mode 100755
index 0000000000000000000000000000000000000000..5fccf17e2ac7382f4d86d3e79a1d789f72bda004
--- /dev/null
+++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/manage.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+import os
+import sys
+
+if __name__ == "__main__":
+    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "oidcprovider.settings")
+    try:
+        from django.core.management import execute_from_command_line
+    except ImportError:
+        # The above import may fail for some other reason. Ensure that the
+        # issue is really that Django is missing to avoid masking other
+        # exceptions on Python 2.
+        try:
+            import django
+        except ImportError:
+            raise ImportError(
+                "Couldn't import Django. Are you sure it's installed and "
+                "available on your PYTHONPATH environment variable? Did you "
+                "forget to activate a virtual environment?"
+            )
+        raise
+    execute_from_command_line(sys.argv)
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/__init__.py b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/management/__init__.py b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/management/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/management/commands/__init__.py b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/management/commands/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/management/commands/createuser.py b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/management/commands/createuser.py
new file mode 100644
index 0000000000000000000000000000000000000000..11fc496c521cf65641dbe5bc4e10509e379f231c
--- /dev/null
+++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/management/commands/createuser.py
@@ -0,0 +1,33 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+"""
+Create a user in the OIDC provider.
+"""
+
+from django.contrib.auth.models import User
+from django.core.management.base import BaseCommand
+
+
+class Command(BaseCommand):
+    help = "Create a user in the OIDC provider."
+
+    def add_arguments(self, parser):
+        parser.add_argument("username", help="account username")
+        parser.add_argument("password", help="account password")
+        parser.add_argument("email", help="account email address")
+
+    def handle(self, **options):
+        username = options["username"]
+        password = options["password"]
+        email = options["email"]
+
+        if User.objects.filter(username=username).exists():
+            self.stdout.write("User {} already exists.".format(username))
+            return
+
+        user = User.objects.create(username=username, email=email)
+        user.set_password(password)
+        user.save()
+        self.stdout.write("User {} created.".format(username))
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/settings.py b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/settings.py
new file mode 100644
index 0000000000000000000000000000000000000000..9f2eca62c36f16daada5279a294470405c38cfdb
--- /dev/null
+++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/settings.py
@@ -0,0 +1,149 @@
+"""
+Django settings for oidcprovider project.
+
+Generated by 'django-admin startproject' using Django 1.11.6.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/1.11/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/1.11/ref/settings/
+"""
+
+import os
+
+# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = '_brj&4ea0ec%ybc(rz32jpqwypdy4@d9lttg&g7!^e(m!-52si'
+SESSION_COOKIE_NAME = 'oidcprovider'
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+ALLOWED_HOSTS = ['*']
+SITE_ID = 1
+
+# Application definition
+
+INSTALLED_APPS = [
+    'django.contrib.admin',
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.messages',
+    'django.contrib.staticfiles',
+    'django.contrib.sites',
+
+    'oidc_provider',
+    'oidcprovider',
+    'pinax_theme_bootstrap',
+    'account',
+    'bootstrapform',
+
+
+]
+
+MIDDLEWARE = [
+    'django.middleware.security.SecurityMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.middleware.common.CommonMiddleware',
+    'django.middleware.csrf.CsrfViewMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.contrib.messages.middleware.MessageMiddleware',
+    'django.middleware.clickjacking.XFrameOptionsMiddleware',
+
+    # django-user-accounts
+    'account.middleware.LocaleMiddleware',
+    'account.middleware.TimezoneMiddleware'
+]
+
+ROOT_URLCONF = 'oidcprovider.urls'
+
+TEMPLATES = [
+    {
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
+        'DIRS': [],
+        'APP_DIRS': True,
+        'OPTIONS': {
+            'context_processors': [
+                'django.template.context_processors.debug',
+                'django.template.context_processors.request',
+                'django.contrib.auth.context_processors.auth',
+                'django.contrib.messages.context_processors.messages',
+                'pinax_theme_bootstrap.context_processors.theme',
+                'account.context_processors.account',
+            ],
+        },
+    },
+]
+
+WSGI_APPLICATION = 'oidcprovider.wsgi.application'
+
+
+# Database
+# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
+
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.sqlite3',
+        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
+    }
+}
+
+
+# Password validation
+# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+    {
+        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+    },
+]
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/1.11/topics/i18n/
+
+LANGUAGE_CODE = 'en-us'
+
+TIME_ZONE = 'UTC'
+
+USE_I18N = True
+
+USE_L10N = True
+
+USE_TZ = True
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/1.11/howto/static-files/
+
+STATIC_URL = '/static/'
+
+
+# OIDC provider settings
+LOGIN_URL = '/account/login'
+ACCOUNT_EMAIL_CONFIRMATION_EMAIL = False
+
+
+# Workaround to actually delete the account instead of marking it as inactive
+def _delete_user(obj):
+    obj.user.delete()
+
+
+ACCOUNT_DELETION_MARK_CALLBACK = _delete_user
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/templates/home.html b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/templates/home.html
new file mode 100644
index 0000000000000000000000000000000000000000..6daa91684373aadf91bc4e4c9fcbb835e32260be
--- /dev/null
+++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/templates/home.html
@@ -0,0 +1,14 @@
+{% extends "site_base.html" %}
+
+{% block head_title %}Home{% endblock %}
+
+{% block body %}
+<h1>Welcome to {% if SITE_NAME %}{{ SITE_NAME }}{% else %}testprovider{% endif %}!</h1>
+<div>
+  {% if request.user.is_authenticated %}
+    <p>Current user: {{ user.email }}</p>
+  {% else %}
+    <p>User not logged in</p>
+  {% endif %}
+</div>
+{% endblock body %}
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/templates/site_base.html b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/templates/site_base.html
new file mode 100644
index 0000000000000000000000000000000000000000..909a80cc317bb3043c437609636d45f44a184112
--- /dev/null
+++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/templates/site_base.html
@@ -0,0 +1,28 @@
+{% extends "theme_bootstrap/base.html" %}
+{% load static %}
+
+{% block footer %}
+    <p>Test OIDC provider</p>
+{% endblock %}
+
+{% block styles %}
+  <link rel="stylesheet" href="{% static 'pinax/css/theme.css' %}">
+  <link rel="stylesheet"
+        href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
+        integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
+        crossorigin="anonymous">
+  <link rel="stylesheet"
+        href="https://stackpath.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css"
+        integrity="sha384-MI32KR77SgI9QAPUs+6R7leEOwtop70UsjEtFEezfKnMjXWx15NENsZpfDgq8m8S"
+        crossorigin="anonymous">
+{% endblock styles %}
+
+{% block scripts %}
+  <script src="https://code.jquery.com/jquery-2.2.4.min.js"
+          integrity="sha384-rY/jv8mMhqDabXSo+UCggqKtdmBfd3qC2/KvyTDNQ6PcUJXaxK1tMepoQda4g5vB"
+          crossorigin="anonymous"></script>
+  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"
+          integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
+          crossorigin="anonymous"></script>
+{% endblock scripts %}
+
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/urls.py b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/urls.py
new file mode 100644
index 0000000000000000000000000000000000000000..3f8296b1167063f2b99f397ea3d166361a41e2a2
--- /dev/null
+++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/urls.py
@@ -0,0 +1,12 @@
+from django.conf.urls import include, url
+from django.contrib import admin
+
+from .views import HomePageView
+
+urlpatterns = [
+    url(r'^openid/', include('oidc_provider.urls', namespace='oidc_provider')),
+    url(r'^account/', include('account.urls')),
+    url(r'^admin/', admin.site.urls),
+    url(r'^$', HomePageView.as_view(), name='home'),
+
+]
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/views.py b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/views.py
new file mode 100644
index 0000000000000000000000000000000000000000..14cc50fec47aa54e48618de782f6e235eab07fda
--- /dev/null
+++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/views.py
@@ -0,0 +1,6 @@
+from django.views.generic.base import TemplateView
+
+
+class HomePageView(TemplateView):
+
+    template_name = "home.html"
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/wsgi.py b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/wsgi.py
new file mode 100644
index 0000000000000000000000000000000000000000..a995efc052615dc3c1882ffff6318636e1f3711d
--- /dev/null
+++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/oidcprovider/wsgi.py
@@ -0,0 +1,16 @@
+"""
+WSGI config for oidcprovider project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "oidcprovider.settings")
+
+application = get_wsgi_application()
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/requirements.txt b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..78415f8c8b022fbacb3f6dbe4678483093b76c1d
--- /dev/null
+++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testprovider/requirements.txt
@@ -0,0 +1,4 @@
+django<3
+django-oidc-provider
+django-user-accounts
+pinax-theme-bootstrap
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/bin/run.sh b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/bin/run.sh
new file mode 100755
index 0000000000000000000000000000000000000000..6c365a5d40df50b7d86136df18a16d4cb67d3358
--- /dev/null
+++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/bin/run.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+TEST_OIDC_ALGO=${TEST_OIDC_ALGO}
+RUNNER="./bin/run_$TEST_OIDC_ALGO.sh"
+
+exec $RUNNER
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/bin/run_hs.sh b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/bin/run_hs.sh
new file mode 100755
index 0000000000000000000000000000000000000000..d2880cfa48ecabdfa4ad011e7d972eb71b365d37
--- /dev/null
+++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/bin/run_hs.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+export OIDC_RP_CLIENT_ID='1'
+export OIDC_RP_CLIENT_SECRET='bd01adf93cfb'
+python manage.py migrate --noinput
+python manage.py runserver 0.0.0.0:8081
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/bin/run_rs.sh b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/bin/run_rs.sh
new file mode 100755
index 0000000000000000000000000000000000000000..82e27b82659ce09f09899f653a8f8c51735ce816
--- /dev/null
+++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/bin/run_rs.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+export OIDC_RP_IDP_SIGN_KEY=$(cat provider_rsa.key)
+export OIDC_RP_CLIENT_ID='2'
+export OIDC_RP_CLIENT_SECRET='a6b4dad2f215'
+export OIDC_RP_SIGN_ALGO='RS256'
+python manage.py migrate --noinput
+python manage.py runserver 0.0.0.0:8081
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/manage.py b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/manage.py
new file mode 100755
index 0000000000000000000000000000000000000000..b588f3da043208c06dc027303e704d9a2f35b0f5
--- /dev/null
+++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/manage.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+import os
+import sys
+
+if __name__ == "__main__":
+    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testrp.settings")
+    try:
+        from django.core.management import execute_from_command_line
+    except ImportError:
+        # The above import may fail for some other reason. Ensure that the
+        # issue is really that Django is missing to avoid masking other
+        # exceptions on Python 2.
+        try:
+            import django
+        except ImportError:
+            raise ImportError(
+                "Couldn't import Django. Are you sure it's installed and "
+                "available on your PYTHONPATH environment variable? Did you "
+                "forget to activate a virtual environment?"
+            )
+        raise
+    execute_from_command_line(sys.argv)
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/provider_rsa.key b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/provider_rsa.key
new file mode 100644
index 0000000000000000000000000000000000000000..8d4b4180509d3405354da78ac948c4eda504dc3d
--- /dev/null
+++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/provider_rsa.key
@@ -0,0 +1,6 @@
+-----BEGIN PUBLIC KEY-----
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAAgiIdiJG7GSMKTRbnGjWpHp1
+ulJ43/iQjDywWh5MP3in2PK8PVI6ItxIFLV81nWZMymA7hjfP7adOlxKY6rI+fEx
+n8cTimI3W/oX6mHrPXm52uj/we839pxxkeD7cmWgaif9Sujuy5AHUuUM1BTlO55P
+OHkmhWyYMKC2P29qgQIDAQAB
+-----END PUBLIC KEY-----
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/requirements.txt b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..4bb815165fb59dae48ce4d0cf1aff7e6ee6c610b
--- /dev/null
+++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/requirements.txt
@@ -0,0 +1,6 @@
+django
+djangorestframework
+python-decouple
+mozilla-django-oidc
+six
+splinter
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/testrp/__init__.py b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/testrp/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/testrp/settings.py b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/testrp/settings.py
new file mode 100644
index 0000000000000000000000000000000000000000..e746e99f9d076b29e5a1724eb0317b1b87f8e113
--- /dev/null
+++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/testrp/settings.py
@@ -0,0 +1,153 @@
+"""
+Django settings for testrp project.
+
+Generated by 'django-admin startproject' using Django 1.11.6.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/1.11/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/1.11/ref/settings/
+"""
+
+import os
+
+from decouple import config
+
+
+# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = '@n!q#(pw!ta3_^*!i7&8m(ev91qdju(5^ijx)a%5+upwv+s_u-'
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+ALLOWED_HOSTS = ['*']
+
+
+# Application definition
+
+INSTALLED_APPS = [
+    'django.contrib.admin',
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.messages',
+    'django.contrib.staticfiles',
+
+    'rest_framework',
+    'mozilla_django_oidc',
+    'testrp'
+]
+
+MIDDLEWARE = [
+    'django.middleware.security.SecurityMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.middleware.common.CommonMiddleware',
+    'django.middleware.csrf.CsrfViewMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.contrib.messages.middleware.MessageMiddleware',
+    'django.middleware.clickjacking.XFrameOptionsMiddleware',
+]
+
+# Django<1.10 Compatibility
+MIDDLEWARE_CLASSES = MIDDLEWARE
+
+ROOT_URLCONF = 'testrp.urls'
+
+TEMPLATES = [
+    {
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
+        'DIRS': [],
+        'APP_DIRS': True,
+        'OPTIONS': {
+            'context_processors': [
+                'django.template.context_processors.debug',
+                'django.template.context_processors.request',
+                'django.contrib.auth.context_processors.auth',
+                'django.contrib.messages.context_processors.messages',
+            ],
+        },
+    },
+]
+
+WSGI_APPLICATION = 'testrp.wsgi.application'
+
+
+# Database
+# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
+
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.sqlite3',
+        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
+    }
+}
+
+
+# Password validation
+# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+    {
+        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+    },
+]
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/1.11/topics/i18n/
+
+LANGUAGE_CODE = 'en-us'
+
+TIME_ZONE = 'UTC'
+
+USE_I18N = True
+
+USE_L10N = True
+
+USE_TZ = True
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/1.11/howto/static-files/
+
+STATIC_URL = '/static/'
+
+REST_FRAMEWORK = {
+    'DEFAULT_AUTHENTICATION_CLASSES': [
+        'mozilla_django_oidc.contrib.drf.OIDCAuthentication'
+    ]
+}
+
+
+# OIDC SETUP
+AUTHENTICATION_BACKENDS = (
+    'mozilla_django_oidc.auth.OIDCAuthenticationBackend',
+)
+
+OIDC_RP_CLIENT_ID = config('OIDC_RP_CLIENT_ID')
+OIDC_RP_CLIENT_SECRET = config('OIDC_RP_CLIENT_SECRET')
+OIDC_RP_SIGN_ALGO = config('OIDC_RP_SIGN_ALGO', default='HS256')
+OIDC_RP_IDP_SIGN_KEY = config('OIDC_RP_IDP_SIGN_KEY', default=None)
+OIDC_OP_AUTHORIZATION_ENDPOINT = 'http://testprovider:8080/openid/authorize'
+OIDC_OP_TOKEN_ENDPOINT = 'http://testprovider:8080/openid/token'
+OIDC_OP_USER_ENDPOINT = 'http://testprovider:8080/openid/userinfo'
+
+LOGOUT_REDIRECT_URL = '/'
+LOGIN_REDIRECT_URL = '/'
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/testrp/templates/home.html b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/testrp/templates/home.html
new file mode 100644
index 0000000000000000000000000000000000000000..0b07cc8c9ec7d6e92d3bed40928d6d5111d46074
--- /dev/null
+++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/testrp/templates/home.html
@@ -0,0 +1,22 @@
+<html>
+  <body>
+    <div>
+      Welcome to testrp!
+    </div>
+    <div>
+      {% if request.user.is_authenticated %}
+        <p>Current user: {{ user.email }}</p>
+        <div>
+          <form action="/oidc/logout/" method="POST">
+            {% csrf_token %}
+            <input type="submit" value="Logout"/>
+          </form>
+        </div>
+      {% else %}
+        <a href="{% url 'oidc_authentication_init' %}">
+          Login
+        </a>
+      {% endif %}
+    </div>
+  </body>
+</html>
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/testrp/urls.py b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/testrp/urls.py
new file mode 100644
index 0000000000000000000000000000000000000000..beaf5a5920817232dc4e9e3b36fb25c9b48c0c39
--- /dev/null
+++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/testrp/urls.py
@@ -0,0 +1,10 @@
+from django.conf.urls import include, url
+
+from .views import HomePageView, TestAPIView
+
+
+urlpatterns = [
+    url(r'^oidc/', include('mozilla_django_oidc.urls')),
+    url(r'^api/$', TestAPIView.as_view(), name='api'),
+    url(r'^$', HomePageView.as_view(), name='home')
+]
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/testrp/views.py b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/testrp/views.py
new file mode 100644
index 0000000000000000000000000000000000000000..47b860cce32ef0b7be8f307f0ea352cb16ce486c
--- /dev/null
+++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/testrp/views.py
@@ -0,0 +1,17 @@
+from django.views.generic.base import TemplateView
+from mozilla_django_oidc.utils import is_authenticated
+from rest_framework.response import Response
+from rest_framework.views import APIView
+
+
+class HomePageView(TemplateView):
+
+    template_name = "home.html"
+
+
+class TestAPIView(APIView):
+
+    def get(self, request):
+        return Response({
+            'is_authenticated': is_authenticated(request.user)
+        })
diff --git a/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/testrp/wsgi.py b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/testrp/wsgi.py
new file mode 100644
index 0000000000000000000000000000000000000000..ea4648b82c599721ede45f39c98263c3fac19d73
--- /dev/null
+++ b/SAS/TMSS/test/oidc/docker-test-mozilla-django-oidc/testrp/testrp/wsgi.py
@@ -0,0 +1,16 @@
+"""
+WSGI config for testrp project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testrp.settings")
+
+application = get_wsgi_application()
diff --git a/SAS/TMSS/test/oidc/example/__pycache__/__init__.cpython-34.pyc b/SAS/TMSS/test/oidc/example/__pycache__/__init__.cpython-34.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..819d68a23dababb1257d69f3b39b35a9d08a0ef0
Binary files /dev/null and b/SAS/TMSS/test/oidc/example/__pycache__/__init__.cpython-34.pyc differ
diff --git a/SAS/TMSS/test/oidc/example/__pycache__/app.cpython-34.pyc b/SAS/TMSS/test/oidc/example/__pycache__/app.cpython-34.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..004bd471eec2552f614d26573477e473b8be185d
Binary files /dev/null and b/SAS/TMSS/test/oidc/example/__pycache__/app.cpython-34.pyc differ
diff --git a/SAS/TMSS/test/oidc/example/__pycache__/views.cpython-34.pyc b/SAS/TMSS/test/oidc/example/__pycache__/views.cpython-34.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..0f4d528467e3f5573f883a850f931ff5a4c697dc
Binary files /dev/null and b/SAS/TMSS/test/oidc/example/__pycache__/views.cpython-34.pyc differ
diff --git a/SAS/TMSS/test/oidc/example/__pycache__/wsgi.cpython-34.pyc b/SAS/TMSS/test/oidc/example/__pycache__/wsgi.cpython-34.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..4d49e3aecb16e15abd0ed70fdceb8eb407a468ec
Binary files /dev/null and b/SAS/TMSS/test/oidc/example/__pycache__/wsgi.cpython-34.pyc differ
diff --git a/SAS/TMSS/test/oidc/keycloak/create-keycloak-user.sh b/SAS/TMSS/test/oidc/keycloak/create-keycloak-user.sh
new file mode 100644
index 0000000000000000000000000000000000000000..1ede32adf3d5e7ab72f078c46fa8c438ae1464d0
--- /dev/null
+++ b/SAS/TMSS/test/oidc/keycloak/create-keycloak-user.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+cd keycloak/bin
+./kcadm.sh config credentials --server http://localhost:8080/auth --realm master --user admin --password admin
+
+USERID=$(./kcadm.sh create users -r demo -s username=monitoring-user -s enabled=true -o --fields id | jq '.id' | tr -d '"')
+echo $USERID
+./kcadm.sh update users/$USERID/reset-password -r demo -s type=password -s value=default -s temporary=false -n
+./kcadm.sh add-roles --uusername monitoring-user --rolename monitoring -r demo
+
diff --git a/SAS/TMSS/test/oidc/keycloak/realm-export.json b/SAS/TMSS/test/oidc/keycloak/realm-export.json
new file mode 100644
index 0000000000000000000000000000000000000000..5fa0d5bae6b1d0cf4faf618579693af9ce2c39e7
--- /dev/null
+++ b/SAS/TMSS/test/oidc/keycloak/realm-export.json
@@ -0,0 +1,1808 @@
+
+  "id": "demo",
+  "realm": "demo",
+  "notBefore": 0,
+  "revokeRefreshToken": false,
+  "refreshTokenMaxReuse": 0,
+  "accessTokenLifespan": 300,
+  "accessTokenLifespanForImplicitFlow": 900,
+  "ssoSessionIdleTimeout": 1800,
+  "ssoSessionMaxLifespan": 36000,
+  "offlineSessionIdleTimeout": 2592000,
+  "offlineSessionMaxLifespanEnabled": false,
+  "offlineSessionMaxLifespan": 5184000,
+  "accessCodeLifespan": 60,
+  "accessCodeLifespanUserAction": 300,
+  "accessCodeLifespanLogin": 1800,
+  "actionTokenGeneratedByAdminLifespan": 43200,
+  "actionTokenGeneratedByUserLifespan": 300,
+  "enabled": true,
+  "sslRequired": "external",
+  "registrationAllowed": false,
+  "registrationEmailAsUsername": false,
+  "rememberMe": false,
+  "verifyEmail": false,
+  "loginWithEmailAllowed": true,
+  "duplicateEmailsAllowed": false,
+  "resetPasswordAllowed": false,
+  "editUsernameAllowed": false,
+  "bruteForceProtected": false,
+  "permanentLockout": false,
+  "maxFailureWaitSeconds": 900,
+  "minimumQuickLoginWaitSeconds": 60,
+  "waitIncrementSeconds": 60,
+  "quickLoginCheckMilliSeconds": 1000,
+  "maxDeltaTimeSeconds": 43200,
+  "failureFactor": 30,
+  "roles": {
+    "realm": [
+      {
+        "id": "9cdbfeb1-7dea-442b-a798-0845a15b3fef",
+        "name": "uma_authorization",
+        "description": "${role_uma_authorization}",
+        "composite": false,
+        "clientRole": false,
+        "containerId": "demo",
+        "attributes": {}
+      },
+      {
+        "id": "5c42617f-2d8c-422d-8d15-b7630067990e",
+        "name": "demo-administrator",
+        "composite": false,
+        "clientRole": false,
+        "containerId": "demo",
+        "attributes": {}
+      },
+      {
+        "id": "46b3080a-2957-4bd6-9d06-da15dac503e4",
+        "name": "promoter",
+        "composite": false,
+        "clientRole": false,
+        "containerId": "demo",
+        "attributes": {}
+      },
+      {
+        "id": "16a657ca-7a97-4d27-b1d1-8a65087a1f19",
+        "name": "offline_access",
+        "description": "${role_offline-access}",
+        "composite": false,
+        "clientRole": false,
+        "containerId": "demo",
+        "attributes": {}
+      },
+      {
+        "id": "cecacd7e-1775-4ac1-ba99-93d9020ead83",
+        "name": "monitoring",
+        "composite": false,
+        "clientRole": false,
+        "containerId": "demo",
+        "attributes": {}
+      }
+    ],
+    "client": {
+      "realm-management": [
+        {
+          "id": "e441b81d-6ae3-4cf7-bfbf-ed4215eb2f47",
+          "name": "create-client",
+          "description": "${role_create-client}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        },
+        {
+          "id": "8c7cd27d-dfc5-4f83-80bf-373255b888d2",
+          "name": "manage-realm",
+          "description": "${role_manage-realm}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        },
+        {
+          "id": "ed027a01-9172-4e2c-a3c5-959de3d24c22",
+          "name": "view-users",
+          "description": "${role_view-users}",
+          "composite": true,
+          "composites": {
+            "client": {
+              "realm-management": [
+                "query-users",
+                "query-groups"
+              ]
+            }
+          },
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        },
+        {
+          "id": "bf27552e-da67-4780-8d14-c795bc9f3407",
+          "name": "query-clients",
+          "description": "${role_query-clients}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        },
+        {
+          "id": "d6beb376-5920-411d-93fc-b90c091d3d2f",
+          "name": "manage-events",
+          "description": "${role_manage-events}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        },
+        {
+          "id": "3c5e7a53-7ead-4726-940f-d4190b0ec5c7",
+          "name": "manage-users",
+          "description": "${role_manage-users}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        },
+        {
+          "id": "031098a6-8e77-4e01-912b-e50a1f9e7749",
+          "name": "manage-authorization",
+          "description": "${role_manage-authorization}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        },
+        {
+          "id": "9fdf5253-30a4-4b3d-8ca7-2044396d9ccb",
+          "name": "query-groups",
+          "description": "${role_query-groups}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        },
+        {
+          "id": "d43dc9bd-5d06-4eae-9708-95f7e835fbfd",
+          "name": "view-clients",
+          "description": "${role_view-clients}",
+          "composite": true,
+          "composites": {
+            "client": {
+              "realm-management": [
+                "query-clients"
+              ]
+            }
+          },
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        },
+        {
+          "id": "693555e2-14e1-461e-98a1-c8cab8137f8f",
+          "name": "view-realm",
+          "description": "${role_view-realm}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        },
+        {
+          "id": "6b90a2d2-f366-457d-9469-c5da38f990a6",
+          "name": "query-realms",
+          "description": "${role_query-realms}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        },
+        {
+          "id": "c073af53-414a-41f0-b093-0b68c0f1ecba",
+          "name": "impersonation",
+          "description": "${role_impersonation}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        },
+        {
+          "id": "1283a4ff-5217-449c-8c85-ddff72ed73fd",
+          "name": "query-users",
+          "description": "${role_query-users}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        },
+        {
+          "id": "57a640d0-f017-46f8-a23e-d3fc7f8e7de8",
+          "name": "realm-admin",
+          "description": "${role_realm-admin}",
+          "composite": true,
+          "composites": {
+            "client": {
+              "realm-management": [
+                "manage-realm",
+                "create-client",
+                "view-users",
+                "query-clients",
+                "manage-events",
+                "manage-authorization",
+                "manage-users",
+                "query-groups",
+                "view-realm",
+                "view-clients",
+                "query-realms",
+                "impersonation",
+                "query-users",
+                "manage-clients",
+                "view-authorization",
+                "view-events",
+                "view-identity-providers",
+                "manage-identity-providers"
+              ]
+            }
+          },
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        },
+        {
+          "id": "5d4108b8-c2bc-44f2-9038-ea18bf3164ef",
+          "name": "manage-clients",
+          "description": "${role_manage-clients}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        },
+        {
+          "id": "7ee7d0f4-d61f-4e01-ac37-86a7d42e30a0",
+          "name": "view-authorization",
+          "description": "${role_view-authorization}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        },
+        {
+          "id": "9d6dd55e-2d70-4b35-9ae7-43b59ff84cd5",
+          "name": "view-events",
+          "description": "${role_view-events}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        },
+        {
+          "id": "e2b85d94-2efb-458f-ae69-84e04d0975e4",
+          "name": "view-identity-providers",
+          "description": "${role_view-identity-providers}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        },
+        {
+          "id": "466294e7-feb8-4d2f-ae45-6807160553ae",
+          "name": "manage-identity-providers",
+          "description": "${role_manage-identity-providers}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        }
+      ],
+      "demo-frontend": [],
+      "security-admin-console": [],
+      "admin-cli": [],
+      "demo-api": [],
+      "broker": [
+        {
+          "id": "b375956d-b283-45b7-aa0b-76e19d3c9b2f",
+          "name": "read-token",
+          "description": "${role_read-token}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "d165281c-4c36-4185-bfbd-589c8a52945f",
+          "attributes": {}
+        }
+      ],
+      "account": [
+        {
+          "id": "aada5867-9268-4e37-b206-491041cd3360",
+          "name": "view-profile",
+          "description": "${role_view-profile}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "f5c24837-4fc7-4994-9c1e-bd70f2cbeed6",
+          "attributes": {}
+        },
+        {
+          "id": "39893eb7-65a6-4d5e-9704-f59952deaeab",
+          "name": "manage-account",
+          "description": "${role_manage-account}",
+          "composite": true,
+          "composites": {
+            "client": {
+              "account": [
+                "manage-account-links"
+              ]
+            }
+          },
+          "clientRole": true,
+          "containerId": "f5c24837-4fc7-4994-9c1e-bd70f2cbeed6",
+          "attributes": {}
+        },
+        {
+          "id": "193f4d1a-4c4a-4029-9092-1aa44df23b42",
+          "name": "manage-account-links",
+          "description": "${role_manage-account-links}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "f5c24837-4fc7-4994-9c1e-bd70f2cbeed6",
+          "attributes": {}
+        }
+      ]
+    }
+  },
+  "groups": [
+    {
+      "id": "62909b3a-eb39-4916-a640-f401d0592a47",
+      "name": "administrator",
+      "path": "/administrator",
+      "attributes": {},
+      "realmRoles": [
+        "demo-administrator"
+      ],
+      "clientRoles": {},
+      "subGroups": []
+    },
+    {
+      "id": "3e007c9c-297e-4403-8ad0-1888e05211ad",
+      "name": "monitoring",
+      "path": "/monitoring",
+      "attributes": {},
+      "realmRoles": [
+        "monitoring"
+      ],
+      "clientRoles": {},
+      "subGroups": []
+    },
+    {
+      "id": "9ca100d3-c97c-495e-9e28-828bfe7e2405",
+      "name": "promoter",
+      "path": "/promoter",
+      "attributes": {},
+      "realmRoles": [
+        "promoter"
+      ],
+      "clientRoles": {},
+      "subGroups": []
+    }
+  ],
+  "requiredCredentials": [
+    "password"
+  ],
+  "otpPolicyType": "totp",
+  "otpPolicyAlgorithm": "HmacSHA1",
+  "otpPolicyInitialCounter": 0,
+  "otpPolicyDigits": 6,
+  "otpPolicyLookAheadWindow": 1,
+  "otpPolicyPeriod": 30,
+  "otpSupportedApplications": [
+    "FreeOTP",
+    "Google Authenticator"
+  ],
+  "clients": [
+    {
+      "id": "1f279ab9-a6b9-4d19-b9f3-f881ff2257eb",
+      "clientId": "admin-cli",
+      "name": "${client_admin-cli}",
+      "surrogateAuthRequired": false,
+      "enabled": true,
+      "clientAuthenticatorType": "client-secret",
+      "secret": "**********",
+      "redirectUris": [],
+      "webOrigins": [],
+      "notBefore": 0,
+      "bearerOnly": false,
+      "consentRequired": false,
+      "standardFlowEnabled": false,
+      "implicitFlowEnabled": false,
+      "directAccessGrantsEnabled": true,
+      "serviceAccountsEnabled": false,
+      "publicClient": true,
+      "frontchannelLogout": false,
+      "protocol": "openid-connect",
+      "attributes": {},
+      "authenticationFlowBindingOverrides": {},
+      "fullScopeAllowed": false,
+      "nodeReRegistrationTimeout": 0,
+      "defaultClientScopes": [
+        "web-origins",
+        "role_list",
+        "roles",
+        "profile",
+        "email"
+      ],
+      "optionalClientScopes": [
+        "address",
+        "phone",
+        "offline_access"
+      ]
+    },
+    {
+      "id": "f5c24837-4fc7-4994-9c1e-bd70f2cbeed6",
+      "clientId": "account",
+      "name": "${client_account}",
+      "baseUrl": "/auth/realms/demo/account",
+      "surrogateAuthRequired": false,
+      "enabled": true,
+      "clientAuthenticatorType": "client-secret",
+      "secret": "**********",
+      "redirectUris": [
+        "/auth/realms/demo/account/*"
+      ],
+      "webOrigins": [],
+      "notBefore": 0,
+      "bearerOnly": false,
+      "consentRequired": false,
+      "standardFlowEnabled": true,
+      "implicitFlowEnabled": false,
+      "directAccessGrantsEnabled": false,
+      "serviceAccountsEnabled": false,
+      "publicClient": false,
+      "frontchannelLogout": false,
+      "protocol": "openid-connect",
+      "attributes": {},
+      "authenticationFlowBindingOverrides": {},
+      "fullScopeAllowed": false,
+      "nodeReRegistrationTimeout": 0,
+      "defaultClientScopes": [
+        "web-origins",
+        "role_list",
+        "roles",
+        "profile",
+        "email"
+      ],
+      "optionalClientScopes": [
+        "address",
+        "phone",
+        "offline_access"
+      ]
+    },
+    {
+      "id": "0c80e92e-024a-4324-b516-cf814f7e09df",
+      "clientId": "realm-management",
+      "name": "${client_realm-management}",
+      "surrogateAuthRequired": false,
+      "enabled": true,
+      "clientAuthenticatorType": "client-secret",
+      "secret": "**********",
+      "redirectUris": [],
+      "webOrigins": [],
+      "notBefore": 0,
+      "bearerOnly": true,
+      "consentRequired": false,
+      "standardFlowEnabled": true,
+      "implicitFlowEnabled": false,
+      "directAccessGrantsEnabled": false,
+      "serviceAccountsEnabled": false,
+      "publicClient": false,
+      "frontchannelLogout": false,
+      "protocol": "openid-connect",
+      "attributes": {},
+      "authenticationFlowBindingOverrides": {},
+      "fullScopeAllowed": false,
+      "nodeReRegistrationTimeout": 0,
+      "defaultClientScopes": [
+        "web-origins",
+        "role_list",
+        "roles",
+        "profile",
+        "email"
+      ],
+      "optionalClientScopes": [
+        "address",
+        "phone",
+        "offline_access"
+      ]
+    },
+    {
+      "id": "3bcece73-8800-42d4-82fe-7c0012be3780",
+      "clientId": "security-admin-console",
+      "name": "${client_security-admin-console}",
+      "baseUrl": "/auth/admin/demo/console/index.html",
+      "surrogateAuthRequired": false,
+      "enabled": true,
+      "clientAuthenticatorType": "client-secret",
+      "secret": "**********",
+      "redirectUris": [
+        "/auth/admin/demo/console/*"
+      ],
+      "webOrigins": [],
+      "notBefore": 0,
+      "bearerOnly": false,
+      "consentRequired": false,
+      "standardFlowEnabled": true,
+      "implicitFlowEnabled": false,
+      "directAccessGrantsEnabled": false,
+      "serviceAccountsEnabled": false,
+      "publicClient": true,
+      "frontchannelLogout": false,
+      "protocol": "openid-connect",
+      "attributes": {},
+      "authenticationFlowBindingOverrides": {},
+      "fullScopeAllowed": false,
+      "nodeReRegistrationTimeout": 0,
+      "protocolMappers": [
+        {
+          "id": "e70c7d3c-dd18-4084-81c8-c57a31a31cb9",
+          "name": "locale",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "locale",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "locale",
+            "jsonType.label": "String"
+          }
+        }
+      ],
+      "defaultClientScopes": [
+        "web-origins",
+        "role_list",
+        "roles",
+        "profile",
+        "email"
+      ],
+      "optionalClientScopes": [
+        "address",
+        "phone",
+        "offline_access"
+      ]
+    },
+    {
+      "id": "2e0fa909-b029-4d69-ac0e-ff3e593d57e6",
+      "clientId": "demo-frontend",
+      "surrogateAuthRequired": false,
+      "enabled": true,
+      "clientAuthenticatorType": "client-secret",
+      "secret": "**********",
+      "redirectUris": [
+        "http://localhost:9002/*"
+      ],
+      "webOrigins": [],
+      "notBefore": 0,
+      "bearerOnly": false,
+      "consentRequired": false,
+      "standardFlowEnabled": true,
+      "implicitFlowEnabled": false,
+      "directAccessGrantsEnabled": true,
+      "serviceAccountsEnabled": false,
+      "publicClient": true,
+      "frontchannelLogout": false,
+      "protocol": "openid-connect",
+      "attributes": {
+        "saml.assertion.signature": "false",
+        "saml.force.post.binding": "false",
+        "saml.multivalued.roles": "false",
+        "saml.encrypt": "false",
+        "saml.server.signature": "false",
+        "saml.server.signature.keyinfo.ext": "false",
+        "exclude.session.state.from.auth.response": "false",
+        "saml_force_name_id_format": "false",
+        "saml.client.signature": "false",
+        "tls.client.certificate.bound.access.tokens": "false",
+        "saml.authnstatement": "false",
+        "display.on.consent.screen": "false",
+        "saml.onetimeuse.condition": "false"
+      },
+      "authenticationFlowBindingOverrides": {},
+      "fullScopeAllowed": true,
+      "nodeReRegistrationTimeout": -1,
+      "defaultClientScopes": [
+        "web-origins",
+        "role_list",
+        "roles",
+        "profile",
+        "email"
+      ],
+      "optionalClientScopes": [
+        "address",
+        "phone",
+        "offline_access"
+      ]
+    },
+    {
+      "id": "d165281c-4c36-4185-bfbd-589c8a52945f",
+      "clientId": "broker",
+      "name": "${client_broker}",
+      "surrogateAuthRequired": false,
+      "enabled": true,
+      "clientAuthenticatorType": "client-secret",
+      "secret": "**********",
+      "redirectUris": [],
+      "webOrigins": [],
+      "notBefore": 0,
+      "bearerOnly": false,
+      "consentRequired": false,
+      "standardFlowEnabled": true,
+      "implicitFlowEnabled": false,
+      "directAccessGrantsEnabled": false,
+      "serviceAccountsEnabled": false,
+      "publicClient": false,
+      "frontchannelLogout": false,
+      "protocol": "openid-connect",
+      "attributes": {},
+      "authenticationFlowBindingOverrides": {},
+      "fullScopeAllowed": false,
+      "nodeReRegistrationTimeout": 0,
+      "defaultClientScopes": [
+        "web-origins",
+        "role_list",
+        "roles",
+        "profile",
+        "email"
+      ],
+      "optionalClientScopes": [
+        "address",
+        "phone",
+        "offline_access"
+      ]
+    },
+    {
+      "id": "79e1f43b-283a-40d9-a953-c60e9686d329",
+      "clientId": "demo-api",
+      "surrogateAuthRequired": false,
+      "enabled": true,
+      "clientAuthenticatorType": "client-secret",
+      "secret": "**********",
+      "redirectUris": [],
+      "webOrigins": [],
+      "notBefore": 0,
+      "bearerOnly": true,
+      "consentRequired": false,
+      "standardFlowEnabled": true,
+      "implicitFlowEnabled": false,
+      "directAccessGrantsEnabled": true,
+      "serviceAccountsEnabled": false,
+      "publicClient": false,
+      "frontchannelLogout": false,
+      "protocol": "openid-connect",
+      "attributes": {
+        "saml.assertion.signature": "false",
+        "saml.force.post.binding": "false",
+        "saml.multivalued.roles": "false",
+        "saml.encrypt": "false",
+        "saml.server.signature": "false",
+        "saml.server.signature.keyinfo.ext": "false",
+        "exclude.session.state.from.auth.response": "false",
+        "saml_force_name_id_format": "false",
+        "saml.client.signature": "false",
+        "tls.client.certificate.bound.access.tokens": "false",
+        "saml.authnstatement": "false",
+        "display.on.consent.screen": "false",
+        "saml.onetimeuse.condition": "false"
+      },
+      "authenticationFlowBindingOverrides": {},
+      "fullScopeAllowed": true,
+      "nodeReRegistrationTimeout": -1,
+      "defaultClientScopes": [
+        "web-origins",
+        "role_list",
+        "roles",
+        "profile",
+        "email"
+      ],
+      "optionalClientScopes": [
+        "address",
+        "phone",
+        "offline_access"
+      ]
+    }
+  ],
+  "clientScopes": [
+    {
+      "id": "90b602f0-d9a0-4781-9e9b-2dd5f7d28665",
+      "name": "address",
+      "description": "OpenID Connect built-in scope: address",
+      "protocol": "openid-connect",
+      "attributes": {
+        "include.in.token.scope": "true",
+        "display.on.consent.screen": "true",
+        "consent.screen.text": "${addressScopeConsentText}"
+      },
+      "protocolMappers": [
+        {
+          "id": "45f8b45d-72bb-47dc-ab28-2a5c83b81cf8",
+          "name": "address",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-address-mapper",
+          "consentRequired": false,
+          "config": {
+            "user.attribute.formatted": "formatted",
+            "user.attribute.country": "country",
+            "user.attribute.postal_code": "postal_code",
+            "userinfo.token.claim": "true",
+            "user.attribute.street": "street",
+            "id.token.claim": "true",
+            "user.attribute.region": "region",
+            "access.token.claim": "true",
+            "user.attribute.locality": "locality"
+          }
+        }
+      ]
+    },
+    {
+      "id": "10c965ec-c1af-4f24-9945-9d94fb7ee037",
+      "name": "email",
+      "description": "OpenID Connect built-in scope: email",
+      "protocol": "openid-connect",
+      "attributes": {
+        "include.in.token.scope": "true",
+        "display.on.consent.screen": "true",
+        "consent.screen.text": "${emailScopeConsentText}"
+      },
+      "protocolMappers": [
+        {
+          "id": "c31c85b3-9641-4656-b19a-eb399567522e",
+          "name": "email",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-property-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "email",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "email",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "33354d4c-bb3b-4410-9238-f5e8448dc955",
+          "name": "email verified",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-property-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "emailVerified",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "email_verified",
+            "jsonType.label": "boolean"
+          }
+        }
+      ]
+    },
+    {
+      "id": "1338f8cd-1cc1-4446-9f54-4e7db3e319d1",
+      "name": "offline_access",
+      "description": "OpenID Connect built-in scope: offline_access",
+      "protocol": "openid-connect",
+      "attributes": {
+        "consent.screen.text": "${offlineAccessScopeConsentText}",
+        "display.on.consent.screen": "true"
+      }
+    },
+    {
+      "id": "0c36241e-ca1f-415d-b218-4da0ef9fc57b",
+      "name": "phone",
+      "description": "OpenID Connect built-in scope: phone",
+      "protocol": "openid-connect",
+      "attributes": {
+        "include.in.token.scope": "true",
+        "display.on.consent.screen": "true",
+        "consent.screen.text": "${phoneScopeConsentText}"
+      },
+      "protocolMappers": [
+        {
+          "id": "9b8380cc-5aaf-4bf7-89b4-f29c7dba6a0c",
+          "name": "phone number",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "phoneNumber",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "phone_number",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "d2bdd3f1-3293-4c59-92c5-a64967a49ec7",
+          "name": "phone number verified",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "phoneNumberVerified",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "phone_number_verified",
+            "jsonType.label": "boolean"
+          }
+        }
+      ]
+    },
+    {
+      "id": "9ba88b22-dfd9-4544-966e-ab2e07e02283",
+      "name": "profile",
+      "description": "OpenID Connect built-in scope: profile",
+      "protocol": "openid-connect",
+      "attributes": {
+        "include.in.token.scope": "true",
+        "display.on.consent.screen": "true",
+        "consent.screen.text": "${profileScopeConsentText}"
+      },
+      "protocolMappers": [
+        {
+          "id": "abc6a671-5996-48ec-aac3-5d970b85e9a6",
+          "name": "family name",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-property-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "lastName",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "family_name",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "7a6e6867-8767-4782-8400-e8cba29588e2",
+          "name": "updated at",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "updatedAt",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "updated_at",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "a45c0185-4e57-4d73-a55c-de4e30886c84",
+          "name": "picture",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "picture",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "picture",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "8786b25f-17e7-4187-8d7d-d2aa61c6dcd8",
+          "name": "profile",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "profile",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "profile",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "95ece622-b3cc-49f5-b848-a9b8a5407ef4",
+          "name": "birthdate",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "birthdate",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "birthdate",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "59189670-aa93-4933-a796-53dab6a23e45",
+          "name": "locale",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "locale",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "locale",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "addccb2b-468f-404c-9e80-de9d344f6b94",
+          "name": "nickname",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "nickname",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "nickname",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "cd2fe8d9-f253-477f-94fd-29942d56849c",
+          "name": "website",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "website",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "website",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "450e97fc-e780-4262-8f26-0cf8ac903aa3",
+          "name": "username",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-property-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "username",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "preferred_username",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "4b40a0f9-44a1-4a01-b0c5-bfb8be5bccb6",
+          "name": "full name",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-full-name-mapper",
+          "consentRequired": false,
+          "config": {
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "userinfo.token.claim": "true"
+          }
+        },
+        {
+          "id": "07343c50-155a-430c-8ebb-c68a931a0c80",
+          "name": "gender",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "gender",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "gender",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "8076d3ce-1d9f-4779-93c3-9b6a06e8bdac",
+          "name": "middle name",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "middleName",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "middle_name",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "d26abee7-b8a7-4ca5-9755-38e18cc539d6",
+          "name": "given name",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-property-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "firstName",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "given_name",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "83344728-39e4-4600-9c5b-b9d69da3c4f0",
+          "name": "zoneinfo",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "zoneinfo",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "zoneinfo",
+            "jsonType.label": "String"
+          }
+        }
+      ]
+    },
+    {
+      "id": "289f7853-fa6e-4f17-a01d-6c409bddc25a",
+      "name": "role_list",
+      "description": "SAML role list",
+      "protocol": "saml",
+      "attributes": {
+        "consent.screen.text": "${samlRoleListScopeConsentText}",
+        "display.on.consent.screen": "true"
+      },
+      "protocolMappers": [
+        {
+          "id": "c0d2acb4-d20f-48de-94c7-ec6ac1c964e1",
+          "name": "role list",
+          "protocol": "saml",
+          "protocolMapper": "saml-role-list-mapper",
+          "consentRequired": false,
+          "config": {
+            "single": "false",
+            "attribute.nameformat": "Basic",
+            "attribute.name": "Role"
+          }
+        }
+      ]
+    },
+    {
+      "id": "6820c5fc-b7db-4a1d-b71a-8dbd0c7cf98c",
+      "name": "roles",
+      "description": "OpenID Connect scope for add user roles to the access token",
+      "protocol": "openid-connect",
+      "attributes": {
+        "include.in.token.scope": "false",
+        "display.on.consent.screen": "true",
+        "consent.screen.text": "${rolesScopeConsentText}"
+      },
+      "protocolMappers": [
+        {
+          "id": "1cb77c0d-6397-4b3d-acef-8ae1d4089944",
+          "name": "client roles",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-client-role-mapper",
+          "consentRequired": false,
+          "config": {
+            "multivalued": "true",
+            "user.attribute": "foo",
+            "access.token.claim": "true",
+            "claim.name": "resource_access.${client_id}.roles",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "0cf6603d-891d-4f42-8da0-d187f0816a6d",
+          "name": "audience resolve",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-audience-resolve-mapper",
+          "consentRequired": false,
+          "config": {}
+        },
+        {
+          "id": "9e8111be-4598-4145-96da-a9a0fcda8169",
+          "name": "realm roles",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-realm-role-mapper",
+          "consentRequired": false,
+          "config": {
+            "multivalued": "true",
+            "user.attribute": "foo",
+            "access.token.claim": "true",
+            "claim.name": "realm_access.roles",
+            "jsonType.label": "String"
+          }
+        }
+      ]
+    },
+    {
+      "id": "ba84e265-6519-45db-88fc-f86ec057f28c",
+      "name": "web-origins",
+      "description": "OpenID Connect scope for add allowed web origins to the access token",
+      "protocol": "openid-connect",
+      "attributes": {
+        "include.in.token.scope": "false",
+        "display.on.consent.screen": "false",
+        "consent.screen.text": ""
+      },
+      "protocolMappers": [
+        {
+          "id": "3d010883-cafc-4e4a-9246-3669d767f409",
+          "name": "allowed web origins",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-allowed-origins-mapper",
+          "consentRequired": false,
+          "config": {}
+        }
+      ]
+    }
+  ],
+  "defaultDefaultClientScopes": [
+    "role_list",
+    "profile",
+    "email",
+    "roles",
+    "web-origins"
+  ],
+  "defaultOptionalClientScopes": [
+    "offline_access",
+    "address",
+    "phone"
+  ],
+  "browserSecurityHeaders": {
+    "contentSecurityPolicyReportOnly": "",
+    "xContentTypeOptions": "nosniff",
+    "xRobotsTag": "none",
+    "xFrameOptions": "SAMEORIGIN",
+    "xXSSProtection": "1; mode=block",
+    "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';",
+    "strictTransportSecurity": "max-age=31536000; includeSubDomains"
+  },
+  "smtpServer": {},
+  "eventsEnabled": false,
+  "eventsListeners": [
+    "jboss-logging"
+  ],
+  "enabledEventTypes": [],
+  "adminEventsEnabled": false,
+  "adminEventsDetailsEnabled": false,
+  "components": {
+    "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [
+      {
+        "id": "5d3fa6d3-b34b-4996-b19c-4b57c49968c0",
+        "name": "Allowed Client Scopes",
+        "providerId": "allowed-client-templates",
+        "subType": "authenticated",
+        "subComponents": {},
+        "config": {
+          "allow-default-scopes": [
+            "true"
+          ]
+        }
+      },
+      {
+        "id": "eee392b9-35d5-4166-90b3-87b8e7fe7102",
+        "name": "Allowed Client Scopes",
+        "providerId": "allowed-client-templates",
+        "subType": "anonymous",
+        "subComponents": {},
+        "config": {
+          "allow-default-scopes": [
+            "true"
+          ]
+        }
+      },
+      {
+        "id": "5d4439dc-fae2-41ff-b1a9-36f70dc0ca79",
+        "name": "Trusted Hosts",
+        "providerId": "trusted-hosts",
+        "subType": "anonymous",
+        "subComponents": {},
+        "config": {
+          "host-sending-registration-request-must-match": [
+            "true"
+          ],
+          "client-uris-must-match": [
+            "true"
+          ]
+        }
+      },
+      {
+        "id": "c1ba9983-f538-417e-8c7c-a4569946ac98",
+        "name": "Allowed Protocol Mapper Types",
+        "providerId": "allowed-protocol-mappers",
+        "subType": "anonymous",
+        "subComponents": {},
+        "config": {
+          "allowed-protocol-mapper-types": [
+            "oidc-full-name-mapper",
+            "oidc-usermodel-attribute-mapper",
+            "oidc-usermodel-property-mapper",
+            "oidc-address-mapper",
+            "saml-user-attribute-mapper",
+            "saml-user-property-mapper",
+            "oidc-sha256-pairwise-sub-mapper",
+            "saml-role-list-mapper"
+          ]
+        }
+      },
+      {
+        "id": "506d7fe8-a747-4ad2-89be-6c467cd9820d",
+        "name": "Max Clients Limit",
+        "providerId": "max-clients",
+        "subType": "anonymous",
+        "subComponents": {},
+        "config": {
+          "max-clients": [
+            "200"
+          ]
+        }
+      },
+      {
+        "id": "bdb7e39e-b1dd-4d19-826f-dcf0b1e8b505",
+        "name": "Allowed Protocol Mapper Types",
+        "providerId": "allowed-protocol-mappers",
+        "subType": "authenticated",
+        "subComponents": {},
+        "config": {
+          "allowed-protocol-mapper-types": [
+            "saml-user-attribute-mapper",
+            "oidc-full-name-mapper",
+            "saml-role-list-mapper",
+            "saml-user-property-mapper",
+            "oidc-usermodel-attribute-mapper",
+            "oidc-sha256-pairwise-sub-mapper",
+            "oidc-usermodel-property-mapper",
+            "oidc-address-mapper"
+          ]
+        }
+      },
+      {
+        "id": "4d541fe3-f769-4457-817d-5ca7af5705a0",
+        "name": "Full Scope Disabled",
+        "providerId": "scope",
+        "subType": "anonymous",
+        "subComponents": {},
+        "config": {}
+      },
+      {
+        "id": "d1b29f0d-4c6b-4c03-95bf-e27c23ef3a2c",
+        "name": "Consent Required",
+        "providerId": "consent-required",
+        "subType": "anonymous",
+        "subComponents": {},
+        "config": {}
+      }
+    ],
+    "org.keycloak.keys.KeyProvider": [
+      {
+        "id": "5ca0e66f-c953-4088-842a-f65f1cfce006",
+        "name": "rsa-generated",
+        "providerId": "rsa-generated",
+        "subComponents": {},
+        "config": {
+          "priority": [
+            "100"
+          ]
+        }
+      },
+      {
+        "id": "004fea62-f268-4580-aafd-0b59b82740f2",
+        "name": "aes-generated",
+        "providerId": "aes-generated",
+        "subComponents": {},
+        "config": {
+          "priority": [
+            "100"
+          ]
+        }
+      },
+      {
+        "id": "b9531509-0e75-48dd-8860-7dbec1431d09",
+        "name": "hmac-generated",
+        "providerId": "hmac-generated",
+        "subComponents": {},
+        "config": {
+          "priority": [
+            "100"
+          ],
+          "algorithm": [
+            "HS256"
+          ]
+        }
+      }
+    ]
+  },
+  "internationalizationEnabled": false,
+  "supportedLocales": [],
+  "authenticationFlows": [
+    {
+      "id": "e18853d2-04f1-4abd-a397-6d204a640c0d",
+      "alias": "Handle Existing Account",
+      "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider",
+      "providerId": "basic-flow",
+      "topLevel": false,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticator": "idp-confirm-link",
+          "requirement": "REQUIRED",
+          "priority": 10,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "idp-email-verification",
+          "requirement": "ALTERNATIVE",
+          "priority": 20,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "requirement": "ALTERNATIVE",
+          "priority": 30,
+          "flowAlias": "Verify Existing Account by Re-authentication",
+          "userSetupAllowed": false,
+          "autheticatorFlow": true
+        }
+      ]
+    },
+    {
+      "id": "1fea2aeb-619c-41fc-b8cc-d5b4529a500d",
+      "alias": "Verify Existing Account by Re-authentication",
+      "description": "Reauthentication of existing account",
+      "providerId": "basic-flow",
+      "topLevel": false,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticator": "idp-username-password-form",
+          "requirement": "REQUIRED",
+          "priority": 10,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "auth-otp-form",
+          "requirement": "OPTIONAL",
+          "priority": 20,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        }
+      ]
+    },
+    {
+      "id": "382ebfeb-0fd2-4612-890b-6351a44fe1c1",
+      "alias": "browser",
+      "description": "browser based authentication",
+      "providerId": "basic-flow",
+      "topLevel": true,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticator": "auth-cookie",
+          "requirement": "ALTERNATIVE",
+          "priority": 10,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "auth-spnego",
+          "requirement": "DISABLED",
+          "priority": 20,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "identity-provider-redirector",
+          "requirement": "ALTERNATIVE",
+          "priority": 25,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "requirement": "ALTERNATIVE",
+          "priority": 30,
+          "flowAlias": "forms",
+          "userSetupAllowed": false,
+          "autheticatorFlow": true
+        }
+      ]
+    },
+    {
+      "id": "34a5ff88-ffd5-4f60-9d65-99ddc6d9c41a",
+      "alias": "clients",
+      "description": "Base authentication for clients",
+      "providerId": "client-flow",
+      "topLevel": true,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticator": "client-secret",
+          "requirement": "ALTERNATIVE",
+          "priority": 10,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "client-jwt",
+          "requirement": "ALTERNATIVE",
+          "priority": 20,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "client-secret-jwt",
+          "requirement": "ALTERNATIVE",
+          "priority": 30,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "client-x509",
+          "requirement": "ALTERNATIVE",
+          "priority": 40,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        }
+      ]
+    },
+    {
+      "id": "3d8c95b8-49a5-416b-823b-5533e089e1a7",
+      "alias": "direct grant",
+      "description": "OpenID Connect Resource Owner Grant",
+      "providerId": "basic-flow",
+      "topLevel": true,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticator": "direct-grant-validate-username",
+          "requirement": "REQUIRED",
+          "priority": 10,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "direct-grant-validate-password",
+          "requirement": "REQUIRED",
+          "priority": 20,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "direct-grant-validate-otp",
+          "requirement": "OPTIONAL",
+          "priority": 30,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        }
+      ]
+    },
+    {
+      "id": "0ae57c67-dfd8-4264-b6ce-6731f71c3237",
+      "alias": "docker auth",
+      "description": "Used by Docker clients to authenticate against the IDP",
+      "providerId": "basic-flow",
+      "topLevel": true,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticator": "docker-http-basic-authenticator",
+          "requirement": "REQUIRED",
+          "priority": 10,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        }
+      ]
+    },
+    {
+      "id": "272cb097-3372-45c6-b0cc-d920a1cc6bd6",
+      "alias": "first broker login",
+      "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
+      "providerId": "basic-flow",
+      "topLevel": true,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticatorConfig": "review profile config",
+          "authenticator": "idp-review-profile",
+          "requirement": "REQUIRED",
+          "priority": 10,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticatorConfig": "create unique user config",
+          "authenticator": "idp-create-user-if-unique",
+          "requirement": "ALTERNATIVE",
+          "priority": 20,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "requirement": "ALTERNATIVE",
+          "priority": 30,
+          "flowAlias": "Handle Existing Account",
+          "userSetupAllowed": false,
+          "autheticatorFlow": true
+        }
+      ]
+    },
+    {
+      "id": "ab046190-a8d4-4d52-9503-35325498a15f",
+      "alias": "forms",
+      "description": "Username, password, otp and other auth forms.",
+      "providerId": "basic-flow",
+      "topLevel": false,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticator": "auth-username-password-form",
+          "requirement": "REQUIRED",
+          "priority": 10,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "auth-otp-form",
+          "requirement": "OPTIONAL",
+          "priority": 20,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        }
+      ]
+    },
+    {
+      "id": "21f7be44-8819-4ba0-95cb-01a336745cd0",
+      "alias": "http challenge",
+      "description": "An authentication flow based on challenge-response HTTP Authentication Schemes",
+      "providerId": "basic-flow",
+      "topLevel": true,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticator": "no-cookie-redirect",
+          "requirement": "REQUIRED",
+          "priority": 10,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "basic-auth",
+          "requirement": "REQUIRED",
+          "priority": 20,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "basic-auth-otp",
+          "requirement": "DISABLED",
+          "priority": 30,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "auth-spnego",
+          "requirement": "DISABLED",
+          "priority": 40,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        }
+      ]
+    },
+    {
+      "id": "e2f99e58-44fb-4776-aaa4-ffc809294c15",
+      "alias": "registration",
+      "description": "registration flow",
+      "providerId": "basic-flow",
+      "topLevel": true,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticator": "registration-page-form",
+          "requirement": "REQUIRED",
+          "priority": 10,
+          "flowAlias": "registration form",
+          "userSetupAllowed": false,
+          "autheticatorFlow": true
+        }
+      ]
+    },
+    {
+      "id": "6b1f9993-fd6b-4cf5-b218-99914899154b",
+      "alias": "registration form",
+      "description": "registration form",
+      "providerId": "form-flow",
+      "topLevel": false,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticator": "registration-user-creation",
+          "requirement": "REQUIRED",
+          "priority": 20,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "registration-profile-action",
+          "requirement": "REQUIRED",
+          "priority": 40,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "registration-password-action",
+          "requirement": "REQUIRED",
+          "priority": 50,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "registration-recaptcha-action",
+          "requirement": "DISABLED",
+          "priority": 60,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        }
+      ]
+    },
+    {
+      "id": "2ac061c4-101e-453d-9b00-ecced9adf98a",
+      "alias": "reset credentials",
+      "description": "Reset credentials for a user if they forgot their password or something",
+      "providerId": "basic-flow",
+      "topLevel": true,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticator": "reset-credentials-choose-user",
+          "requirement": "REQUIRED",
+          "priority": 10,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "reset-credential-email",
+          "requirement": "REQUIRED",
+          "priority": 20,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "reset-password",
+          "requirement": "REQUIRED",
+          "priority": 30,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "reset-otp",
+          "requirement": "OPTIONAL",
+          "priority": 40,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        }
+      ]
+    },
+    {
+      "id": "d5cb10f4-333b-4b2e-85ef-533e16d25029",
+      "alias": "saml ecp",
+      "description": "SAML ECP Profile Authentication Flow",
+      "providerId": "basic-flow",
+      "topLevel": true,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticator": "http-basic-authenticator",
+          "requirement": "REQUIRED",
+          "priority": 10,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        }
+      ]
+    }
+  ],
+  "authenticatorConfig": [
+    {
+      "id": "9181f4be-676d-4639-8883-e8b3bd99dc9c",
+      "alias": "create unique user config",
+      "config": {
+        "require.password.update.after.registration": "false"
+      }
+    },
+    {
+      "id": "580e9d0d-3439-44cb-8be7-852cdf9de45a",
+      "alias": "review profile config",
+      "config": {
+        "update.profile.on.first.login": "missing"
+      }
+    }
+  ],
+  "requiredActions": [
+    {
+      "alias": "CONFIGURE_TOTP",
+      "name": "Configure OTP",
+      "providerId": "CONFIGURE_TOTP",
+      "enabled": true,
+      "defaultAction": false,
+      "priority": 10,
+      "config": {}
+    },
+    {
+      "alias": "terms_and_conditions",
+      "name": "Terms and Conditions",
+      "providerId": "terms_and_conditions",
+      "enabled": false,
+      "defaultAction": false,
+      "priority": 20,
+      "config": {}
+    },
+    {
+      "alias": "UPDATE_PASSWORD",
+      "name": "Update Password",
+      "providerId": "UPDATE_PASSWORD",
+      "enabled": true,
+      "defaultAction": false,
+      "priority": 30,
+      "config": {}
+    },
+    {
+      "alias": "UPDATE_PROFILE",
+      "name": "Update Profile",
+      "providerId": "UPDATE_PROFILE",
+      "enabled": true,
+      "defaultAction": false,
+      "priority": 40,
+      "config": {}
+    },
+    {
+      "alias": "VERIFY_EMAIL",
+      "name": "Verify Email",
+      "providerId": "VERIFY_EMAIL",
+      "enabled": true,
+      "defaultAction": false,
+      "priority": 50,
+      "config": {}
+    }
+  ],
+  "browserFlow": "browser",
+  "registrationFlow": "registration",
+  "directGrantFlow": "direct grant",
+  "resetCredentialsFlow": "reset credentials",
+  "clientAuthenticationFlow": "clients",
+  "dockerAuthenticationFlow": "docker auth",
+  "attributes": {
+    "_browser_header.xXSSProtection": "1; mode=block",
+    "_browser_header.xFrameOptions": "SAMEORIGIN",
+    "_browser_header.strictTransportSecurity": "max-age=31536000; includeSubDomains",
+    "permanentLockout": "false",
+    "quickLoginCheckMilliSeconds": "1000",
+    "_browser_header.xRobotsTag": "none",
+    "maxFailureWaitSeconds": "900",
+    "minimumQuickLoginWaitSeconds": "60",
+    "failureFactor": "30",
+    "actionTokenGeneratedByUserLifespan": "300",
+    "maxDeltaTimeSeconds": "43200",
+    "_browser_header.xContentTypeOptions": "nosniff",
+    "offlineSessionMaxLifespan": "5184000",
+    "actionTokenGeneratedByAdminLifespan": "43200",
+    "_browser_header.contentSecurityPolicyReportOnly": "",
+    "bruteForceProtected": "false",
+    "_browser_header.contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';",
+    "waitIncrementSeconds": "60",
+    "offlineSessionMaxLifespanEnabled": "false"
+  },
+  "keycloakVersion": "4.6.0.Final",
+  "userManagedAccessAllowed": false
+}
diff --git a/SAS/TMSS/test/oidc/keycloak/tmss_keycloak_Dockerfile b/SAS/TMSS/test/oidc/keycloak/tmss_keycloak_Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..40af052fa688ac67a90b5fff8eb358237900aa19
--- /dev/null
+++ b/SAS/TMSS/test/oidc/keycloak/tmss_keycloak_Dockerfile
@@ -0,0 +1,10 @@
+FROM jboss/keycloak:4.6.0.Final
+ 
+ENV KEYCLOAK_USER=admin
+ENV KEYCLOAK_PASSWORD=admin
+ENV KEYCLOAK_IMPORT=/tmp/realm-export.json
+ 
+ADD realm-export.json /tmp/realm-export.json
+ADD create-keycloak-user.sh /opt/jboss/create-keycloak-user.sh
+
+
diff --git a/SAS/TMSS/test/oidc/pyop_example/README.md b/SAS/TMSS/test/oidc/pyop_example/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..eaff811bdcd0e7e41fdf8d56691fa96a3981133e
--- /dev/null
+++ b/SAS/TMSS/test/oidc/pyop_example/README.md
@@ -0,0 +1,8 @@
+# pyOP example application 
+To run the example application, execute the following commands:
+
+```bash
+cd example/
+pip install -r requirements.txt # install the dependencies
+gunicorn wsgi:app -b :9090 --certfile https.crt --keyfile https.key # run the application
+```
\ No newline at end of file
diff --git a/SAS/TMSS/test/oidc/pyop_example/__init__.py b/SAS/TMSS/test/oidc/pyop_example/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/SAS/TMSS/test/oidc/pyop_example/app.py b/SAS/TMSS/test/oidc/pyop_example/app.py
new file mode 100644
index 0000000000000000000000000000000000000000..806dc4cab84103ad5ab3396b4a59cbc7b7032d4d
--- /dev/null
+++ b/SAS/TMSS/test/oidc/pyop_example/app.py
@@ -0,0 +1,60 @@
+from flask.app import Flask
+from flask.helpers import url_for
+from jwkest.jwk import RSAKey, rsa_load
+
+from pyop.authz_state import AuthorizationState
+from pyop.provider import Provider
+from pyop.subject_identifier import HashBasedSubjectIdentifierFactory
+from pyop.userinfo import Userinfo
+
+
+def init_oidc_provider(app):
+    with app.app_context():
+        issuer = url_for('oidc_provider.index')[:-1]
+        authentication_endpoint = url_for('oidc_provider.authentication_endpoint')
+        jwks_uri = url_for('oidc_provider.jwks_uri')
+        token_endpoint = url_for('oidc_provider.token_endpoint')
+        userinfo_endpoint = url_for('oidc_provider.userinfo_endpoint')
+        registration_endpoint = url_for('oidc_provider.registration_endpoint')
+        end_session_endpoint = url_for('oidc_provider.end_session_endpoint')
+
+    configuration_information = {
+        'issuer': issuer,
+        'authorization_endpoint': authentication_endpoint,
+        'jwks_uri': jwks_uri,
+        'token_endpoint': token_endpoint,
+        'userinfo_endpoint': userinfo_endpoint,
+        'registration_endpoint': registration_endpoint,
+        'end_session_endpoint': end_session_endpoint,
+        'scopes_supported': ['openid', 'profile'],
+        'response_types_supported': ['code', 'code id_token', 'code token', 'code id_token token'],  # code and hybrid
+        'response_modes_supported': ['query', 'fragment'],
+        'grant_types_supported': ['authorization_code', 'implicit'],
+        'subject_types_supported': ['pairwise'],
+        'token_endpoint_auth_methods_supported': ['client_secret_basic'],
+        'claims_parameter_supported': True
+    }
+
+    userinfo_db = Userinfo(app.users)
+    signing_key = RSAKey(key=rsa_load('signing_key.pem'), alg='RS256')
+    provider = Provider(signing_key, configuration_information,
+                        AuthorizationState(HashBasedSubjectIdentifierFactory(app.config['SUBJECT_ID_HASH_SALT'])),
+                        {}, userinfo_db)
+
+    return provider
+
+
+def oidc_provider_init_app(name=None):
+    name = name or __name__
+    app = Flask(name)
+    app.config.from_pyfile('application.cfg')
+
+    app.users = {'test_user': {'name': 'Testing Name'}}
+
+    from .views import oidc_provider_views
+    app.register_blueprint(oidc_provider_views)
+
+    # Initialize the oidc_provider after views to be able to set correct urls
+    app.provider = init_oidc_provider(app)
+
+    return app
diff --git a/SAS/TMSS/test/oidc/pyop_example/application.cfg b/SAS/TMSS/test/oidc/pyop_example/application.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..36a1da6723269c362ec85862a1ef2c25f9aade8d
--- /dev/null
+++ b/SAS/TMSS/test/oidc/pyop_example/application.cfg
@@ -0,0 +1,5 @@
+SERVER_NAME = 'localhost:9090'
+SECRET_KEY = 'secret_key'
+SESSION_COOKIE_NAME='pyop_session'
+SUBJECT_ID_HASH_SALT = 'salt'
+PREFERRED_URL_SCHEME = 'https'
\ No newline at end of file
diff --git a/SAS/TMSS/test/oidc/pyop_example/https.crt b/SAS/TMSS/test/oidc/pyop_example/https.crt
new file mode 100644
index 0000000000000000000000000000000000000000..20a202d8992f026f63a09dea769fe8abadabafb8
--- /dev/null
+++ b/SAS/TMSS/test/oidc/pyop_example/https.crt
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIIEBjCCAu6gAwIBAgIJAIybVu7kfIK0MA0GCSqGSIb3DQEBBQUAMF8xCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQxGDAWBgNVBAMTD2xva2kuaXRzLnVtdS5zZTAeFw0xNTEy
+MTAxNDQyMDFaFw0yNTEyMDcxNDQyMDFaMF8xCzAJBgNVBAYTAkFVMRMwEQYDVQQI
+EwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQx
+GDAWBgNVBAMTD2xva2kuaXRzLnVtdS5zZTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBALiLDBwIteIobC+7JHoNeQRrTIbws9BghN4UUzyLo7+xeP9YwHaS
+tq6HqYK4cVLyx8k06Siw/4PwqMPNj9/B4f/ZXhEkXgbBP5TP36UgKrUIk4zInRFb
+Rjy+DcqjSZdgW1CKBKWJstXjSYen5rPm+voM/0msi164NPcfDMQIZmcQWh0MmEfG
+qlvdwTvjdaAQt8p7CGsxIdu4gPfhubknbTQKu+BVq5/RCVP7VU830PSr1RYhthX8
+Gt8ir32jEdDdjIrfA/zFx6PChyLkQFXtg/9WymnIM1j2ngNreL2nppwnqMYRnI9i
+C/y7MY4al3WeL9IETrtgh1jzXUNpgpJ03B0CAwEAAaOBxDCBwTAdBgNVHQ4EFgQU
+cTNphzIIRpQBQ2VT0Vx9xQYzNN0wgZEGA1UdIwSBiTCBhoAUcTNphzIIRpQBQ2VT
+0Vx9xQYzNN2hY6RhMF8xCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRl
+MSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxGDAWBgNVBAMTD2xv
+a2kuaXRzLnVtdS5zZYIJAIybVu7kfIK0MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcN
+AQEFBQADggEBAJYJfUqOPTyZ+tflKoN4l+scIXpBxqtQbjX+MYli6VHpl+M8y163
+KsCglXPddL7Z58KBrUDx1m6f7dFQ3PMYn/S2dUcRrNOdfaDKZ5QgyYj/iVr8HSOh
+6i1OtMFaBqW5WyqA5YgvUz63hZ2kDOBHZcEfSn2+roylBUiueV9gFNKDWneNMLo2
+PMZxcGWZ3wIQbu9ahakbJUvTigFStKeLoY1A2ZSTH7W4elB5DDxYOKZSzd/KZpfn
+/o/Pc7YbbEUYgIyf3QNusdH+t2pw9ZkrlKMhiv9ZAmjAWMDY7O/i3r7u7AkODu7z
+OHbH3rJqkbaiS8/q0cMZG6AMUzPRglzsTc4=
+-----END CERTIFICATE-----
diff --git a/SAS/TMSS/test/oidc/pyop_example/https.key b/SAS/TMSS/test/oidc/pyop_example/https.key
new file mode 100644
index 0000000000000000000000000000000000000000..2abb725087feda493114a731026b866396c07797
--- /dev/null
+++ b/SAS/TMSS/test/oidc/pyop_example/https.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAuIsMHAi14ihsL7skeg15BGtMhvCz0GCE3hRTPIujv7F4/1jA
+dpK2roepgrhxUvLHyTTpKLD/g/Cow82P38Hh/9leESReBsE/lM/fpSAqtQiTjMid
+EVtGPL4NyqNJl2BbUIoEpYmy1eNJh6fms+b6+gz/SayLXrg09x8MxAhmZxBaHQyY
+R8aqW93BO+N1oBC3ynsIazEh27iA9+G5uSdtNAq74FWrn9EJU/tVTzfQ9KvVFiG2
+Ffwa3yKvfaMR0N2Mit8D/MXHo8KHIuRAVe2D/1bKacgzWPaeA2t4vaemnCeoxhGc
+j2IL/LsxjhqXdZ4v0gROu2CHWPNdQ2mCknTcHQIDAQABAoIBAQCooAWUqDDqUl1o
+z+voyt7FtvXaZ58mzMsb0h6suDwMMTKKwKI8tprOp4+wrrB+RvFfXUWftPwFp6XO
+JMtOfm7vxcM6jqyMJ5DdfYSx8c6UVR3eCoHbFjf70P3xJ3tbIuTNlw/f4w7Sejj6
+B+W6hVjXm4C55TwEdPWQyYJ0rehESxITn7DDjDNXxxwDqwAv8yPTYf8m7mb7qh3V
+EBnvZPIWpgEIVqV2crQSfHJwg29KhS7cnExJBYEPppBQ4aoUGyMqJN0EyBL4DrRo
+Ds6hPttLaXEmB+ACm/OQzhEeFKduob5OIKRSyp9Z8t6/B9uHiIKCENRU4O4zux57
+jZ8MIypRAoGBAO0anHYHlniyP5f/8u4kvTnW3wbDtRJ3L3zVSKN8shQmQnjNWmsI
+LhLWLU2OTRsXlJB7oYqNBojUBdeGimYot9kmjx4XkxELB6XAaYDG6pDvM7axC5qH
+iuC4jVHdEsIfy9dP6wZ/b+A+JOWWpS1vdAizfvgWI32JLGDPkjjUlzNvAoGBAMdA
+F5KZZLZFYsZM470/bb20qFMURDRI+5yz0VUHTNUQEr/xhMcYFske6ox14A7gXzwd
+SHAvTDkV8DsGu/FzSWzZmVhNc1EtdM3Cbe3Y7WJoIQoupuxBDEvrBE9riyOjW61q
+dYIO2ymfJfc2Vx6d7LAK9itXdqa3RIo7ZPK7EbMzAoGAAR1C5vsaJe8QhXJafewG
+R6NO4QVCcJfGzVtjQAFyBM45OcAdUKt1K/l9tQOaMSpnNFagZ7pJ8ZKthFnJhLlk
+Q8z+lzGdK1NV8d15oXVN3OiC4bTrTQqeCHhVkbDsSaVEm/pwLFOk/vTLz5hpplED
+xpaxXhEckZZ3cu0GzuWQ4FkCgYAb34tspqjAFtTKiNcTElx3vV4OwTcJWWxZb45J
+JsxIwgbdcxvv/h6x4/FL1PGTIzAvaKlJiFRRaBBDMZ35GPeckpQxFiSbppBAeIKI
+U2Bh888rbXtMcY0W0bm4ooLEaYXZrJrjptBh8jGNc7ycO9twhRgK2CFxERI1hDmK
++0BuoQKBgHLRFdJYFkNKB+j56vTlB5AFTXcjs6x0dZ4j0SDAqVYgIHErpd2bhgOz
+s698rfdadwbYN4UEqvV/2NhDLx/jag4BvCermK71HaXvYnvqKw3UHGAG8K/dCCFR
+eSuZnxTSoWAPjMLi64hRzbWVT+oeZuy83lG6qDygbI1z1nlYWdo3
+-----END RSA PRIVATE KEY-----
diff --git a/SAS/TMSS/test/oidc/pyop_example/requirements.txt b/SAS/TMSS/test/oidc/pyop_example/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e6b8e9f26cf02724603d4cdd5f621db2588e3b04
--- /dev/null
+++ b/SAS/TMSS/test/oidc/pyop_example/requirements.txt
@@ -0,0 +1,3 @@
+pyop
+Flask
+gunicorn
\ No newline at end of file
diff --git a/SAS/TMSS/test/oidc/pyop_example/signing_key.pem b/SAS/TMSS/test/oidc/pyop_example/signing_key.pem
new file mode 100644
index 0000000000000000000000000000000000000000..74fe00d413e958c55ff2c8894c78ce47fc36ddcd
--- /dev/null
+++ b/SAS/TMSS/test/oidc/pyop_example/signing_key.pem
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQCi7nye2Ye1MrUD/sZAplfkpMkXHYduydvfvv/+Ihx1ClxKS/KG
+/1EhqyBVVvhHLRs9pimKMyLm2pBE51rGOt//XKhsoFAa37VID2iz7DQuV6DGgyBS
+FaKgaYBpinEQy2WcjU4eABnABV2r+K2UmGkqVJheqHqOqHUKasT4gy/6kQIDAQAB
+AoGAf7u+YX6ioNCvDwHHBVojn/H8YK3axmVkhiYkZWTysGM99VVTPridL2sMfzse
+jBZ1u8Av4tOyMg/5eLtz8+KmRjljpeAEFfsA1htWE8vESXnvDFwKldXD9Vi/kppb
+CYqASGCBUX3i1LPYffvjUxIgD+Tjx4k56c5EN5G331flDV0CQQDP8fWraegLJ+K1
+iXGNQzpaqG3EI3vf35Yb2bJpmD39QIXFIcJJ5MZHVW+1TyvgiavM4hS2+LGA8kGh
+OvMWfbYTAkEAyJWGBUmAW9mooo1Vw4tJjEWAHjHvzcQ4dqIju+WN8Xy5JTWkDD6Z
+VgKGtgLt2HfpSsgej14+Rh5mrjo4SbYxSwJAKG0syq9jOk/9xjc7STBJtvhJprkT
+SxnHsBBpnBfJ7WNO3l1KzVzZo2Kbvg7vQ87gBIvrZQsCT0RJuBOi0LuN2wJAAInm
+Qj1gSt7axRT8FfpZyDankW0w56yPOkJVNjv3lZ5wINl0B1RjtQdstTBs0xf/WGQR
+MPFf2XBbdjxRymDi4QJBAM3MUYPOlUk1UVCSQKyCkBwL3zMaPjBjD5LXkhGJzxsb
+T74NznwmCib/r0Rl/KmD7/bAq7R4aheOS/OMaZyhbkk=
+-----END RSA PRIVATE KEY-----
diff --git a/SAS/TMSS/test/oidc/pyop_example/templates/logout.jinja2 b/SAS/TMSS/test/oidc/pyop_example/templates/logout.jinja2
new file mode 100644
index 0000000000000000000000000000000000000000..4322588f2197e9b8579819a9084f7c95c17e1d27
--- /dev/null
+++ b/SAS/TMSS/test/oidc/pyop_example/templates/logout.jinja2
@@ -0,0 +1,8 @@
+<!doctype html>
+<title>Logout</title>
+
+Do you really want to logout?
+<form method="POST">
+    <button type="submit" name="logout" value="logout">Yes, logout</button>
+    <button type="submit" name="no_logout" value="no_logout">No, cancel logout</button>
+</form>
\ No newline at end of file
diff --git a/SAS/TMSS/test/oidc/pyop_example/views.py b/SAS/TMSS/test/oidc/pyop_example/views.py
new file mode 100644
index 0000000000000000000000000000000000000000..7d7335bb224c667973519d56203f73799d0af70c
--- /dev/null
+++ b/SAS/TMSS/test/oidc/pyop_example/views.py
@@ -0,0 +1,124 @@
+from urllib.parse import urlencode, parse_qs
+
+import flask
+from flask import Blueprint, redirect
+from flask import current_app
+from flask import jsonify
+from flask.helpers import make_response
+from flask.templating import render_template
+from oic.oic.message import TokenErrorResponse, UserInfoErrorResponse, EndSessionRequest
+
+from pyop.access_token import AccessToken, BearerTokenError
+from pyop.exceptions import InvalidAuthenticationRequest, InvalidAccessToken, InvalidClientAuthentication, OAuthError, \
+    InvalidSubjectIdentifier, InvalidClientRegistrationRequest
+from pyop.util import should_fragment_encode
+
+oidc_provider_views = Blueprint('oidc_provider', __name__, url_prefix='')
+
+
+@oidc_provider_views.route('/')
+def index():
+    return 'Hello world!'
+
+
+@oidc_provider_views.route('/registration', methods=['POST'])
+def registration_endpoint():
+    try:
+        response = current_app.provider.handle_client_registration_request(flask.request.get_data().decode('utf-8'))
+        return make_response(jsonify(response.to_dict()), 201)
+    except InvalidClientRegistrationRequest as e:
+        return make_response(jsonify(e.to_dict()), status=400)
+
+
+@oidc_provider_views.route('/authentication', methods=['GET'])
+def authentication_endpoint():
+    # parse authentication request
+    try:
+        auth_req = current_app.provider.parse_authentication_request(urlencode(flask.request.args),
+                                                                     flask.request.headers)
+    except InvalidAuthenticationRequest as e:
+        current_app.logger.debug('received invalid authn request', exc_info=True)
+        error_url = e.to_error_url()
+        if error_url:
+            return redirect(error_url, 303)
+        else:
+            # show error to user
+            return make_response('Something went wrong: {}'.format(str(e)), 400)
+
+    # automagic authentication
+    authn_response = current_app.provider.authorize(auth_req, 'test_user')
+    response_url = authn_response.request(auth_req['redirect_uri'], should_fragment_encode(auth_req))
+    return redirect(response_url, 303)
+
+
+@oidc_provider_views.route('/.well-known/openid-configuration')
+def provider_configuration():
+    return jsonify(current_app.provider.provider_configuration.to_dict())
+
+
+@oidc_provider_views.route('/jwks')
+def jwks_uri():
+    return jsonify(current_app.provider.jwks)
+
+
+@oidc_provider_views.route('/token', methods=['POST'])
+def token_endpoint():
+    try:
+        token_response = current_app.provider.handle_token_request(flask.request.get_data().decode('utf-8'),
+                                                                   flask.request.headers)
+        return jsonify(token_response.to_dict())
+    except InvalidClientAuthentication as e:
+        current_app.logger.debug('invalid client authentication at token endpoint', exc_info=True)
+        error_resp = TokenErrorResponse(error='invalid_client', error_description=str(e))
+        response = make_response(error_resp.to_json(), 401)
+        response.headers['Content-Type'] = 'application/json'
+        response.headers['WWW-Authenticate'] = 'Basic'
+        return response
+    except OAuthError as e:
+        current_app.logger.debug('invalid request: %s', str(e), exc_info=True)
+        error_resp = TokenErrorResponse(error=e.oauth_error, error_description=str(e))
+        response = make_response(error_resp.to_json(), 400)
+        response.headers['Content-Type'] = 'application/json'
+        return response
+
+
+@oidc_provider_views.route('/userinfo', methods=['GET', 'POST'])
+def userinfo_endpoint():
+    try:
+        response = current_app.provider.handle_userinfo_request(flask.request.get_data().decode('utf-8'),
+                                                                flask.request.headers)
+        return jsonify(response.to_dict())
+    except (BearerTokenError, InvalidAccessToken) as e:
+        error_resp = UserInfoErrorResponse(error='invalid_token', error_description=str(e))
+        response = make_response(error_resp.to_json(), 401)
+        response.headers['WWW-Authenticate'] = AccessToken.BEARER_TOKEN_TYPE
+        response.headers['Content-Type'] = 'application/json'
+        return response
+
+
+def do_logout(end_session_request):
+    try:
+        current_app.provider.logout_user(end_session_request=end_session_request)
+    except InvalidSubjectIdentifier as e:
+        return make_response('Logout unsuccessful!', 400)
+
+    redirect_url = current_app.provider.do_post_logout_redirect(end_session_request)
+    if redirect_url:
+        return redirect(redirect_url, 303)
+
+    return make_response('Logout successful!')
+
+
+@oidc_provider_views.route('/logout', methods=['GET', 'POST'])
+def end_session_endpoint():
+    if flask.request.method == 'GET':
+        # redirect from RP
+        end_session_request = EndSessionRequest().deserialize(urlencode(flask.request.args))
+        flask.session['end_session_request'] = end_session_request.to_dict()
+        return render_template('logout.jinja2')
+    else:
+        form = parse_qs(flask.request.get_data().decode('utf-8'))
+        if 'logout' in form:
+            return do_logout(EndSessionRequest().from_dict(flask.session['end_session_request']))
+        else:
+            return make_response('You chose not to logout')
diff --git a/SAS/TMSS/test/oidc/pyop_example/wsgi.py b/SAS/TMSS/test/oidc/pyop_example/wsgi.py
new file mode 100644
index 0000000000000000000000000000000000000000..f98a0a821882982289dddc1606622dee3b0084b8
--- /dev/null
+++ b/SAS/TMSS/test/oidc/pyop_example/wsgi.py
@@ -0,0 +1,7 @@
+import logging
+
+from example.app import oidc_provider_init_app
+
+name = 'oidc_provider'
+app = oidc_provider_init_app(name)
+logging.basicConfig(level=logging.DEBUG)
diff --git a/SAS/TMSS/test/oidc/readme.txt b/SAS/TMSS/test/oidc/readme.txt
new file mode 100644
index 0000000000000000000000000000000000000000..435abc6cc6049c56de632a53f556786241eb7eaf
--- /dev/null
+++ b/SAS/TMSS/test/oidc/readme.txt
@@ -0,0 +1,31 @@
+This directory contains a few different OpenID Connect Identity Provider implementations to test against.
+This has not yet been tested/integrated with the web frontend POC behind nginx, but at least the Mozilla test container
+works against the Django dev server.
+
+- Test Mozilla Django OIDC: This is currently the only working solution. It is a docker setup that the developers of
+                            the Django plugin that we use have created for their own testing. The docker compose file
+                            currently pulls things externally, so this is the vanilla setup. The downside of this is
+                            that due to how OpenID works, the identity provider needs to know the callback URIs of the
+                            services that use it and that is hardcoded. So either we set up our test deployment to use
+                            same container/host names, or we have to patch things the way we need it. I chose the latter
+                            and adapted the fixtures to have test client on localhost and also add a test user.
+                            So building and running the oidc_tesprovider should provide an OpenID Identity Provider on
+                            port 8080, which is comfortable to talk to Django on http://localhost:8000.
+                            You should be able to logon on http://localhost:8000/oidc/authenticate as 'paulus'/'pauluspass'
+- PYOP: pyop is a Python-based identity provider that can be simply installed via pip. It seems to be more of a library
+        than a mature and ready-to-deploy product. The project's repository contains an example implementation, which
+        looked good at first, since it's quite a simple flask-based setup without much overhead or tons of dependencies.
+        I almost got it to work, and there is useful logging once you managed to make Django actually talk to it, but
+        I did not continue on getting this to work. See below for some guidance:
+        A caveat of pyop is that there don't seem to be fixtures for preconfiguring clients, so before being able to
+        talk to it, you have to register as a client with the callback URL you want to use or it will complain, e.g.:
+        curl -k -X POST -d '{"redirect_uris": ["http://localhost:8000/oidc/callback/"] }' https://localhost:9090/registration
+        That will return a json with client_id and client_secret that have to be placed in the settings.py of Django,
+        or even better in environment variables before tmss is started (we could/should automize that for testing).
+        Pyop then accepts the request but chokes on it because it does not support the client authentication method
+        that Django uses for its request. (Must be client_secret_basic, but apparently is not.) This could also be an
+        issue of the Mozilla Django Plugin we use or configurable (OIDC_TOKEN_USE_BASIC_AUTH=True?)...?
+- Keycloak: This seems to be a full blown and quite mature identity provider implementation in Java.
+            I found a nice docker solution that supposedly sets up a test provider with fixtures and all,
+            but unfortunately it does not build currently (Docker image seems unmaintained, so maybe not
+            the best choice anyway).