diff --git a/charts/controller/templates/_helpers.tpl b/charts/controller/templates/_helpers.tpl
index 1ee5df8e4..0182de3ce 100644
--- a/charts/controller/templates/_helpers.tpl
+++ b/charts/controller/templates/_helpers.tpl
@@ -82,8 +82,13 @@ env:
     secretKeyRef:
       name: database-creds
       key: password
+- name: DRYCC_DATABASE_NAME
+  valueFrom:
+    secretKeyRef:
+      name: database-creds
+      key: controller-database-name
 - name: DRYCC_DATABASE_URL
-  value: "postgres://$(DRYCC_DATABASE_USER):$(DRYCC_DATABASE_PASSWORD)@$(DRYCC_DATABASE_SERVICE_HOST):$(DRYCC_DATABASE_SERVICE_PORT)/$(DRYCC_DATABASE_USER)"
+  value: "postgres://$(DRYCC_DATABASE_USER):$(DRYCC_DATABASE_PASSWORD)@$(DRYCC_DATABASE_SERVICE_HOST):$(DRYCC_DATABASE_SERVICE_PORT)/$(DRYCC_DATABASE_NAME)"
 {{- end }}
 - name: WORKFLOW_NAMESPACE
   valueFrom:
@@ -149,6 +154,30 @@ env:
 - name: "DRYCC_RABBITMQ_URL"
   value: "amqp://$(DRYCC_RABBITMQ_USERNAME):$(DRYCC_RABBITMQ_PASSWORD)@drycc-rabbitmq-0.drycc-rabbitmq.{{$.Release.Namespace}}.svc.{{$.Values.global.cluster_domain}}:5672/drycc"
 {{- end }}
+{{- if eq .Values.global.passport_location "on-cluster"}}
+- name: "DRYCC_PASSPORT_DOMAIN"
+  value: drycc-passport.{{ .Values.global.platform_domain }}
+- name: "SOCIAL_AUTH_DRYCC_AUTHORIZATION_URL"
+  value: "$(DRYCC_PASSPORT_DOMAIN)/oauth/token/"
+- name: "SOCIAL_AUTH_DRYCC_ACCESS_API_URL"
+  value: "$(DRYCC_PASSPORT_DOMAIN)/users/"
+- name: "SOCIAL_AUTH_DRYCC_USERINFO_URL"
+  value: "$(DRYCC_PASSPORT_DOMAIN)/oauth/userinfo/"
+- name: "SOCIAL_AUTH_DRYCC_JWKS_URI"
+  value: "$(DRYCC_PASSPORT_DOMAIN)/oauth/.well-known/jwks.json"
+- name: "SOCIAL_AUTH_DRYCC_OIDC_ENDPOINT"
+  value: "$(DRYCC_PASSPORT_DOMAIN)/oauth"
+- name: SOCIAL_AUTH_DRYCC_CONTROLLER_KEY
+  valueFrom:
+    secretKeyRef:
+      name: passport-creds
+      key: social-auth-drycc-controller-key
+- name: SOCIAL_AUTH_DRYCC_CONTROLLER_SECRET
+  valueFrom:
+    secretKeyRef:
+      name: passport-creds
+      key: social-auth-drycc-controller-secret
+{{- end }}
 {{- range $key, $value := .Values.environment }}
 - name: {{ $key }}
   value: {{ $value | quote }}
@@ -168,4 +197,4 @@ resources:
     memory: {{.Values.limits_memory}}
 {{- end }}
 {{- end }}
-{{- end }}
\ No newline at end of file
+{{- end }}
diff --git a/charts/controller/templates/controller-cronjob-daily.yaml b/charts/controller/templates/controller-cronjob-daily.yaml
index 9bf1bbafa..e88cbcfc6 100644
--- a/charts/controller/templates/controller-cronjob-daily.yaml
+++ b/charts/controller/templates/controller-cronjob-daily.yaml
@@ -7,9 +7,10 @@ metadata:
   annotations:
     component.drycc.cc/version: {{ .Values.image_tag }}
 spec:
-  failedJobsHistoryLimit: 1
   schedule: "0 0 * * *"
-  successfulJobsHistoryLimit: 3
+  concurrencyPolicy: {{ .Values.concurrency_policy }}
+  successfulJobsHistoryLimit: 1
+  failedJobsHistoryLimit: 1
   jobTemplate:
     spec:
       template:
diff --git a/charts/controller/templates/controller-cronjob-hourly.yaml b/charts/controller/templates/controller-cronjob-hourly.yaml
new file mode 100644
index 000000000..dd0de7fb5
--- /dev/null
+++ b/charts/controller/templates/controller-cronjob-hourly.yaml
@@ -0,0 +1,31 @@
+apiVersion: batch/v1beta1
+kind: CronJob
+metadata:
+  name: drycc-controller-cronjob-daily
+  labels:
+    heritage: drycc
+  annotations:
+    component.drycc.cc/version: {{ .Values.image_tag }}
+spec:
+  schedule: "0 */1 * * *"
+  concurrencyPolicy: {{ .Values.concurrency_policy }}
+  successfulJobsHistoryLimit: 1
+  failedJobsHistoryLimit: 1
+  jobTemplate:
+    spec:
+      template:
+        spec:
+          restartPolicy: OnFailure
+          serviceAccount: drycc-controller
+          containers:
+          - image: {{.Values.image_registry}}/{{.Values.org}}/controller:{{.Values.image_tag}}
+            imagePullPolicy: {{.Values.pull_policy}}
+            name: drycc-controller-measure-app
+            command:
+              - /bin/bash
+              - -c
+            args:
+              - python -u /app/manage.py measure_app
+            {{- include "controller.envs" . | indent 12 }}
+            {{- include "controller.volumeMounts" . | indent 12 }}
+          {{- include "controller.volumes" . | indent 10 }}
diff --git a/charts/controller/values.yaml b/charts/controller/values.yaml
index c2c2c49b1..be0473725 100644
--- a/charts/controller/values.yaml
+++ b/charts/controller/values.yaml
@@ -22,6 +22,11 @@ app_storage_class: ""
 replicas: 1
 # Set celery replicas
 celery_replicas: 1
+# Set cronjob concurrencyPolicy
+# Allow (default): The cron job allows concurrently running jobs
+# Forbid: The cron job does not allow concurrent runs; if it is time for a new job run and the previous job run hasn't finished yet, the cron job skips the new job run
+# Replace: If it is time for a new job run and the previous job run hasn't finished yet, the cron job replaces the currently running job run with a new job run
+concurrency_policy: "Replace"
 # Configuring this will no longer use the built-in database component
 database_url: ""
 
@@ -30,6 +35,16 @@ database_url: ""
 # this is usually a non required setting.
 environment:
   RESERVED_NAMES: "drycc, drycc-builder, drycc-monitor-grafana"
+##　Oauth settings. If passport_location is off-cluster, set the following environment variables
+#  LOGIN_REDIRECT_URL: ""
+#  SOCIAL_AUTH_DRYCC_AUTHORIZATION_URL: ""
+#  SOCIAL_AUTH_DRYCC_ACCESS_TOKEN_URL: ""
+#  SOCIAL_AUTH_DRYCC_ACCESS_API_URL: ""
+#  SOCIAL_AUTH_DRYCC_USERINFO_URL: ""
+#  SOCIAL_AUTH_DRYCC_JWKS_URI: ""
+#  SOCIAL_AUTH_DRYCC_OIDC_ENDPOINT: ""
+#  SOCIAL_AUTH_DRYCC_KEY: ""
+#  SOCIAL_AUTH_DRYCC_SECRET: ""
 
 redis:
   replicas: 1
@@ -79,3 +94,4 @@ global:
   platform_domain: ""
   # Whether cert_manager is enabled to automatically generate controller certificates
   cert_manager_enabled: "true"
+  passport_location: "on-cluster"
diff --git a/rootfs/Dockerfile b/rootfs/Dockerfile
index 2a6a894ed..87eee433d 100644
--- a/rootfs/Dockerfile
+++ b/rootfs/Dockerfile
@@ -9,7 +9,7 @@ RUN apk add --update --virtual .build-deps \
     cargo \
     libffi-dev \
     musl-dev \
-    openldap-dev \
+    openssl-dev \
   && pip3 install --disable-pip-version-check --no-cache-dir -r /app/requirements.txt \
   && runDeps="$( \
     scanelf --needed --nobanner --format '%n#p' --recursive /usr/local \
diff --git a/rootfs/api/apps_extra/__init__.py b/rootfs/api/apps_extra/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/rootfs/api/apps_extra/social_core/actions.py b/rootfs/api/apps_extra/social_core/actions.py
new file mode 100644
index 000000000..f96a73f7e
--- /dev/null
+++ b/rootfs/api/apps_extra/social_core/actions.py
@@ -0,0 +1,124 @@
+from urllib.parse import quote
+
+from social_core.utils import sanitize_redirect, user_is_authenticated, \
+                   user_is_active, partial_pipeline_data, setting_url
+
+
+def do_auth(backend, redirect_name='next'):
+    # Save any defined next value into session
+    data = backend.strategy.request_data(merge=False)
+
+    # Save extra data into session.
+    for field_name in backend.setting('FIELDS_STORED_IN_SESSION', []):
+        if field_name in data:
+            backend.strategy.session_set(field_name, data[field_name])
+        else:
+            backend.strategy.session_set(field_name, None)
+    # uri = None
+    if redirect_name in data:
+        # Check and sanitize a user-defined GET/POST next field value
+        redirect_uri = data[redirect_name]
+        if backend.setting('SANITIZE_REDIRECTS', True):
+            allowed_hosts = backend.setting('ALLOWED_REDIRECT_HOSTS', []) + \
+                            [backend.strategy.request_host()]
+            redirect_uri = sanitize_redirect(allowed_hosts, redirect_uri)
+        backend.strategy.session_set(
+            redirect_name,
+            redirect_uri or backend.setting('LOGIN_REDIRECT_URL')
+        )
+    response = backend.start()
+    url = response.url.split('?')[1]
+
+    def form2json(form_data):
+        from urllib.parse import parse_qs, urlparse
+        query = urlparse('?' + form_data).query
+        params = parse_qs(query)
+        return {key: params[key][0] for key in params}
+    from django.core.cache import cache
+    cache.set("oidc_key_" + data.get('key', ''), form2json(url).get('state'), 60 * 10)
+    return response
+
+
+def do_complete(backend, login, user=None, redirect_name='next',
+                *args, **kwargs):
+    data = backend.strategy.request_data()
+
+    is_authenticated = user_is_authenticated(user)
+    user = user if is_authenticated else None
+
+    partial = partial_pipeline_data(backend, user, *args, **kwargs)
+    if partial:
+        user = backend.continue_pipeline(partial)
+        # clean partial data after usage
+        backend.strategy.clean_partial_pipeline(partial.token)
+    else:
+        user = backend.complete(user=user, *args, **kwargs)
+
+    # pop redirect value before the session is trashed on login(), but after
+    # the pipeline so that the pipeline can change the redirect if needed
+    redirect_value = backend.strategy.session_get(redirect_name, '') or \
+        data.get(redirect_name, '')
+
+    # check if the output value is something else than a user and just
+    # return it to the client
+    user_model = backend.strategy.storage.user.user_model()
+    if user and not isinstance(user, user_model):
+        return user
+
+    if is_authenticated:
+        if not user:
+            url = setting_url(backend, redirect_value, 'LOGIN_REDIRECT_URL')
+        else:
+            url = setting_url(backend, redirect_value,
+                              'NEW_ASSOCIATION_REDIRECT_URL',
+                              'LOGIN_REDIRECT_URL')
+    elif user:
+        if user_is_active(user):
+            # catch is_new/social_user in case login() resets the instance
+            is_new = getattr(user, 'is_new', False)
+            social_user = user.social_user
+            login(backend, user, social_user)
+            # store last login backend name in session
+            backend.strategy.session_set('social_auth_last_login_backend',
+                                         social_user.provider)
+
+            if is_new:
+                url = setting_url(backend,
+                                  'NEW_USER_REDIRECT_URL',
+                                  redirect_value,
+                                  'LOGIN_REDIRECT_URL')
+            else:
+                url = setting_url(backend, redirect_value,
+                                  'LOGIN_REDIRECT_URL')
+        else:
+            if backend.setting('INACTIVE_USER_LOGIN', False):
+                social_user = user.social_user
+                login(backend, user, social_user)
+            url = setting_url(backend, 'INACTIVE_USER_URL', 'LOGIN_ERROR_URL',
+                              'LOGIN_URL')
+    else:
+        url = setting_url(backend, 'LOGIN_ERROR_URL', 'LOGIN_URL')
+
+    if redirect_value and redirect_value != url:
+        redirect_value = quote(redirect_value)
+        url += ('&' if '?' in url else '?') + \
+            '{0}={1}'.format(redirect_name, redirect_value)
+
+    if backend.setting('SANITIZE_REDIRECTS', True):
+        allowed_hosts = backend.setting('ALLOWED_REDIRECT_HOSTS', []) + \
+                        [backend.strategy.request_host()]
+        url = sanitize_redirect(allowed_hosts, url) or \
+            backend.setting('LOGIN_REDIRECT_URL')
+    response = backend.strategy.redirect(url)
+    social_auth = user.social_auth.filter(provider='drycc').\
+        order_by('-modified').last()
+    response.set_cookie("name", user.username,
+                        max_age=social_auth.extra_data.get('expires_in'))
+    response.set_cookie("id_token", social_auth.extra_data.get('id_token'),
+                        max_age=social_auth.extra_data.get('expires_in'))
+    from django.core.cache import cache
+    cache.set("oidc_state_" + data.get('state'),
+              {'token': social_auth.extra_data.get('id_token', 'fail'),
+               'username': user.username},
+              60 * 10)
+    return response
diff --git a/rootfs/api/authentication.py b/rootfs/api/authentication.py
index 56c107bd2..7d55ad9b8 100644
--- a/rootfs/api/authentication.py
+++ b/rootfs/api/authentication.py
@@ -35,9 +35,9 @@ def authenticate(self, request):
             return AnonymousUser(), None
 
 
-class DryccTokenAuthentication(TokenAuthentication):
+class DryccOIDCAuthentication(TokenAuthentication):
     def authenticate(self, request):
-        if 'manager' in request.META.get('HTTP_USER_AGENT', ''):
+        if 'Drycc' in request.META.get('HTTP_USER_AGENT', ''):
             auth = get_authorization_header(request).split()
 
             if not auth or auth[0].lower() != self.keyword.lower().encode():
@@ -57,7 +57,7 @@ def authenticate(self, request):
                 raise exceptions.AuthenticationFailed(msg)
             return cache.get_or_set(
                 token, lambda: self._get_user(token), settings.OAUTH_CACHE_USER_TIME), None  # noqa
-        return super(DryccTokenAuthentication, self).authenticate(request)  # noqa
+        return super(DryccOIDCAuthentication, self).authenticate(request)  # noqa
 
     @staticmethod
     def _get_user(key):
diff --git a/rootfs/api/backend.py b/rootfs/api/backend.py
index 9490c5e97..f4aea288e 100644
--- a/rootfs/api/backend.py
+++ b/rootfs/api/backend.py
@@ -1,6 +1,10 @@
+from django.conf import settings
 from django.contrib.auth import get_user_model
 from django.core.exceptions import ObjectDoesNotExist
 
+from social_core.backends.oauth import BaseOAuth2
+from social_core.backends.open_id_connect import OpenIdConnectAuth
+
 from api import serializers
 from api.oauth import OAuthManager
 
@@ -29,3 +33,71 @@ def get_user(self, user_id):
         except ObjectDoesNotExist:
             pass
         return user
+
+
+class DryccOAuth(BaseOAuth2):
+    """Drycc OAuth authentication backend"""
+    name = 'drycc'
+    AUTHORIZATION_URL = settings.SOCIAL_AUTH_DRYCC_AUTHORIZATION_URL
+    ACCESS_TOKEN_URL = settings.SOCIAL_AUTH_DRYCC_ACCESS_TOKEN_URL
+    ACCESS_TOKEN_METHOD = 'POST'
+    SCOPE_SEPARATOR = ','
+    EXTRA_DATA = [
+        ('id', 'id'),
+        ('access_token', 'access_token'),
+        ('refresh_token', 'refresh_token'),
+        ('expires_in', 'expires_in'),
+        ('token_type', 'token_type'),
+        ('id_token', 'id_token'),
+        ('scope', 'scope'),
+    ]
+
+    def get_user_details(self, response):
+        """Return user details from GitHub account"""
+        print(response)
+        return {
+            'username': response.get('username'),
+            'email': response.get('email') or '',
+            'first_name': response.get('first_name'),
+            'last_name': response.get('last_name'),
+            'is_superuser': response.get('is_superuser'),
+            'is_staff': response.get('is_staff'),
+            'is_active': response.get('is_active'),
+        }
+
+    def user_data(self, access_token, *args, **kwargs):
+        """Loads user data from service"""
+        url = settings.SOCIAL_AUTH_DRYCC_ACCESS_API_URL
+        return self.get_json(url, headers={
+            'authorization': 'Bearer ' + access_token})
+
+    def get_user_id(self, details, response):
+        """Use user account id as unique id"""
+        return response.get('id')
+
+
+class DryccOIDC(OpenIdConnectAuth):
+    """Drycc Openid Connect authentication backend"""
+    name = 'drycc'
+    AUTHORIZATION_URL = settings.SOCIAL_AUTH_DRYCC_AUTHORIZATION_URL
+    ACCESS_TOKEN_URL = settings.SOCIAL_AUTH_DRYCC_ACCESS_TOKEN_URL
+    USERINFO_URL = settings.SOCIAL_AUTH_DRYCC_USERINFO_URL
+    JWKS_URI = settings.SOCIAL_AUTH_DRYCC_JWKS_URI
+    OIDC_ENDPOINT = settings.SOCIAL_AUTH_DRYCC_OIDC_ENDPOINT
+    DEFAULT_SCOPE = ['openid']
+    EXTRA_DATA = [
+        ('id', 'id'),
+        ('access_token', 'access_token'),
+        ('refresh_token', 'refresh_token'),
+        ('expires_in', 'expires_in'),
+        ('token_type', 'token_type'),
+        ('id_token', 'id_token'),
+        ('scope', 'scope'),
+    ]
+
+    from social_core.utils import cache
+
+    @cache(ttl=86400)
+    def oidc_config(self):
+        return self.get_json(self.OIDC_ENDPOINT +
+                             '/.well-known/openid-configuration/')
diff --git a/rootfs/api/influxdb.py b/rootfs/api/influxdb.py
index 38aa295a1..0cfdc9b40 100644
--- a/rootfs/api/influxdb.py
+++ b/rootfs/api/influxdb.py
@@ -29,6 +29,9 @@ def _query_stream(flux_script: str) -> Iterator[FluxRecord]:
         except ApiException as e:
             logger.exception(e)
             yield from []
+        except Exception as e:
+            logger.exception(e)
+            yield from []
         else:
             yield from records
 
diff --git a/rootfs/api/oauth.py b/rootfs/api/oauth.py
index 34c2061ff..2eefd78c5 100644
--- a/rootfs/api/oauth.py
+++ b/rootfs/api/oauth.py
@@ -6,32 +6,14 @@
 
 
 class OAuthManager(object):
-    def __enter__(self):
-        return self
 
     def __init__(self):
-        self.client_id = settings.OAUTH_CLIENT_ID
-        self.client_secret = settings.OAUTH_CLIENT_SECRET
-        self.token_url = settings.OAUTH_ACCESS_TOKEN_URL
-        self.api_url = settings.OAUTH_ACCESS_API_URL
+        self.client_id = settings.SOCIAL_AUTH_DRYCC_KEY
+        self.client_secret = settings.SOCIAL_AUTH_DRYCC_SECRET
+        self.token_url = settings.SOCIAL_AUTH_DRYCC_ACCESS_TOKEN_URL
+        self.api_url = settings.SOCIAL_AUTH_DRYCC_ACCESS_API_URL
         self.client = OAuth2Session(self.client_id, self.client_secret)
 
-    def fetch_token(self, username: str, password: str) -> Dict:
-        response = self.client.fetch_token(self.token_url,
-                                           username=username,
-                                           password=password)
-        return response
-
-    def get_user(self) -> Dict:
-        response = self.client.get(f'{self.api_url}/users')
-        result = response.json()
-        return result
-
-    def get_email(self) -> str:
-        response = self.client.get(f'{self.api_url}/users/emails')
-        result = response.json()
-        return result['email']
-
     def get_user_by_token(self, token: str) -> Dict:
         response = requests.get(f'{self.api_url}/users', headers={
             'Authorization': f'Bearer {token}'
@@ -45,6 +27,3 @@ def get_email_by_token(self, token: str) -> Dict:
         })
         result = response.json()
         return result
-
-    def __exit__(self, exc_type, exc_val, exc_tb):
-        self.client.close()
diff --git a/rootfs/api/permissions.py b/rootfs/api/permissions.py
index 99d3ec42c..e70054a35 100644
--- a/rootfs/api/permissions.py
+++ b/rootfs/api/permissions.py
@@ -1,8 +1,6 @@
-
-from rest_framework import exceptions
 from rest_framework import permissions
 from django.conf import settings
-from django.contrib.auth.models import AnonymousUser, User
+from django.contrib.auth.models import AnonymousUser
 
 from api import models
 
@@ -94,31 +92,6 @@ def has_permission(self, request, view):
         return request.method in permissions.SAFE_METHODS or request.user.is_superuser
 
 
-class HasRegistrationAuth(permissions.BasePermission):
-    """
-    Checks to see if registration is enabled
-    """
-    def has_permission(self, request, view):
-        """
-        If settings.REGISTRATION_MODE does not exist, such as during a test, return True
-        Return `True` if permission is granted, `False` otherwise.
-        """
-        try:
-            if settings.REGISTRATION_MODE == 'disabled':
-                raise exceptions.PermissionDenied('Registration is disabled')
-            if settings.REGISTRATION_MODE == 'enabled':
-                return True
-            elif settings.REGISTRATION_MODE == 'admin_only':
-                if not User.objects.filter(is_superuser=True).exists():
-                    return True
-                return request.user.is_superuser
-            else:
-                raise Exception("{} is not a valid registation mode"
-                                .format(settings.REGISTRATION_MODE))
-        except AttributeError:
-            return True
-
-
 class HasBuilderAuth(permissions.BasePermission):
     """
     View permission to allow builder to perform actions
@@ -133,18 +106,3 @@ def has_permission(self, request, view):
         if not auth_header:
             return False
         return auth_header == settings.BUILDER_KEY
-
-
-class CanRegenerateToken(permissions.BasePermission):
-    """
-    Checks if a user can regenerate a token
-    """
-
-    def has_permission(self, request, view):
-        """
-        Return `True` if permission is granted, `False` otherwise.
-        """
-        if 'username' in request.data or 'all' in request.data:
-            return request.user.is_superuser
-        else:
-            return True
diff --git a/rootfs/api/pipeline.py b/rootfs/api/pipeline.py
new file mode 100644
index 000000000..e55ae490e
--- /dev/null
+++ b/rootfs/api/pipeline.py
@@ -0,0 +1,18 @@
+def update_user(backend, user, response, *args, **kwargs):
+    user.username = response.get('username')
+    user.email = response.get('email')
+    user.first_name = response.get('first_name')
+    user.last_name = response.get('last_name')
+    user.is_superuser = response.get('is_superuser')
+    user.is_staff = response.get('is_staff')
+    user.is_active = response.get('is_active')
+    user.save()
+
+
+def load_extra_data(backend, details, response, uid, user, *args, **kwargs):
+    social = kwargs.get('social') or \
+             backend.strategy.storage.user.get_social_auth(backend.name, uid)
+    if social:
+        extra_data = backend.extra_data(user, uid, response, details,
+                                        *args, **kwargs)
+        social.set_extra_data(extra_data)
diff --git a/rootfs/api/settings/production.py b/rootfs/api/settings/production.py
index f8d030759..3c373ac7a 100644
--- a/rootfs/api/settings/production.py
+++ b/rootfs/api/settings/production.py
@@ -2,14 +2,15 @@
 Django settings for the Drycc project.
 """
 from distutils.util import strtobool
+import sys
 import os.path
 import tempfile
-import ldap
 import json
 import dj_database_url
 
-from django_auth_ldap.config import LDAPSearch, GroupOfNamesType
 
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+sys.path.insert(0, os.path.join(BASE_DIR, 'apps_extra'))
 # A boolean that turns on/off debug mode.
 # https://docs.djangoproject.com/en/1.11/ref/settings/#debug
 DEBUG = bool(os.environ.get('DRYCC_DEBUG', False))
@@ -106,6 +107,7 @@
     'jsonfield',
     'rest_framework',
     'rest_framework.authtoken',
+    'social_django',
     # Drycc apps
     'api'
 )
@@ -135,9 +137,9 @@
 )
 
 X_FRAME_OPTIONS = 'DENY'
-CSRF_COOKIE_SECURE = True
+# CSRF_COOKIE_SECURE = True
 CSRF_COOKIE_HTTPONLY = True
-SESSION_COOKIE_SECURE = True
+# SESSION_COOKIE_SECURE = True
 SECURE_CONTENT_TYPE_NOSNIFF = True
 SECURE_BROWSER_XSS_FILTER = True
 
@@ -155,7 +157,7 @@
         'rest_framework.permissions.IsAuthenticated',
     ),
     'DEFAULT_AUTHENTICATION_CLASSES': (
-        'api.authentication.DryccTokenAuthentication',
+        'api.authentication.DryccOIDCAuthentication',
     ),
     'DEFAULT_RENDERER_CLASSES': (
         'rest_framework.renderers.JSONRenderer',
@@ -416,9 +418,6 @@
 MINIO_PORT = os.environ.get('DRYCC_MINIO_SERVICE_PORT', 80)
 APP_STORAGE = os.environ.get('APP_STORAGE')
 
-# check if we can register users with `drycc register`
-REGISTRATION_MODE = os.environ.get('REGISTRATION_MODE', 'enabled')
-
 DRYCC_DATABASE_URL = os.environ.get('DRYCC_DATABASE_URL', 'postgres://:@:5432/drycc')
 DATABASES = {
     'default': dj_database_url.config(default=DRYCC_DATABASE_URL, conn_max_age=600)
@@ -427,68 +426,31 @@
 APP_URL_REGEX = '[a-z0-9-]+'
 
 # Oauth settings
-OAUTH_ACCESS_TOKEN_URL = os.environ.get('OAUTH2_ACCESS_TOKEN_URL', '')
-OAUTH_ACCESS_API_URL = os.environ.get('OAUTH2_ACCESS_API_URL', '')
-OAUTH_CLIENT_ID = os.environ.get('OAUTH2_CLIENT_ID', '')
-OAUTH_CLIENT_SECRET = os.environ.get('OAUTH2_CLIENT_SECRET', '')
+LOGIN_REDIRECT_URL = os.environ.get('LOGIN_REDIRECT_URL', '/admin/')
+SOCIAL_AUTH_DRYCC_AUTHORIZATION_URL = os.environ.get('SOCIAL_AUTH_DRYCC_AUTHORIZATION_URL')
+SOCIAL_AUTH_DRYCC_ACCESS_TOKEN_URL = os.environ.get('SOCIAL_AUTH_DRYCC_ACCESS_TOKEN_URL')
+SOCIAL_AUTH_DRYCC_ACCESS_API_URL = os.environ.get('SOCIAL_AUTH_DRYCC_ACCESS_API_URL')
+SOCIAL_AUTH_DRYCC_USERINFO_URL = os.environ.get('SOCIAL_AUTH_DRYCC_ACCESS_TOKEN_URL')
+SOCIAL_AUTH_DRYCC_JWKS_URI = os.environ.get('SOCIAL_AUTH_DRYCC_ACCESS_API_URL')
+SOCIAL_AUTH_DRYCC_OIDC_ENDPOINT = os.environ.get('SOCIAL_AUTH_DRYCC_OIDC_ENDPOINT')
+SOCIAL_AUTH_DRYCC_KEY = os.environ.get('SOCIAL_AUTH_DRYCC_KEY')
+SOCIAL_AUTH_DRYCC_SECRET = os.environ.get('SOCIAL_AUTH_DRYCC_SECRET')
+SOCIAL_AUTH_POSTGRES_JSONFIELD = True
+SOCIAL_AUTH_PIPELINE = (
+    'social_core.pipeline.social_auth.social_details',
+    'social_core.pipeline.social_auth.social_uid',
+    'social_core.pipeline.social_auth.social_user',
+    'social_core.pipeline.user.get_username',
+    'social_core.pipeline.social_auth.associate_by_email',
+    'social_core.pipeline.user.create_user',
+    'api.pipeline.update_user',
+    'social_core.pipeline.social_auth.associate_user',
+    'social_core.pipeline.social_auth.load_extra_data',
+    'social_core.pipeline.user.user_details',
+)
+AUTHENTICATION_BACKENDS = ("api.backend.DryccOIDC",) + \
+    AUTHENTICATION_BACKENDS
 OAUTH_CACHE_USER_TIME = int(os.environ.get('OAUTH_CACHE_USER_TIME', 30 * 60))
-if OAUTH_ACCESS_TOKEN_URL:
-    AUTHENTICATION_BACKENDS = ("api.backend.DryccOauthBackend",) + AUTHENTICATION_BACKENDS
-
-# LDAP settings taken from environment variables.
-LDAP_ENDPOINT = os.environ.get('LDAP_ENDPOINT', '')
-LDAP_BIND_DN = os.environ.get('LDAP_BIND_DN', '')
-LDAP_BIND_PASSWORD = os.environ.get('LDAP_BIND_PASSWORD', '')
-LDAP_USER_BASEDN = os.environ.get('LDAP_USER_BASEDN', '')
-LDAP_USER_FILTER = os.environ.get('LDAP_USER_FILTER', 'username')
-LDAP_GROUP_BASEDN = os.environ.get('LDAP_GROUP_BASEDN', '')
-LDAP_GROUP_FILTER = os.environ.get('LDAP_GROUP_FILTER', '')
-LDAP_ACTIVE_GROUP = os.environ.get('LDAP_ACTIVE_GROUP', '')
-LDAP_STAFF_GROUP = os.environ.get('LDAP_STAFF_GROUP', '')
-LDAP_SUPERUSER_GROUP = os.environ.get('LDAP_SUPERUSER_GROUP', '')
-
-# Django LDAP backend configuration.
-# See https://pythonhosted.org/django-auth-ldap/reference.html
-# for variables' details.
-# In order to debug LDAP configuration it is possible to enable
-# verbose logging from auth-ldap plugin:
-# https://pythonhosted.org/django-auth-ldap/logging.html
-
-if LDAP_ENDPOINT:
-    AUTHENTICATION_BACKENDS = ("django_auth_ldap.backend.LDAPBackend",) + AUTHENTICATION_BACKENDS
-    AUTH_LDAP_SERVER_URI = LDAP_ENDPOINT
-    AUTH_LDAP_BIND_DN = LDAP_BIND_DN
-    AUTH_LDAP_BIND_PASSWORD = LDAP_BIND_PASSWORD
-    AUTH_LDAP_USER_SEARCH = LDAPSearch(
-        base_dn=LDAP_USER_BASEDN,
-        scope=ldap.SCOPE_SUBTREE,
-        filterstr="%s" % LDAP_USER_FILTER
-    )
-    AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
-        base_dn=LDAP_GROUP_BASEDN,
-        scope=ldap.SCOPE_SUBTREE,
-        filterstr="%s" % LDAP_GROUP_FILTER
-    )
-    AUTH_LDAP_USER_FLAGS_BY_GROUP = {
-        'is_active': LDAP_ACTIVE_GROUP,
-        'is_staff': LDAP_STAFF_GROUP,
-        'is_superuser': LDAP_SUPERUSER_GROUP,
-    }
-    AUTH_LDAP_GROUP_TYPE = GroupOfNamesType()
-    AUTH_LDAP_USER_ATTR_MAP = {
-        "first_name": "givenName",
-        "last_name": "sn",
-        "email": "mail",
-        "username": LDAP_USER_FILTER,
-    }
-    AUTH_LDAP_GLOBAL_OPTIONS = {
-        ldap.OPT_X_TLS_REQUIRE_CERT: False,
-        ldap.OPT_REFERRALS: False
-    }
-    AUTH_LDAP_ALWAYS_UPDATE_USER = True
-    AUTH_LDAP_MIRROR_GROUPS = True
-    AUTH_LDAP_FIND_GROUP_PERMS = True
-    AUTH_LDAP_CACHE_GROUPS = False
 
 # Redis Configuration
 DRYCC_REDIS_ADDRS = os.environ.get('DRYCC_REDIS_ADDRS', '127.0.0.1:6379').split(",")
diff --git a/rootfs/api/tasks.py b/rootfs/api/tasks.py
index add65b91c..14b243b80 100644
--- a/rootfs/api/tasks.py
+++ b/rootfs/api/tasks.py
@@ -10,8 +10,9 @@
 from django.conf import settings
 from django.utils.timezone import now
 from celery import shared_task
+from requests_toolbelt import user_agent
 from api.models.resource import Resource
-
+from api import __version__ as drycc_version
 logger = logging.getLogger(__name__)
 
 
@@ -59,7 +60,8 @@ def measure_config(config: List[Dict[str, str]]):
             url="%s/measurements/config/" % settings.WORKFLOW_MANAGER_URL,
             headers={
                 'Content-Type': 'application/json',
-                'Authorization': 'token %s' % settings.WORKFLOW_MANAGER_TOKEN
+                'Authorization': 'token %s' % settings.WORKFLOW_MANAGER_TOKEN,
+                'User-Agent': user_agent('Drycc Controller ', drycc_version)
             },
             data=json.dumps(config)
         )
@@ -89,7 +91,8 @@ def measure_volumes(volumes: List[Dict[str, str]]):
             url="%s/measurements/volumes/" % settings.WORKFLOW_MANAGER_URL,
             headers={
                 'Content-Type': 'application/json',
-                'Authorization': 'token %s' % settings.WORKFLOW_MANAGER_TOKEN
+                'Authorization': 'token %s' % settings.WORKFLOW_MANAGER_TOKEN,
+                'User-Agent': user_agent('Drycc Controller ', drycc_version)
             },
             data=json.dumps(volumes)
         )
@@ -120,7 +123,8 @@ def measure_networks(networks: List[Dict[str, str]]):
             url="%s/measurements/networks/" % settings.WORKFLOW_MANAGER_URL,
             headers={
                 'Content-Type': 'application/json',
-                'Authorization': 'token %s' % settings.WORKFLOW_MANAGER_TOKEN
+                'Authorization': 'token %s' % settings.WORKFLOW_MANAGER_TOKEN,
+                'User-Agent': user_agent('Drycc Controller ', drycc_version)
             },
             data=json.dumps(networks)
         )
@@ -150,7 +154,8 @@ def measure_instances(instances: List[Dict[str, str]]):
             url="%s/measurements/instances/" % settings.WORKFLOW_MANAGER_URL,
             headers={
                 'Content-Type': 'application/json',
-                'Authorization': 'token %s' % settings.WORKFLOW_MANAGER_TOKEN
+                'Authorization': 'token %s' % settings.WORKFLOW_MANAGER_TOKEN,
+                'User-Agent': user_agent('Drycc Controller ', drycc_version)
             },
             data=json.dumps(instances)
         )
@@ -180,7 +185,8 @@ def measure_resources(resources: List[Dict[str, str]]):
             url="%s/measurements/resources/" % settings.WORKFLOW_MANAGER_URL,
             headers={
                 'Content-Type': 'application/json',
-                'Authorization': 'token %s' % settings.WORKFLOW_MANAGER_TOKEN
+                'Authorization': 'token %s' % settings.WORKFLOW_MANAGER_TOKEN,
+                'User-Agent': user_agent('Drycc Controller ', drycc_version)
             },
             data=json.dumps(resources)
         )
diff --git a/rootfs/api/tests/test_auth.py b/rootfs/api/tests/test_auth.py
deleted file mode 100644
index 96ee72b10..000000000
--- a/rootfs/api/tests/test_auth.py
+++ /dev/null
@@ -1,397 +0,0 @@
-"""
-Unit tests for the Drycc api app.
-
-Run the tests with "./manage.py test api"
-"""
-from django.contrib.auth.models import User
-from django.test.utils import override_settings
-from rest_framework.authtoken.models import Token
-from unittest import mock
-
-from api.tests import TEST_ROOT, DryccTestCase
-from api.models import Certificate
-
-
-class AuthTest(DryccTestCase):
-
-    fixtures = ['test_auth.json']
-
-    """Tests user registration, authentication and authorization"""
-
-    def setUp(self):
-        self.admin = User.objects.get(username='autotest')
-        self.admin_token = Token.objects.get(user=self.admin).key
-        self.user1 = User.objects.get(username='autotest2')
-        self.user1_token = Token.objects.get(user=self.user1).key
-        self.user2 = User.objects.get(username='autotest3')
-        self.user2_token = Token.objects.get(user=self.user2).key
-
-    def test_auth(self):
-        """
-        Test that a user can register using the API, login, whoami and logout
-        """
-        # test registration workflow
-        username, password = 'newuser', 'password'
-        first_name, last_name = 'Otto', 'Test'
-        email = 'autotest@drycc.cc'
-        submit = {
-            'username': username,
-            'password': password,
-            'first_name': first_name,
-            'last_name': last_name,
-            'email': email,
-            # try to abuse superuser/staff level perms (not the first signup!)
-            'is_superuser': True,
-            'is_staff': True,
-        }
-        url = '/v2/auth/register'
-        response = self.client.post(url, submit)
-        self.assertEqual(response.status_code, 201, response.data)
-        for key in response.data:
-            self.assertIn(key, ['id', 'last_login', 'is_superuser', 'username', 'first_name',
-                                'last_name', 'email', 'is_active', 'is_superuser', 'is_staff',
-                                'date_joined', 'groups', 'user_permissions'])
-        expected = {
-            'username': username,
-            'email': email,
-            'first_name': first_name,
-            'last_name': last_name,
-            'is_active': True,
-            'is_superuser': False,
-            'is_staff': False
-        }
-        self.assertDictContainsSubset(expected, response.data)
-
-        # test login
-        response = self.client.login(username=username, password=password)
-        self.assertEqual(response, True)
-
-        user = User.objects.get(username=username)
-        token = Token.objects.get(user=user).key
-        url = '/v2/auth/whoami'
-        response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(token))
-        self.assertEqual(response.status_code, 200)
-        for key in response.data:
-            self.assertIn(key, ['id', 'last_login', 'is_superuser', 'username', 'first_name',
-                                'last_name', 'email', 'is_active', 'is_superuser', 'is_staff',
-                                'date_joined', 'groups', 'user_permissions'])
-        expected = {
-            'username': username,
-            'email': email,
-            'first_name': first_name,
-            'last_name': last_name,
-            'is_active': True,
-            'is_superuser': False,
-            'is_staff': False
-        }
-        self.assertDictContainsSubset(expected, response.data)
-
-    @override_settings(REGISTRATION_MODE="disabled")
-    def test_auth_registration_disabled(self):
-        """test that a new user cannot register when registration is disabled."""
-        url = '/v2/auth/register'
-        submit = {
-            'username': 'testuser',
-            'password': 'password',
-            'first_name': 'test',
-            'last_name': 'user',
-            'email': 'test@user.com',
-            'is_superuser': False,
-            'is_staff': False,
-        }
-        response = self.client.post(url, submit)
-        self.assertEqual(response.status_code, 403)
-
-    @override_settings(REGISTRATION_MODE="admin_only")
-    def test_auth_registration_admin_only_fails_if_not_admin(self):
-        """test that a non superuser cannot register when registration is admin only."""
-        url = '/v2/auth/register'
-        submit = {
-            'username': 'testuser',
-            'password': 'password',
-            'first_name': 'test',
-            'last_name': 'user',
-            'email': 'test@user.com',
-            'is_superuser': False,
-            'is_staff': False,
-        }
-        response = self.client.post(url, submit)
-        self.assertEqual(response.status_code, 403)
-
-    @override_settings(REGISTRATION_MODE="admin_only")
-    def test_auth_registration_admin_only_works(self):
-        """test that a superuser can register when registration is admin only."""
-        url = '/v2/auth/register'
-
-        username, password = 'newuser_by_admin', 'password'
-        first_name, last_name = 'Otto', 'Test'
-        email = 'autotest@drycc.cc'
-
-        submit = {
-            'username': username,
-            'password': password,
-            'first_name': first_name,
-            'last_name': last_name,
-            'email': email,
-            # try to abuse superuser/staff level perms (not the first signup!)
-            'is_superuser': True,
-            'is_staff': True,
-        }
-        response = self.client.post(url, submit,
-                                    HTTP_AUTHORIZATION='token {}'.format(self.admin_token))
-
-        self.assertEqual(response.status_code, 201, response.data)
-        for key in response.data:
-            self.assertIn(key, ['id', 'last_login', 'is_superuser', 'username', 'first_name',
-                                'last_name', 'email', 'is_active', 'is_superuser', 'is_staff',
-                                'date_joined', 'groups', 'user_permissions'])
-        expected = {
-            'username': username,
-            'email': email,
-            'first_name': first_name,
-            'last_name': last_name,
-            'is_active': True,
-            'is_superuser': False,
-            'is_staff': False
-        }
-        self.assertDictContainsSubset(expected, response.data)
-
-        # test login
-        response = self.client.login(username=username, password=password)
-        self.assertEqual(response, True)
-
-    @override_settings(REGISTRATION_MODE="not_a_mode")
-    def test_auth_registration_fails_with_nonexistant_mode(self):
-        """test that a registration should fail with a nonexistant mode"""
-        url = '/v2/auth/register'
-        submit = {
-            'username': 'testuser',
-            'password': 'password',
-            'first_name': 'test',
-            'last_name': 'user',
-            'email': 'test@user.com',
-            'is_superuser': False,
-            'is_staff': False,
-        }
-
-        try:
-            self.client.post(url, submit)
-        except Exception as e:
-            self.assertEqual(str(e), 'not_a_mode is not a valid registation mode')
-
-    def test_cancel(self):
-        """Test that a registered user can cancel her account."""
-        # test registration workflow
-        username, password = 'newuser', 'password'
-        submit = {
-            'username': username,
-            'password': password,
-            'first_name': 'Otto',
-            'last_name': 'Test',
-            'email': 'autotest@drycc.cc',
-            # try to abuse superuser/staff level perms
-            'is_superuser': True,
-            'is_staff': True,
-        }
-
-        other_username, other_password = 'newuser2', 'password'
-        other_submit = {
-            'username': other_username,
-            'password': other_password,
-            'first_name': 'Test',
-            'last_name': 'Tester',
-            'email': 'autotest-2@drycc.cc',
-            'is_superuser': False,
-            'is_staff': False,
-        }
-        url = '/v2/auth/register'
-        response = self.client.post(url, submit)
-        self.assertEqual(response.status_code, 201, response.data)
-
-        # cancel the account
-        url = '/v2/auth/cancel'
-        user = User.objects.get(username=username)
-        token = Token.objects.get(user=user).key
-        response = self.client.delete(url, HTTP_AUTHORIZATION='token {}'.format(token))
-        self.assertEqual(response.status_code, 204, response.data)
-
-        url = '/v2/auth/register'
-        response = self.client.post(url, other_submit)
-        self.assertEqual(response.status_code, 201, response.data)
-
-        # normal user can't delete another user
-        url = '/v2/auth/cancel'
-        other_user = User.objects.get(username=other_username)
-        other_token = Token.objects.get(user=other_user).key
-        response = self.client.delete(url, {'username': self.admin.username},
-                                      HTTP_AUTHORIZATION='token {}'.format(other_token))
-        self.assertEqual(response.status_code, 403)
-
-        # admin can delete another user
-        response = self.client.delete(url, {'username': other_username},
-                                      HTTP_AUTHORIZATION='token {}'.format(self.admin_token))
-        self.assertEqual(response.status_code, 204, response.data)
-
-        # user can not be deleted if it has an app attached to it
-        response = self.client.post(
-            '/v2/apps',
-            HTTP_AUTHORIZATION='token {}'.format(self.admin_token)
-        )
-        self.assertEqual(response.status_code, 201, response.data)
-        app_id = response.data['id']  # noqa
-        self.assertIn('id', response.data)
-
-        response = self.client.delete(url, {'username': str(self.admin)},
-                                      HTTP_AUTHORIZATION='token {}'.format(self.admin_token))
-        self.assertEqual(response.status_code, 409)
-
-        # user can not be deleted if it has a downstream object owned by them, like a certificate
-        domain_name = 'foo.com'
-        with open('{}/certs/{}.key'.format(TEST_ROOT, domain_name)) as f:
-            key = f.read()
-        with open('{}/certs/{}.cert'.format(TEST_ROOT, domain_name)) as f:
-            cert = f.read()
-
-        Certificate.objects.create(owner=self.admin, certificate=cert, key=key)
-        response = self.client.delete(url, {'username': str(self.admin)},
-                                      HTTP_AUTHORIZATION='token {}'.format(self.admin_token))
-        self.assertEqual(response.status_code, 409, response.data)
-
-    def test_passwd(self):
-        """Test that a registered user can change the password."""
-        # test registration workflow
-        username, password = 'newuser', 'password'
-        first_name, last_name = 'Otto', 'Test'
-        email = 'autotest@drycc.cc'
-        submit = {
-            'username': username,
-            'password': password,
-            'first_name': first_name,
-            'last_name': last_name,
-            'email': email,
-        }
-        url = '/v2/auth/register'
-        response = self.client.post(url, submit)
-        self.assertEqual(response.status_code, 201, response.data)
-        # change password without new password
-        url = '/v2/auth/passwd'
-        user = User.objects.get(username=username)
-        token = Token.objects.get(user=user).key
-        response = self.client.post(url, {},
-                                    HTTP_AUTHORIZATION='token {}'.format(token))
-        self.assertEqual(response.status_code, 400, response.data)
-        self.assertEqual(response.data, {'detail': 'new_password is a required field'})
-        # change password without password field
-        response = self.client.post(url, {'new_password': 'test'},
-                                    HTTP_AUTHORIZATION='token {}'.format(token))
-        self.assertEqual(response.status_code, 400, response.data)
-        self.assertEqual(response.data, {'detail': 'password is a required field'})
-        # change password
-        submit = {
-            'password': 'password2',
-            'new_password': password,
-        }
-        response = self.client.post(url, submit,
-                                    HTTP_AUTHORIZATION='token {}'.format(token))
-        self.assertEqual(response.status_code, 401, response.data)
-        self.assertEqual(response.data, {'detail': 'Current password does not match'})
-        self.assertEqual(response.get('content-type'), 'application/json')
-        submit = {
-            'password': password,
-            'new_password': 'password2',
-        }
-        response = self.client.post(url, submit,
-                                    HTTP_AUTHORIZATION='token {}'.format(token))
-        self.assertEqual(response.status_code, 200, response.data)
-
-        # test login with old password
-        response = self.client.login(username=username, password=password)
-        self.assertEqual(response, False)
-
-        # test login with new password
-        response = self.client.login(username=username, password='password2')
-        self.assertEqual(response, True)
-
-    def test_change_user_passwd(self):
-        """
-        Test that an administrator can change a user's password, while a regular user cannot.
-        """
-        # change password
-        url = '/v2/auth/passwd'
-        old_password = self.user1.password
-        new_password = 'password'
-        submit = {
-            'username': self.user1.username,
-            'new_password': new_password,
-        }
-        response = self.client.post(url, submit,
-                                    HTTP_AUTHORIZATION='token {}'.format(self.admin_token))
-        self.assertEqual(response.status_code, 200, response.data)
-
-        # test login with old password
-        response = self.client.login(username=self.user1.username, password=old_password)
-        self.assertEqual(response, False)
-
-        # test login with new password
-        response = self.client.login(username=self.user1.username, password=new_password)
-        self.assertEqual(response, True)
-
-        # Non-admins can't change another user's password
-        submit['password'], submit['new_password'] = submit['new_password'], old_password
-        url = '/v2/auth/passwd'
-        response = self.client.post(url, submit,
-                                    HTTP_AUTHORIZATION='token {}'.format(self.user2_token))
-        self.assertEqual(response.status_code, 403)
-
-        # change back password with a regular user
-        response = self.client.post(url, submit,
-                                    HTTP_AUTHORIZATION='token {}'.format(self.user1_token))
-        self.assertEqual(response.status_code, 200, response.data)
-
-        # test login with new password
-        response = self.client.login(username=self.user1.username, password=old_password)
-        self.assertEqual(response, True)
-
-    def test_regenerate(self):
-        """ Test that token regeneration works"""
-        url = '/v2/auth/tokens/'
-
-        self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.admin_token)
-        response = self.client.post(url, {})
-        self.assertEqual(response.status_code, 200, response.data)
-        self.assertNotEqual(response.data['token'], self.admin_token)
-
-        self.admin_token = Token.objects.get(user=self.admin).key
-        self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.admin_token)
-
-        response = self.client.post(url, {"username": "autotest2"})
-        self.assertEqual(response.status_code, 200, response.data)
-        self.assertNotEqual(response.data['token'], self.user1_token)
-
-        response = self.client.post(url, {"all": "true"})
-        self.assertEqual(response.status_code, 200, response.data)
-
-        response = self.client.post(url, {})
-        self.assertEqual(response.status_code, 401, response.data)
-
-    def test_key(self):
-        url = '/v2/auth/tokens/autotest2/'
-        self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.admin_token)
-        response = self.client.get(url)
-        self.assertEqual(response.status_code, 200, response.data)
-
-        self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.user1_token)
-        response = self.client.get(url)
-        self.assertEqual(response.status_code, 200, response.data)
-
-        self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.user2_token)
-        response = self.client.get(url)
-        self.assertEqual(response.status_code, 403, response.data)
-
-    @mock.patch('django_auth_ldap.backend.logger')
-    def test_auth_no_ldap_by_default(self, mock_logger):
-        """Ensure that LDAP authentication is disabled by default."""
-        self.test_auth()
-        # NOTE(bacongobbler): Using https://github.com/drycc/controller/issues/1189
-        # as a test case
-        mock_logger.warning.assert_not_called()
diff --git a/rootfs/api/tests/test_build.py b/rootfs/api/tests/test_build.py
index f9a3181e4..f53390afb 100644
--- a/rootfs/api/tests/test_build.py
+++ b/rootfs/api/tests/test_build.py
@@ -119,7 +119,7 @@ def test_build_default_containers(self, mock_requests):
         self.assertEqual(container['type'], 'cmd')
         self.assertEqual(container['release'], 'v2')
         # pod name is auto generated so use regex
-        self.assertRegex(container['name'], app_id + '-cmd-[0-9]{8,10}-[a-z0-9]{5}')
+        self.assertRegex(container['name'], app_id + '-cmd-[0-9]{1,10}-[a-z0-9]{5}')
 
         # post an image as a build with a procfile
         app_id = self.create_app()
@@ -143,7 +143,7 @@ def test_build_default_containers(self, mock_requests):
         self.assertEqual(container['type'], 'web')
         self.assertEqual(container['release'], 'v2')
         # pod name is auto generated so use regex
-        self.assertRegex(container['name'], app_id + '-web-[0-9]{7,10}-[a-z0-9]{5}')
+        self.assertRegex(container['name'], app_id + '-web-[0-9]{1,10}-[a-z0-9]{5}')
 
         # start with a new app
         app_id = self.create_app()
@@ -166,7 +166,7 @@ def test_build_default_containers(self, mock_requests):
         self.assertEqual(container['type'], 'cmd')
         self.assertEqual(container['release'], 'v2')
         # pod name is auto generated so use regex
-        self.assertRegex(container['name'], app_id + '-cmd-[0-9]{8,10}-[a-z0-9]{5}')
+        self.assertRegex(container['name'], app_id + '-cmd-[0-9]{1,10}-[a-z0-9]{5}')
 
         # start with a new app
         app_id = self.create_app()
@@ -193,7 +193,7 @@ def test_build_default_containers(self, mock_requests):
         self.assertEqual(container['type'], 'cmd')
         self.assertEqual(container['release'], 'v2')
         # pod name is auto generated so use regex
-        self.assertRegex(container['name'], app_id + '-cmd-[0-9]{8,10}-[a-z0-9]{5}')
+        self.assertRegex(container['name'], app_id + '-cmd-[0-9]{1,10}-[a-z0-9]{5}')
 
         # start with a new app
         app_id = self.create_app()
@@ -220,7 +220,7 @@ def test_build_default_containers(self, mock_requests):
         self.assertEqual(container['type'], 'web')
         self.assertEqual(container['release'], 'v2')
         # pod name is auto generated so use regex
-        self.assertRegex(container['name'], app_id + '-web-[0-9]{7,10}-[a-z0-9]{5}')
+        self.assertRegex(container['name'], app_id + '-web-[0-9]{1,10}-[a-z0-9]{5}')
 
         # start with a new app
         app_id = self.create_app()
@@ -278,7 +278,7 @@ def test_build_forgotten_procfile(self, mock_requests):
         self.assertEqual(container['type'], 'worker')
         self.assertEqual(container['release'], 'v2')
         # pod name is auto generated so use regex
-        self.assertRegex(container['name'], app_id + '-worker-[0-9]{8,10}-[a-z0-9]{5}')
+        self.assertRegex(container['name'], app_id + '-worker-[0-9]{1,10}-[a-z0-9]{5}')
 
         # do another deploy for this time forget Procfile
         url = "/v2/apps/{app_id}/builds".format(**locals())
@@ -349,7 +349,7 @@ def test_build_no_remove_process(self, mock_requests):
         self.assertEqual(container['type'], 'worker')
         self.assertEqual(container['release'], 'v2')
         # pod name is auto generated so use regex
-        self.assertRegex(container['name'], app_id + '-worker-[0-9]{7,10}-[a-z0-9]{5}')
+        self.assertRegex(container['name'], app_id + '-worker-[0-9]{1,10}-[a-z0-9]{5}')
 
         # do another deploy for this time forget Procfile
         url = "/v2/apps/{app_id}/builds".format(**locals())
@@ -366,7 +366,7 @@ def test_build_no_remove_process(self, mock_requests):
         self.assertEqual(container['type'], 'worker')
         self.assertEqual(container['release'], 'v3')
         # pod name is auto generated so use regex
-        self.assertRegex(container['name'], app_id + '-worker-[0-9]{8,10}-[a-z0-9]{5}')
+        self.assertRegex(container['name'], app_id + '-worker-[0-9]{1,10}-[a-z0-9]{5}')
 
         # verify web is still there
         url = "/v2/apps/{app_id}/pods/web".format(**locals())
@@ -377,7 +377,7 @@ def test_build_no_remove_process(self, mock_requests):
         self.assertEqual(container['type'], 'web')
         self.assertEqual(container['release'], 'v3')
         # pod name is auto generated so use regex
-        self.assertRegex(container['name'], app_id + '-web-[0-9]{8,10}-[a-z0-9]{5}')
+        self.assertRegex(container['name'], app_id + '-web-[0-9]{1,10}-[a-z0-9]{5}')
 
         # look at the app structure
         url = "/v2/apps/{app_id}".format(**locals())
@@ -400,7 +400,7 @@ def test_build_no_remove_process(self, mock_requests):
         self.assertEqual(container['type'], 'worker')
         self.assertEqual(container['release'], 'v3')
         # pod name is auto generated so use regex
-        self.assertRegex(container['name'], app_id + '-worker-[0-9]{8,10}-[a-z0-9]{5}')
+        self.assertRegex(container['name'], app_id + '-worker-[0-9]{1,10}-[a-z0-9]{5}')
 
     @override_settings(DRYCC_DEPLOY_REJECT_IF_PROCFILE_MISSING=True)
     def test_build_forgotten_procfile_reject(self, mock_requests):
@@ -441,7 +441,7 @@ def test_build_forgotten_procfile_reject(self, mock_requests):
         self.assertEqual(container['type'], 'worker')
         self.assertEqual(container['release'], 'v2')
         # pod name is auto generated so use regex
-        self.assertRegex(container['name'], app_id + '-worker-[0-9]{8,10}-[a-z0-9]{5}')
+        self.assertRegex(container['name'], app_id + '-worker-[0-9]{1,10}-[a-z0-9]{5}')
 
         # do another deploy for this time forget Procfile
         url = "/v2/apps/{app_id}/builds".format(**locals())
diff --git a/rootfs/api/tests/test_hooks.py b/rootfs/api/tests/test_hooks.py
index 36d8bc0c5..cb53f751b 100644
--- a/rootfs/api/tests/test_hooks.py
+++ b/rootfs/api/tests/test_hooks.py
@@ -240,7 +240,7 @@ def test_build_hook_procfile(self, mock_requests):
         self.assertEqual(container['type'], 'web')
         self.assertEqual(container['release'], 'v2')
         # pod name is auto generated so use regex
-        self.assertRegex(container['name'], app_id + '-web-[0-9]{8,10}-[a-z0-9]{5}')
+        self.assertRegex(container['name'], app_id + '-web-[0-9]{1,10}-[a-z0-9]{5}')
 
         # post the build without an auth token
         self.client.credentials()
@@ -285,7 +285,7 @@ def test_build_hook_dockerfile(self, mock_requests):
         self.assertEqual(container['type'], 'cmd')
         self.assertEqual(container['release'], 'v2')
         # pod name is auto generated so use regex
-        self.assertRegex(container['name'], app_id + '-cmd-[0-9]{8,10}-[a-z0-9]{5}')
+        self.assertRegex(container['name'], app_id + '-cmd-[0-9]{1,10}-[a-z0-9]{5}')
 
         # post the build without an auth token
         self.client.credentials()
diff --git a/rootfs/api/tests/test_perm.py b/rootfs/api/tests/test_perm.py
index 85eaa8e5f..d7d387283 100644
--- a/rootfs/api/tests/test_perm.py
+++ b/rootfs/api/tests/test_perm.py
@@ -1,170 +1,14 @@
+from django.test import tag
 from django.contrib.auth.models import User
 from rest_framework.authtoken.models import Token
-from django.test.utils import override_settings
 from api.tests import DryccTestCase
 
 
-class TestAdminPerms(DryccTestCase):
-
-    def test_first_signup(self):
-        # register a first user
-        username, password = 'firstuser', 'password'
-        email = 'autotest@drycc.cc'
-        submit = {
-            'username': username,
-            'password': password,
-            'email': email,
-        }
-        url = '/v2/auth/register'
-        response = self.client.post(url, submit)
-        self.assertEqual(response.status_code, 201, response.data)
-        self.assertTrue(response.data['is_superuser'])
-
-        # register a second user
-        username, password = 'seconduser', 'password'
-        email = 'autotest@drycc.cc'
-        submit = {
-            'username': username,
-            'password': password,
-            'email': email,
-        }
-        url = '/v2/auth/register'
-        response = self.client.post(url, submit)
-        self.assertEqual(response.status_code, 201, response.data)
-        self.assertFalse(response.data['is_superuser'])
-
-    @override_settings(REGISTRATION_MODE="admin_only")
-    def test_first_signup_admin_only(self):
-        # register a first user
-        username, password = 'firstuser', 'password'
-        email = 'autotest@drycc.cc'
-        submit = {
-            'username': username,
-            'password': password,
-            'email': email,
-        }
-        url = '/v2/auth/register'
-        response = self.client.post(url, submit)
-        self.assertEqual(response.status_code, 201, response.data)
-        self.assertTrue(response.data['is_superuser'])
-
-    def test_list(self):
-        submit = {
-            'username': 'firstuser',
-            'password': 'password',
-            'email': 'autotest@drycc.cc',
-        }
-        url = '/v2/auth/register'
-        response = self.client.post(url, submit)
-        self.assertEqual(response.status_code, 201, response.data)
-        self.assertTrue(response.data['is_superuser'])
-
-        user = User.objects.get(username='firstuser')
-        token = Token.objects.get(user=user).key
-        self.client.credentials(HTTP_AUTHORIZATION='Token ' + token)
-
-        response = self.client.get('/v2/admin/perms')
-        self.assertEqual(response.status_code, 200, response.data)
-        self.assertEqual(len(response.data['results']), 1)
-        self.assertEqual(response.data['results'][0]['username'], 'firstuser')
-        self.assertTrue(response.data['results'][0]['is_superuser'])
-
-        # register a non-superuser
-        submit = {
-            'username': 'seconduser',
-            'password': 'password',
-            'email': 'autotest@drycc.cc',
-        }
-        url = '/v2/auth/register'
-        response = self.client.post(url, submit)
-        self.assertEqual(response.status_code, 201, response.data)
-        self.assertFalse(response.data['is_superuser'])
-
-        user = User.objects.get(username='seconduser')
-        token = Token.objects.get(user=user).key
-        self.client.credentials(HTTP_AUTHORIZATION='Token ' + token)
-
-        response = self.client.get('/v2/admin/perms')
-        self.assertEqual(response.status_code, 403)
-        self.assertIn('You do not have permission', response.data['detail'])
-
-    def test_create(self):
-        submit = {
-            'username': 'first',
-            'password': 'password',
-            'email': 'autotest@drycc.cc',
-        }
-        url = '/v2/auth/register'
-        response = self.client.post(url, submit)
-        self.assertEqual(response.status_code, 201, response.data)
-        self.assertTrue(response.data['is_superuser'])
-
-        submit = {
-            'username': 'second',
-            'password': 'password',
-            'email': 'autotest@drycc.cc',
-        }
-        url = '/v2/auth/register'
-        response = self.client.post(url, submit)
-        self.assertEqual(response.status_code, 201, response.data)
-        self.assertFalse(response.data['is_superuser'])
-
-        user = User.objects.get(username='first')
-        token = Token.objects.get(user=user).key
-        self.client.credentials(HTTP_AUTHORIZATION='Token ' + token)
-        # grant user 2 the superuser perm
-        url = '/v2/admin/perms'
-        body = {'username': 'second'}
-        response = self.client.post(url, body)
-        self.assertEqual(response.status_code, 201, response.data)
-        response = self.client.get(url)
-        self.assertEqual(response.status_code, 200, response.data)
-        self.assertEqual(len(response.data['results']), 2)
-        self.assertIn('second', str(response.data['results']))
-
-    def test_delete(self):
-        submit = {
-            'username': 'first',
-            'password': 'password',
-            'email': 'autotest@drycc.cc',
-        }
-        url = '/v2/auth/register'
-        response = self.client.post(url, submit)
-        self.assertEqual(response.status_code, 201, response.data)
-        self.assertTrue(response.data['is_superuser'])
-
-        submit = {
-            'username': 'second.lastname',
-            'password': 'password',
-            'email': 'autotest@drycc.cc',
-        }
-        url = '/v2/auth/register'
-        response = self.client.post(url, submit)
-        self.assertEqual(response.status_code, 201, response.data)
-        self.assertFalse(response.data['is_superuser'])
-
-        user = User.objects.get(username='first')
-        token = Token.objects.get(user=user).key
-        self.client.credentials(HTTP_AUTHORIZATION='Token ' + token)
-        # grant user 2 the superuser perm
-        url = '/v2/admin/perms'
-        body = {'username': 'second.lastname'}
-        response = self.client.post(url, body)
-        self.assertEqual(response.status_code, 201, response.data)
-
-        # revoke the superuser perm
-        response = self.client.delete(url + '/second.lastname')
-        self.assertEqual(response.status_code, 204, response.data)
-        response = self.client.get(url)
-        self.assertEqual(response.status_code, 200, response.data)
-        self.assertEqual(len(response.data['results']), 1)
-        self.assertNotIn('two', str(response.data['results']))
-
-
 class TestAppPerms(DryccTestCase):
 
     fixtures = ['test_sharing.json']
 
+    @tag('auth')
     def setUp(self):
         self.user = User.objects.get(username='autotest-1')
         self.token = Token.objects.get(user=self.user).key
@@ -176,6 +20,7 @@ def setUp(self):
         self.user3 = User.objects.get(username='autotest-3')
         self.token3 = Token.objects.get(user=self.user3).key
 
+    @tag('auth')
     def test_create(self):
         # check that user 1 sees her lone app and user 2's app
         self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.token)
@@ -219,6 +64,7 @@ def test_create(self):
             self.assertEqual(len(response.data['results']), 0)
         # TODO:  check that user 2 can git push the app
 
+    @tag('auth')
     def test_create_errors(self):
         # check that user 1 sees her lone app
         response = self.client.get('/v2/apps')
@@ -231,6 +77,7 @@ def test_create_errors(self):
         response = self.client.post(url, body)
         self.assertEqual(response.status_code, 403)
 
+    @tag('auth')
     def test_delete(self):
         # give user 2 permission to user 1's app
         response = self.client.get('/v2/apps')
@@ -263,6 +110,7 @@ def test_delete(self):
         response = self.client.delete(url)
         self.assertEqual(response.status_code, 403)
 
+    @tag('auth')
     def test_list(self):
         # check that user 1 sees her lone app and user 2's app
         response = self.client.get('/v2/apps')
@@ -280,12 +128,14 @@ def test_list(self):
         response = self.client.get("/v2/apps/{}/perms".format(app_id))
         self.assertEqual(response.data, {'users': ['autotest-2']})
 
+    @tag('auth')
     def test_admin_can_list(self):
         """Check that an administrator can list an app's perms"""
         response = self.client.get('/v2/apps')
         self.assertEqual(response.status_code, 200, response.data)
         self.assertEqual(len(response.data['results']), 2)
 
+    @tag('auth')
     def test_list_errors(self):
         response = self.client.get('/v2/apps')
         app_id = response.data['results'][0]['id']
@@ -295,6 +145,7 @@ def test_list_errors(self):
         response = self.client.get("/v2/apps/{}/perms".format(app_id))
         self.assertEqual(response.status_code, 403)
 
+    @tag('auth')
     def test_unauthorized_user_cannot_modify_perms(self):
         """
         An unauthorized user should not be able to modify other apps' permissions.
@@ -313,6 +164,7 @@ def test_unauthorized_user_cannot_modify_perms(self):
         response = self.client.post(url, body)
         self.assertEqual(response.status_code, 403)
 
+    @tag('auth')
     def test_collaborator_cannot_share(self):
         """
         An collaborator should not be able to modify the app's permissions.
diff --git a/rootfs/api/urls.py b/rootfs/api/urls.py
index 5ed8ac65f..b819fa47f 100644
--- a/rootfs/api/urls.py
+++ b/rootfs/api/urls.py
@@ -5,17 +5,23 @@
 
 from django.conf import settings
 from django.conf.urls import include, url
-from rest_framework.authtoken.views import obtain_auth_token as views_obtain_auth_token
 from rest_framework.routers import DefaultRouter
-
+from social_core.utils import setting_name
 from api import views
 
 
 router = DefaultRouter(trailing_slash=False)
-
+extra = getattr(settings, setting_name('TRAILING_SLASH'), True) and '/' or ''
 # Add the generated REST URLs and login/logout endpoint
 urlpatterns = [
     url(r'^', include(router.urls)),
+    url(r'^login/(?P<backend>[^/]+){0}$'.format(extra), views.auth,
+        name='begin'),
+    url(r'^complete/(?P<backend>[^/]+){0}$'.format(extra), views.complete,
+        name='complete'),
+    url('', include('social_django.urls', namespace='social')),
+    url(r'auth/login/?$', views.AuthLoginView.as_view()),
+    url(r'auth/token/(?P<key>[-_\w]+)/?$', views.AuthTokenView.as_view()),
     # application release components
     url(r"^apps/(?P<id>{})/config/?$".format(settings.APP_URL_REGEX),
         views.ConfigViewSet.as_view({'get': 'retrieve', 'post': 'create'})),
@@ -119,20 +125,8 @@
     url(r'^hooks/config/?$',
         views.ConfigHookViewSet.as_view({'post': 'create'})),
     # authn / authz
-    url(r'^auth/register/?$',
-        views.UserRegistrationViewSet.as_view({'post': 'create'})),
-    url(r'^auth/cancel/?$',
-        views.UserManagementViewSet.as_view({'delete': 'destroy'})),
-    url(r'^auth/passwd/?$',
-        views.UserManagementViewSet.as_view({'post': 'passwd'})),
     url(r'^auth/whoami/?$',
         views.UserManagementViewSet.as_view({'get': 'list'})),
-    url(r'^auth/login/$',
-        views_obtain_auth_token),
-    url(r'^auth/tokens/$',
-        views.TokenManagementViewSet.as_view({'post': 'regenerate'})),
-    url(r'^auth/tokens/(?P<username>[\w.@+-]+)/?$',
-        views.TokenManagementViewSet.as_view({'get': 'token'})),
     # admin sharing
     url(r'^admin/perms/(?P<username>[\w.@+-]+)/?$',
         views.AdminPermsViewSet.as_view({'delete': 'destroy'})),
@@ -155,6 +149,6 @@
         views.UserView.as_view({'patch': 'enable'})),
     url(r'^users/(?P<username>[\w.@+-]+)/disable/?$',
         views.UserView.as_view({'patch': 'disable'})),
-    url(r'^apps/(?P<id>{})/metrics/(?P<container_type>[a-z0-9]+(\-[a-z0-9]+)*)?$'.format(settings.APP_URL_REGEX),  # noqa
+    url(r'^apps/(?P<id>{})/metrics/(?P<container_type>[a-z0-9]+(\-[a-z0-9]+)*)/?$'.format(settings.APP_URL_REGEX),  # noqa
         views.MetricView.as_view({'get': 'status'})),
 ]
diff --git a/rootfs/api/views.py b/rootfs/api/views.py
index 762b8535b..fd83919bf 100644
--- a/rootfs/api/views.py
+++ b/rootfs/api/views.py
@@ -1,28 +1,38 @@
 """
 RESTful view classes for presenting Drycc API objects.
 """
+import uuid
 import logging
+import json
 from copy import deepcopy
+from django.core.cache import cache
 from django.http import Http404, HttpResponse
 from django.conf import settings
 from django.contrib.auth.models import User
-from django.shortcuts import get_object_or_404
+from django.shortcuts import get_object_or_404, redirect
 from guardian.shortcuts import assign_perm, get_objects_for_user, \
     get_users_with_perms, remove_perm
 from django.views.generic import View
-from django.db.models.deletion import ProtectedError
-from rest_framework import mixins, renderers, status
-from rest_framework.exceptions import PermissionDenied, NotFound, AuthenticationFailed
+from rest_framework import renderers, status
+from rest_framework.exceptions import PermissionDenied, NotFound
 from rest_framework.permissions import IsAuthenticated
 from rest_framework.response import Response
 from rest_framework.viewsets import GenericViewSet
-from rest_framework.authtoken.models import Token
 
-from api import influxdb, authentication, models, permissions, serializers, viewsets
+from api import influxdb, models, permissions, serializers, viewsets
 from api.models import AlreadyExists, ServiceUnavailable, DryccException, \
     UnprocessableEntity
 
+from django.views.decorators.cache import never_cache
+from django.contrib.auth import REDIRECT_FIELD_NAME
+from django.views.decorators.csrf import csrf_exempt
+from social_django.utils import psa
+from social_django.views import _do_login
+from social_core.utils import setting_name
+from api.apps_extra.social_core.actions import do_auth, do_complete
+
 logger = logging.getLogger(__name__)
+NAMESPACE = getattr(settings, setting_name('URL_NAMESPACE'), None) or 'social'
 
 
 class ReadinessCheckView(View):
@@ -53,92 +63,42 @@ def get(self, request):
     head = get
 
 
-class UserRegistrationViewSet(GenericViewSet,
-                              mixins.CreateModelMixin):
-    """ViewSet to handle registering new users. The logic is in the serializer."""
-    authentication_classes = [authentication.AnonymousOrAuthenticatedAuthentication]
-    permission_classes = [permissions.HasRegistrationAuth]
-    serializer_class = serializers.UserSerializer
+@never_cache
+@psa('{0}:complete'.format(NAMESPACE))
+def auth(request, backend):
+    return do_auth(request.backend, redirect_name=REDIRECT_FIELD_NAME)
 
-    def create(self, request, *args, **kwargs):
-        if settings.LDAP_ENDPOINT:
-            raise DryccException("You cannot register user when ldap is enabled.")
-        if settings.OAUTH_ACCESS_TOKEN_URL:
-            raise DryccException("You cannot register user when oauth2 is enabled.")
-        return super(UserRegistrationViewSet, self).create(request, *args, **kwargs)
 
+@never_cache
+@csrf_exempt
+@psa('{0}:complete'.format(NAMESPACE))
+def complete(request, backend, *args, **kwargs):
+    """Authentication complete view"""
+    return do_complete(request.backend, _do_login, user=request.user,
+                       redirect_name=REDIRECT_FIELD_NAME, request=request,
+                       *args, **kwargs)
 
-class UserManagementViewSet(GenericViewSet):
-    serializer_class = serializers.UserSerializer
 
-    def get_queryset(self):
-        return User.objects.filter(pk=self.request.user.pk)
+class AuthLoginView(View):
+    def post(self, request, *args, **kwargs):
+        def get_local_host(request):
+            uri = request.build_absolute_uri()
+            return uri[0:uri.find(request.path)]
+        res = redirect(get_local_host(request) + "/v2/login/drycc/?key=" + uuid.uuid4().hex)
+        return res
 
-    def get_object(self):
-        return self.get_queryset()[0]
 
-    def list(self, request, **kwargs):
-        user = self.get_object()
-        serializer = self.get_serializer(user, many=False)
-        return Response(serializer.data)
+class AuthTokenView(View):
+    def get(self, request, *args, **kwargs):
+        state = cache.get("oidc_key_" + self.kwargs['key'], "")
+        token = cache.get("oidc_state_" + state, {})
+        if not token.get('token'):
+            return HttpResponse(status=404)
+        return HttpResponse(json.dumps(token))
 
-    def destroy(self, request, **kwargs):
-        if settings.LDAP_ENDPOINT:
-            raise DryccException("You cannot destroy user when ldap is enabled.")
-        if settings.OAUTH_ACCESS_TOKEN_URL:
-            raise DryccException("You cannot destroy user when oauth2 is enabled.")
-        calling_obj = self.get_object()
-        target_obj = calling_obj
-
-        if request.data.get('username'):
-            # if you "accidentally" target yourself, that should be fine
-            if calling_obj.username == request.data['username'] or calling_obj.is_superuser:
-                target_obj = get_object_or_404(User, username=request.data['username'])
-            else:
-                raise PermissionDenied()
 
-        # A user can not be removed without apps changing ownership first
-        if len(models.App.objects.filter(owner=target_obj)) > 0:
-            msg = '{} still has applications assigned. Delete or transfer ownership'.format(str(target_obj))  # noqa
-            raise AlreadyExists(msg)
-
-        try:
-            target_obj.delete()
-            return Response(status=status.HTTP_204_NO_CONTENT)
-        except ProtectedError as e:
-            raise AlreadyExists(e)
-
-    def passwd(self, request, **kwargs):
-        if not request.data.get('new_password'):
-            raise DryccException("new_password is a required field")
-        if settings.LDAP_ENDPOINT:
-            raise DryccException("You cannot change password when ldap is enabled.")
-        if settings.OAUTH_ACCESS_TOKEN_URL:
-            raise DryccException("You cannot change user when oauth2 is enabled.")
-        caller_obj = self.get_object()
-        target_obj = self.get_object()
-        if request.data.get('username'):
-            # if you "accidentally" target yourself, that should be fine
-            if caller_obj.username == request.data['username'] or caller_obj.is_superuser:
-                target_obj = get_object_or_404(User, username=request.data['username'])
-            else:
-                raise PermissionDenied()
-
-        if not caller_obj.is_superuser:
-            if not request.data.get('password'):
-                raise DryccException("password is a required field")
-            if not target_obj.check_password(request.data['password']):
-                raise AuthenticationFailed('Current password does not match')
-
-        target_obj.set_password(request.data['new_password'])
-        target_obj.save()
-        return Response({'status': 'password set'})
-
-
-class TokenManagementViewSet(GenericViewSet,
-                             mixins.DestroyModelMixin):
+class UserManagementViewSet(GenericViewSet):
     serializer_class = serializers.UserSerializer
-    permission_classes = [permissions.CanRegenerateToken]
 
     def get_queryset(self):
         return User.objects.filter(pk=self.request.user.pk)
@@ -146,34 +106,10 @@ def get_queryset(self):
     def get_object(self):
         return self.get_queryset()[0]
 
-    def regenerate(self, request, **kwargs):
-        obj = self.get_object()
-
-        if 'all' in request.data:
-            for user in User.objects.all():
-                if not user.is_anonymous:
-                    token = Token.objects.get(user=user)
-                    token.delete()
-                    Token.objects.create(user=user)
-            return Response("")
-
-        if 'username' in request.data:
-            obj = get_object_or_404(User,
-                                    username=request.data['username'])
-            self.check_object_permissions(self.request, obj)
-
-        token = Token.objects.get(user=obj)
-        token.delete()
-        token = Token.objects.create(user=obj)
-        return Response({'token': token.key})
-
-    def token(self, request, **kwargs):
-        if self.request.user.username == kwargs['username'] \
-                or self.request.user.is_superuser:
-            obj = get_object_or_404(User, username=kwargs['username'])
-            token = Token.objects.get(user=obj)
-            return Response({'token': token.key})
-        return Response(status=status.HTTP_403_FORBIDDEN)
+    def list(self, request, **kwargs):
+        user = self.get_object()
+        serializer = self.get_serializer(user, many=False)
+        return Response(serializer.data)
 
 
 class BaseDryccViewSet(viewsets.OwnerViewSet):
diff --git a/rootfs/bin/test-unit b/rootfs/bin/test-unit
index 7b9761a37..6df4dfd44 100755
--- a/rootfs/bin/test-unit
+++ b/rootfs/bin/test-unit
@@ -29,5 +29,5 @@ start_redis
 start_rabbitmq
 start_celery
 python3 manage.py check
-coverage run manage.py test --settings=api.settings.testing --noinput api scheduler.tests
+coverage run manage.py test --settings=api.settings.testing --noinput api scheduler.tests --exclude-tag=auth
 coverage report -m
diff --git a/rootfs/requirements.txt b/rootfs/requirements.txt
index 0245e1b9f..9d98929e7 100644
--- a/rootfs/requirements.txt
+++ b/rootfs/requirements.txt
@@ -1,8 +1,7 @@
 # Drycc controller requirements
 backoff==1.10.0
 django==2.2.24
-django-auth-ldap==2.2.0
-django-cors-middleware==1.5.0
+django-cors-headers==3.7.0
 django-guardian==2.3.0
 djangorestframework==3.11.2
 docker==4.2.2
@@ -24,4 +23,6 @@ celery==5.0.2
 django_redis==4.12.1
 influxdb-client==1.13.0
 dj-database-url
+social-auth-app-django==4.0.0
+python-jose
 Authlib
