Remove JWT flow for authorization

Change-Id: Iab1edbff313efd0988ed68afee9f8e747dc8fe5f
diff --git a/Changes b/Changes
index baa36e4..c09b288 100755
--- a/Changes
+++ b/Changes
@@ -8,6 +8,8 @@
         - Enable Zooming on mobile clients. (diewald)
         - Auth Plugin sets cookie expiration explicitely
           to 3 days. (diewald)
+        - Remove JWT Auth flow, which was deprecated since 0.42.
+          (diewald)
 
 0.44 2022-02-31
         - Fixed autosecrets migration. (diewald)
diff --git a/lib/Kalamar/Plugin/Auth.pm b/lib/Kalamar/Plugin/Auth.pm
index 7e712fb..2666108 100644
--- a/lib/Kalamar/Plugin/Auth.pm
+++ b/lib/Kalamar/Plugin/Auth.pm
@@ -3,13 +3,11 @@
 use File::Basename 'dirname';
 use File::Spec::Functions qw/catdir/;
 use Mojo::ByteStream 'b';
-use Mojo::Util qw!deprecated b64_encode encode!;
+use Mojo::Util qw!b64_encode encode!;
 use Mojo::JSON qw'decode_json encode_json';
 use Encode 'is_utf8';
 
 # This is a plugin to deal with the Kustvakt OAuth server.
-# It establishes both the JWT as well as the OAuth password
-# flow for login.
 # All tokens are stored in the session. Access tokens are short-lived,
 # which limits the effects of misuse.
 # Refresh tokens are bound to client id and client secret,
@@ -37,6 +35,11 @@
     $param = { %$param, %$config_param };
   };
 
+  if ($param->{jwt}) {
+    $app->log->error('JWT flow is no longer supported');
+    return;
+  };
+
   # Load 'notifications' plugin
   unless (exists $app->renderer->helpers->{notify}) {
     $app->plugin(Notifications => {
@@ -253,1207 +256,761 @@
   # Log in to the system
   my $r = $app->routes;
 
-  if ($param->{oauth2}) {
-
-    my $client_id = $param->{client_id};
-    my $client_secret = $param->{client_secret};
+  my $client_id = $param->{client_id};
+  my $client_secret = $param->{client_secret};
 
 
-    # Sets a requested token and returns
-    # an error, if it didn't work
-    $app->helper(
-      'auth.set_tokens_p' => sub {
-        my ($c, $json) = @_;
-        my $promise = Mojo::Promise->new;
+  # Sets a requested token and returns
+  # an error, if it didn't work
+  $app->helper(
+    'auth.set_tokens_p' => sub {
+      my ($c, $json) = @_;
+      my $promise = Mojo::Promise->new;
 
-        # No json object
-        unless ($json) {
-          return $promise->reject({
-            message => 'Response is no valid JSON object (remote)'
-          });
-        };
+      # No json object
+      unless ($json) {
+        return $promise->reject({
+          message => 'Response is no valid JSON object (remote)'
+        });
+      };
 
-        # There is an error here
-        # Dealing with errors here
-        if ($json->{error} && ref $json->{error} ne 'ARRAY') {
-          return $promise->reject(
-            {
-              message => $json->{error} . ($json->{error_description} ? ': ' . $json->{error_description} : '')
-            }
-          );
-        }
-
-        # There is an array of errors
-        elsif (my $error = $json->{errors} // $json->{error}) {
-          if (ref $error eq 'ARRAY') {
-            my @errors = ();
-            foreach (@{$error}) {
-              if ($_->[1]) {
-                push @errors, { code => $_->[0], message => $_->[1]}
-              }
-            }
-            return $promise->reject(@errors);
-          }
-
-          return $promise->reject({message => $error});
-        };
-
-        # Everything is fine
-        my $access_token  = $json->{access_token};
-        my $token_type    = $json->{token_type};
-        my $refresh_token = $json->{refresh_token};
-        my $expires_in    = $json->{"expires_in"} // $EXPECTED_EXPIRATION_IN;
-        my $auth          = $token_type . ' ' . $access_token;
-        # my $scope       = $json->{scope};
-
-        # Set session info
-        $c->session(auth => $auth);
-
-        # Expiration of the token minus tolerance
-        $c->session(auth_exp => time + $expires_in - 60);
-
-        # Set session info for refresh token
-        # This can be stored in the session, as it is useless
-        # unless the client secret is stolen
-        $c->session(auth_r => $refresh_token) if $refresh_token;
-
-        # Set stash info
-        $c->stash(auth => $auth);
-
-        return $promise->resolve;
-      }
-    );
-
-
-    # Refresh tokens and return a promise
-    $app->helper(
-      'auth.refresh_p' => sub {
-        my $c = shift;
-        my $refresh_token = shift;
-
-        # Get OAuth access token
-        state $r_url = Mojo::URL->new($c->korap->api)->path('oauth2/token');
-
-        $c->app->log->debug("Refresh at $r_url");
-
-        return $c->kalamar_ua->post_p($r_url, {} => form => {
-          grant_type => 'refresh_token',
-          client_id => $client_id,
-          client_secret => $client_secret,
-          refresh_token => $refresh_token
-        })->then(
-          sub {
-            my $tx = shift;
-            my $json = $tx->result->json;
-
-            # Response is fine
-            if ($tx->res->is_success) {
-
-              $c->app->log->info("Refresh was successful");
-
-              # Set the tokens and return a promise
-              return $c->auth->set_tokens_p($json);
-            };
-
-            # There is a client error - refresh fails
-            if ($tx->res->is_client_error && $json) {
-
-              $c->stash(auth => undef);
-              $c->stash(auth_exp => undef);
-              delete $c->session->{user};
-              delete $c->session->{auth};
-              delete $c->session->{auth_r};
-              delete $c->session->{auth_exp};
-
-              # Response is 400
-              return Mojo::Promise->reject(
-                $json->{error_description} // $c->loc('Auth_refreshFail')
-              );
-            };
-
-            if ($tx->res->is_server_error) {
-              return Mojo::Promise->reject(
-                '600'
-              )
-            };
-
-            $c->notify(error => $c->loc('Auth_responseError'));
-            return Mojo::Promise->reject;
-          }
-        )
-      }
-    );
-
-    # Get a list of registered clients
-    $app->helper(
-      'auth.client_list_p' => sub {
-        my $c = shift;
-
-        # Get list of registered clients
-        state $r_url = Mojo::URL->new($c->korap->api)->path('oauth2/client/list');
-
-        # Get the list of all clients
-        return $c->korap_request(post => $r_url, {} => form => {
-          super_client_id => $client_id,
-          super_client_secret => $client_secret,
-          authorized_only => 'no'
-        })->then(
-          sub {
-            my $tx = shift;
-            my $json = $tx->result->json;
-
-            # Response is fine
-            if ($tx->res->is_success) {
-              return Mojo::Promise->resolve($json);
-            };
-
-            $c->log->error($c->dumper($tx->res->to_string));
-
-            # Failure
-            $c->notify(error => $c->loc('Auth_responseError'));
-            return Mojo::Promise->reject($json // 'No response');
+      # There is an error here
+      # Dealing with errors here
+      if ($json->{error} && ref $json->{error} ne 'ARRAY') {
+        return $promise->reject(
+          {
+            message => $json->{error} . ($json->{error_description} ? ': ' . $json->{error_description} : '')
           }
         );
       }
-    );
 
-
-    # Get a list of registered clients
-    $app->helper(
-      'auth.token_list_p' => sub {
-        my $c = shift;
-        my $user_client_id = shift;
-
-        # Revoke the token
-        state $r_url = Mojo::URL->new($c->korap->api)->path('oauth2/token/list');
-
-        my $form = {
-          super_client_id => $client_id,
-          super_client_secret => $client_secret,
-          token_type => 'access_token',
-        };
-
-        if ($user_client_id) {
-          $form->{client_id} = $user_client_id;
-        };
-
-        # Get the list of all clients
-        return $c->korap_request(post => $r_url, {} => form => $form)->then(
-          sub {
-            my $tx = shift;
-            my $json = $tx->result->json;
-
-            # Response is fine
-            if ($tx->res->is_success) {
-              return Mojo::Promise->resolve($json);
+      # There is an array of errors
+      elsif (my $error = $json->{errors} // $json->{error}) {
+        if (ref $error eq 'ARRAY') {
+          my @errors = ();
+          foreach (@{$error}) {
+            if ($_->[1]) {
+              push @errors, { code => $_->[0], message => $_->[1]}
             };
-
-            $c->log->error($c->dumper($tx->res->to_string));
-
-            # Failure
-            $c->notify(error => $c->loc('Auth_responseError'));
-            return Mojo::Promise->reject($json // 'No response');
-          }
-        );
-      }
-    );
-
-
-    # Issue a korap request with "oauth"orization
-    # This will override the core request helper
-    $app->helper(
-      korap_request => sub {
-        my $c      = shift;
-        my $method = shift;
-        my $path   = shift;
-        my @param = @_;
-
-        # TODO:
-        #   Check if $tx is not leaked!
-
-        # Get plugin user agent
-        my $ua = $c->kalamar_ua;
-
-        my $url = Mojo::URL->new($path);
-        my $tx = $ua->build_tx(uc($method), $url->clone, @param);
-
-        # Set X-Forwarded for
-        $tx->req->headers->header(
-          'X-Forwarded-For' => $c->client_ip
-        );
-
-        # Emit Hook to alter request
-        $c->app->plugins->emit_hook(
-          before_korap_request => ($c, $tx)
-        );
-
-        my $h = $tx->req->headers;
-
-        # If the request already has an Authorization
-        # header, respect it!
-        if ($h->authorization) {
-          return $ua->start_p($tx);
+          };
+          return $promise->reject(@errors);
         };
 
-        # Get auth token
-        if (my $auth_token = $c->auth->token) {
+        return $promise->reject({message => $error});
+      };
 
-          # The token is already expired!
-          my $exp = $c->session('auth_exp');
-          if (defined $exp && $exp < time) {
+      # Everything is fine
+      my $access_token  = $json->{access_token};
+      my $token_type    = $json->{token_type};
+      my $refresh_token = $json->{refresh_token};
+      my $expires_in    = $json->{"expires_in"} // $EXPECTED_EXPIRATION_IN;
+      my $auth          = $token_type . ' ' . $access_token;
+      # my $scope       = $json->{scope};
 
-            # Remove auth ...
+      # Set session info
+      $c->session(auth => $auth);
+
+      # Expiration of the token minus tolerance
+      $c->session(auth_exp => time + $expires_in - 60);
+
+      # Set session info for refresh token
+      # This can be stored in the session, as it is useless
+      # unless the client secret is stolen
+      $c->session(auth_r => $refresh_token) if $refresh_token;
+
+      # Set stash info
+      $c->stash(auth => $auth);
+
+      return $promise->resolve;
+    }
+  );
+
+
+  # Refresh tokens and return a promise
+  $app->helper(
+    'auth.refresh_p' => sub {
+      my $c = shift;
+      my $refresh_token = shift;
+
+      # Get OAuth access token
+      state $r_url = Mojo::URL->new($c->korap->api)->path('oauth2/token');
+
+      $c->app->log->debug("Refresh at $r_url");
+
+      return $c->kalamar_ua->post_p($r_url, {} => form => {
+        grant_type => 'refresh_token',
+        client_id => $client_id,
+        client_secret => $client_secret,
+        refresh_token => $refresh_token
+      })->then(
+        sub {
+          my $tx = shift;
+          my $json = $tx->result->json;
+
+          # Response is fine
+          if ($tx->res->is_success) {
+
+            $c->app->log->info("Refresh was successful");
+
+            # Set the tokens and return a promise
+            return $c->auth->set_tokens_p($json);
+          };
+
+          # There is a client error - refresh fails
+          if ($tx->res->is_client_error && $json) {
+
             $c->stash(auth => undef);
+            $c->stash(auth_exp => undef);
+            delete $c->session->{user};
+            delete $c->session->{auth};
+            delete $c->session->{auth_r};
+            delete $c->session->{auth_exp};
 
-            # And get refresh token from session
-            if (my $refresh_token = $c->session('auth_r')) {
+            # Response is 400
+            return Mojo::Promise->reject(
+              $json->{error_description} // $c->loc('Auth_refreshFail')
+            );
+          };
 
-              $c->app->log->debug("Refresh is required");
+          if ($tx->res->is_server_error) {
+            return Mojo::Promise->reject(
+              '600'
+            )
+          };
 
-              # Refresh
-              return $c->auth->refresh_p($refresh_token)->then(
-                sub {
-                  $c->app->log->debug("Search with refreshed tokens");
+          $c->notify(error => $c->loc('Auth_responseError'));
+          return Mojo::Promise->reject;
+        }
+      )
+    }
+  );
 
-                  # Tokens were set - now send the request the first time!
-                  $tx->req->headers->authorization($c->stash('auth'));
-                  return $ua->start_p($tx);
-                }
-              );
-            }
+  # Get a list of registered clients
+  $app->helper(
+    'auth.client_list_p' => sub {
+      my $c = shift;
 
-            # The token is expired and no refresh token is
-            # available - issue an unauthorized request!
-            else {
+      # Get list of registered clients
+      state $r_url = Mojo::URL->new($c->korap->api)->path('oauth2/client/list');
 
-              $c->stash(auth => undef);
-              $c->stash(auth_exp => undef);
-              delete $c->session->{user};
-              delete $c->session->{auth};
-              delete $c->session->{auth_r};
-              delete $c->session->{auth_exp};
+      # Get the list of all clients
+      return $c->korap_request(post => $r_url, {} => form => {
+        super_client_id => $client_id,
+        super_client_secret => $client_secret,
+        authorized_only => 'no'
+      })->then(
+        sub {
+          my $tx = shift;
+          my $json = $tx->result->json;
 
-              # Warn on Error!
-              $c->notify(warn => $c->loc('Auth_tokenExpired'));
-              return $ua->start_p($tx);
-            };
+          # Response is fine
+          if ($tx->res->is_success) {
+            return Mojo::Promise->resolve($json);
+          };
+
+          $c->log->error($c->dumper($tx->res->to_string));
+
+          # Failure
+          $c->notify(error => $c->loc('Auth_responseError'));
+          return Mojo::Promise->reject($json // 'No response');
+        }
+      );
+    }
+  );
+
+
+  # Get a list of registered clients
+  $app->helper(
+    'auth.token_list_p' => sub {
+      my $c = shift;
+      my $user_client_id = shift;
+
+      # Revoke the token
+      state $r_url = Mojo::URL->new($c->korap->api)->path('oauth2/token/list');
+
+      my $form = {
+        super_client_id => $client_id,
+        super_client_secret => $client_secret,
+        token_type => 'access_token',
+      };
+
+      if ($user_client_id) {
+        $form->{client_id} = $user_client_id;
+      };
+
+      # Get the list of all clients
+      return $c->korap_request(post => $r_url, {} => form => $form)->then(
+        sub {
+          my $tx = shift;
+          my $json = $tx->result->json;
+
+          # Response is fine
+          if ($tx->res->is_success) {
+            return Mojo::Promise->resolve($json);
+          };
+
+          $c->log->error($c->dumper($tx->res->to_string));
+
+          # Failure
+          $c->notify(error => $c->loc('Auth_responseError'));
+          return Mojo::Promise->reject($json // 'No response');
+        }
+      );
+    }
+  );
+
+
+  # Issue a korap request with "oauth"orization
+  # This will override the core request helper
+  $app->helper(
+    korap_request => sub {
+      my $c      = shift;
+      my $method = shift;
+      my $path   = shift;
+      my @param = @_;
+
+      # TODO:
+      #   Check if $tx is not leaked!
+
+      # Get plugin user agent
+      my $ua = $c->kalamar_ua;
+
+      my $url = Mojo::URL->new($path);
+      my $tx = $ua->build_tx(uc($method), $url->clone, @param);
+
+      # Set X-Forwarded for
+      $tx->req->headers->header(
+        'X-Forwarded-For' => $c->client_ip
+      );
+
+      # Emit Hook to alter request
+      $c->app->plugins->emit_hook(
+        before_korap_request => ($c, $tx)
+      );
+
+      my $h = $tx->req->headers;
+
+      # If the request already has an Authorization
+      # header, respect it!
+      if ($h->authorization) {
+        return $ua->start_p($tx);
+      };
+
+      # Get auth token
+      if (my $auth_token = $c->auth->token) {
+
+        # The token is already expired!
+        my $exp = $c->session('auth_exp');
+        if (defined $exp && $exp < time) {
+
+          # Remove auth ...
+          $c->stash(auth => undef);
+
+          # And get refresh token from session
+          if (my $refresh_token = $c->session('auth_r')) {
+
+            $c->app->log->debug("Refresh is required");
+
+            # Refresh
+            return $c->auth->refresh_p($refresh_token)->then(
+              sub {
+                $c->app->log->debug("Search with refreshed tokens");
+
+                # Tokens were set - now send the request the first time!
+                $tx->req->headers->authorization($c->stash('auth'));
+                return $ua->start_p($tx);
+              }
+            );
           }
 
-          # Auth token is fine
+          # The token is expired and no refresh token is
+          # available - issue an unauthorized request!
           else {
 
-            # Set auth
-            $h->authorization($auth_token);
-          }
+            $c->stash(auth => undef);
+            $c->stash(auth_exp => undef);
+            delete $c->session->{user};
+            delete $c->session->{auth};
+            delete $c->session->{auth_r};
+            delete $c->session->{auth_exp};
+
+            # Warn on Error!
+            $c->notify(warn => $c->loc('Auth_tokenExpired'));
+            return $ua->start_p($tx);
+          };
         }
 
-        # No token set
+        # Auth token is fine
         else {
 
-          # Return unauthorized request
-          return $ua->start_p($tx);
-        };
+          # Set auth
+          $h->authorization($auth_token);
+        }
+      }
 
-        # Issue an authorized request and automatically
-        # refresh the token on expiration!
-        return $ua->start_p($tx)->then(
-          sub {
-            my $tx = shift;
+      # No token set
+      else {
 
-            # Response is fine
-            if ($tx->res->is_success) {
-              return Mojo::Promise->resolve($tx);
-            }
+        # Return unauthorized request
+        return $ua->start_p($tx);
+      };
 
-            # There is a client error - maybe refresh!
-            elsif ($tx->res->is_client_error) {
+      # Issue an authorized request and automatically
+      # refresh the token on expiration!
+      return $ua->start_p($tx)->then(
+        sub {
+          my $tx = shift;
 
-              # Check the error
-              my $json = $tx->res->json('/errors/0/1');
-              if ($json && ($json =~ /expired|invalid/)) {
-                $c->stash(auth => undef);
-                $c->stash(auth_exp => undef);
-                delete $c->session->{user};
-                delete $c->session->{auth};
+          # Response is fine
+          if ($tx->res->is_success) {
+            return Mojo::Promise->resolve($tx);
+          }
 
-                # And get refresh token from session
-                if (my $refresh_token = $c->session('auth_r')) {
+          # There is a client error - maybe refresh!
+          elsif ($tx->res->is_client_error) {
 
-                  # Refresh
-                  return $c->auth->refresh_p($refresh_token)->then(
-                    sub {
-                      $c->app->log->debug("Search with refreshed tokens");
+            # Check the error
+            my $json = $tx->res->json('/errors/0/1');
+            if ($json && ($json =~ /expired|invalid/)) {
+              $c->stash(auth => undef);
+              $c->stash(auth_exp => undef);
+              delete $c->session->{user};
+              delete $c->session->{auth};
 
-                      my $tx = $ua->build_tx(uc($method), $url->clone, @param);
+              # And get refresh token from session
+              if (my $refresh_token = $c->session('auth_r')) {
 
-                      # Set X-Forwarded for
-                      $tx->req->headers->header(
-                        'X-Forwarded-For' => $c->client_ip
-                      );
+                # Refresh
+                return $c->auth->refresh_p($refresh_token)->then(
+                  sub {
+                    $c->app->log->debug("Search with refreshed tokens");
 
-                      # Tokens were set - now send the request the first time!
-                      $tx->req->headers->authorization($c->stash('auth'));
-                      return $ua->start_p($tx);
-                    }
-                  )
-                };
+                    my $tx = $ua->build_tx(uc($method), $url->clone, @param);
 
-                # Reject the invalid token
-                $c->notify(error => $c->loc('Auth_tokenInvalid'));
-                return Mojo::Promise->reject;
+                    # Set X-Forwarded for
+                    $tx->req->headers->header(
+                      'X-Forwarded-For' => $c->client_ip
+                    );
+
+                    # Tokens were set - now send the request the first time!
+                    $tx->req->headers->authorization($c->stash('auth'));
+                    return $ua->start_p($tx);
+                  }
+                )
               };
 
-              return Mojo::Promise->resolve($tx);
-            }
-
-            # There is a server error - just report
-            elsif ($tx->res->is_server_error) {
-              my $err = $tx->res->error;
-              if ($err) {
-                return Mojo::Promise->reject($err->{code} . ': ' . $err->{message});
-              }
-              else {
-                $c->notify(error => $c->loc('Auth_serverError'));
-                return Mojo::Promise->reject;
-              };
+              # Reject the invalid token
+              $c->notify(error => $c->loc('Auth_tokenInvalid'));
+              return Mojo::Promise->reject;
             };
 
-            $c->notify(error => $c->loc('Auth_responseError'));
-            return Mojo::Promise->reject;
+            return Mojo::Promise->resolve($tx);
           }
-        );
-      }
-    );
 
-    # Password flow for OAuth
-    $r->post('/user/login')->to(
+          # There is a server error - just report
+          elsif ($tx->res->is_server_error) {
+            my $err = $tx->res->error;
+            if ($err) {
+              return Mojo::Promise->reject($err->{code} . ': ' . $err->{message});
+            }
+            else {
+              $c->notify(error => $c->loc('Auth_serverError'));
+              return Mojo::Promise->reject;
+            };
+          };
+
+          $c->notify(error => $c->loc('Auth_responseError'));
+          return Mojo::Promise->reject;
+        }
+      );
+    }
+  );
+
+  # Password flow for OAuth
+  $r->post('/user/login')->to(
+    cb => sub {
+      my $c = shift;
+
+      # Validate input
+      my $v = $c->validation;
+      $v->required('handle_or_email', 'trim');
+      $v->required('pwd', 'trim');
+      $v->csrf_protect;
+      $v->optional('fwd')->closed_redirect;
+
+      my $user = check_decode($v->param('handle_or_email'));
+      unless ($user) {
+        $c->notify(error => $c->loc('Auth_invalidChar'));
+        $c->param(handle_or_email => '');
+        return $c->relative_redirect_to('index');
+      };
+
+      my $fwd = $v->param('fwd');
+
+      # Set flash for redirect
+      $c->flash(handle_or_email => $user);
+
+      if ($v->has_error || index($user, ':') >= 0) {
+        if ($v->has_error('fwd')) {
+          $c->notify(error => $c->loc('Auth_openRedirectFail'));
+        }
+        elsif ($v->has_error('csrf_token')) {
+          $c->notify(error => $c->loc('Auth_csrfFail'));
+        }
+        else {
+          $c->notify(error => $c->loc('Auth_loginFail'));
+        };
+
+        return $c->relative_redirect_to($fwd // 'index');
+      }
+
+      my $pwd = $v->param('pwd');
+
+      $c->app->log->debug("Login from user $user");
+
+      # <specific>
+
+      # Get OAuth access token
+      my $url = Mojo::URL->new($c->korap->api)->path('oauth2/token');
+
+      # Korap request for login
+      $c->korap_request('post', $url, {}, form => {
+        grant_type => 'password',
+        username => $user,
+        password => $pwd,
+        client_id => $client_id,
+        client_secret => $client_secret
+      })->then(
+        sub {
+          # Set the tokens and return a promise
+          return $c->auth->set_tokens_p(shift->result->json)
+        }
+      )->then(
+        sub {
+          # Set user info
+          $c->session(user => $user);
+          $c->stash(user => $user);
+
+          # Notify on success
+          $c->app->log->debug(qq!Login successful: "$user"!);
+          $c->notify(success => $c->loc('Auth_loginSuccess'));
+        }
+      )->catch(
+        sub {
+
+          # Notify the user on login failure
+          unless (@_) {
+            $c->notify(error => $c->loc('Auth_loginFail'));
+          }
+
+          # There are known errors
+          foreach (@_) {
+            if (ref $_ eq 'HASH') {
+              my $err = ($_->{code} ? $_->{code} . ': ' : '') .
+                $_->{message};
+              $c->notify(error => $err);
+              # Log failure
+              $c->app->log->debug($err);
+            }
+            else {
+              $c->notify(error => $_);
+              $c->app->log->debug($_);
+            };
+          };
+
+          $c->app->log->debug(qq!Login fail: "$user"!);
+        }
+      )->finally(
+        sub {
+          # Redirect to slash
+          return $c->relative_redirect_to($fwd // 'index');
+        }
+      )
+
+      # Start IOLoop
+      ->wait;
+
+      return 1;
+    }
+  )->name('login');
+
+
+  # Log out of the session
+  $r->get('/user/logout')->to(
+    cb => sub {
+      my $c = shift;
+
+      # TODO: csrf-protection!
+
+      my $refresh_token = $c->session('auth_r');
+
+      # Revoke the token
+      state $url = Mojo::URL->new($c->korap->api)->path('oauth2/revoke');
+
+      $c->kalamar_ua->post_p($url => {} => form => {
+        client_id => $client_id,
+        client_secret => $client_secret,
+        token => $refresh_token,
+        token_type => 'refresh_token'
+      })->then(
+        sub {
+          my $tx = shift;
+          my $json = $tx->result->json;
+
+          my $promise;
+
+          # Response is fine
+          if ($tx->res->is_success) {
+            $c->app->log->info("Revocation was successful");
+            $c->notify(success => $c->loc('Auth_logoutSuccess'));
+
+            $c->stash(auth => undef);
+            $c->stash(auth_exp => undef);
+            $c->flash(handle_or_email => delete $c->session->{user});
+            delete $c->session->{auth};
+            delete $c->session->{auth_r};
+            delete $c->session->{auth_exp};
+            return Mojo::Promise->resolve;
+          };
+
+          # Token may be invalid
+          $c->notify('error', $c->loc('Auth_logoutFail'));
+
+          # There is a client error - refresh fails
+          if ($tx->res->is_client_error && $json) {
+
+            return Mojo::Promise->reject(
+              $json->{error_description}
+            );
+          };
+
+          # Resource may not be found (404)
+          return Mojo::Promise->reject
+
+        }
+      )->catch(
+        sub {
+          my $err = shift;
+
+          # Server may be irresponsible
+          $c->notify('error', $c->loc('Auth_logoutFail'));
+          return Mojo::Promise->reject($err);
+        }
+      )->finally(
+        sub {
+          return $c->redirect_to('index');
+        }
+      )->wait;
+    }
+  )->name('logout');
+
+
+  # If "experimental_registration" is set, open
+  # OAuth registration dialogues.
+  if ($param->{experimental_client_registration}) {
+
+    # Add settings
+    $app->navi->add(settings => (
+      $app->loc('Auth_oauthSettings'), 'oauth'
+    ));
+
+    # Route to oauth settings
+    $r->get('/settings/oauth')->to(
       cb => sub {
         my $c = shift;
 
-        # Validate input
-        my $v = $c->validation;
-        $v->required('handle_or_email', 'trim');
-        $v->required('pwd', 'trim');
-        $v->csrf_protect;
-        $v->optional('fwd')->closed_redirect;
+        _set_no_cache($c->res->headers);
 
-        my $user = check_decode($v->param('handle_or_email'));
-        unless ($user) {
-          $c->notify(error => $c->loc('Auth_invalidChar'));
-          $c->param(handle_or_email => '');
-          return $c->relative_redirect_to('index');
+        unless ($c->auth->token) {
+          return $c->render(
+            template => 'exception',
+            msg => $c->loc('Auth_authenticationFail'),
+            status => 401
+          );
         };
 
-        my $fwd = $v->param('fwd');
+        # Wait for async result
+        $c->render_later;
 
-        # Set flash for redirect
-        $c->flash(handle_or_email => $user);
-
-        if ($v->has_error || index($user, ':') >= 0) {
-          if ($v->has_error('fwd')) {
-            $c->notify(error => $c->loc('Auth_openRedirectFail'));
+        $c->auth->client_list_p->then(
+          sub {
+            $c->stash('client_list' => shift);
           }
-          elsif ($v->has_error('csrf_token')) {
+        )->catch(
+          sub {
+            return;
+          }
+        )->finally(
+          sub {
+            return $c->render(template => 'auth/clients')
+          }
+        );
+      }
+    )->name('oauth-settings');
+
+    # Route to oauth client registration
+    $r->post('/settings/oauth/register')->to(
+      cb => sub {
+        my $c = shift;
+
+        _set_no_cache($c->res->headers);
+
+        my $v = $c->validation;
+
+        unless ($c->auth->token) {
+          return $c->render(
+            content => 'Unauthorized',
+            status => 401
+          );
+        };
+
+        $v->csrf_protect;
+        $v->required('name', 'trim', 'not_empty')->size(3, 255);
+        $v->required('type')->in('PUBLIC', 'CONFIDENTIAL');
+        $v->required('desc', 'trim', 'not_empty')->size(3, 255);
+        $v->optional('url', 'trim', 'not_empty')->like(qr/^(http|$)/i);
+        $v->optional('redirect_uri', 'trim', 'not_empty')->like(qr/^(http|$)/i);
+        $v->optional('src', 'not_empty');
+
+        $c->stash(template => 'auth/clients');
+
+        # Render with error
+        if ($v->has_error) {
+          if ($v->has_error('csrf_token')) {
             $c->notify(error => $c->loc('Auth_csrfFail'));
           }
           else {
-            $c->notify(error => $c->loc('Auth_loginFail'));
+            $c->notify(error => $c->loc('Auth_paramError'));
           };
+          return $c->render;
+        } elsif ($c->req->is_limit_exceeded) {
+          $c->notify(error => $c->loc('Auth_fileSizeExceeded'));
+          return $c->render;
+        };
 
-          return $c->relative_redirect_to($fwd // 'index');
-        }
+        my $type = $v->param('type');
+        my $src = $v->param('src');
+        my $src_json;
 
-        my $pwd = $v->param('pwd');
+        my $json_obj = {
+          name         => $v->param('name'),
+          type         => $type,
+          description  => $v->param('desc'),
+          url          => $v->param('url'),
+          redirect_uri => $v->param('redirect_uri')
+        };
 
-        $c->app->log->debug("Login from user $user");
+        # Check plugin source
+        if ($src) {
 
-        # <specific>
-
-        # Get OAuth access token
-        my $url = Mojo::URL->new($c->korap->api)->path('oauth2/token');
-
-        # Korap request for login
-        $c->korap_request('post', $url, {}, form => {
-          grant_type => 'password',
-          username => $user,
-          password => $pwd,
-          client_id => $client_id,
-          client_secret => $client_secret
-        })->then(
-          sub {
-            # Set the tokens and return a promise
-            return $c->auth->set_tokens_p(shift->result->json)
-          }
-        )->then(
-          sub {
-            # Set user info
-            $c->session(user => $user);
-            $c->stash(user => $user);
-
-            # Notify on success
-            $c->app->log->debug(qq!Login successful: "$user"!);
-            $c->notify(success => $c->loc('Auth_loginSuccess'));
-          }
-        )->catch(
-          sub {
-
-            # Notify the user on login failure
-            unless (@_) {
-              $c->notify(error => $c->loc('Auth_loginFail'));
-            }
-
-            # There are known errors
-            foreach (@_) {
-              if (ref $_ eq 'HASH') {
-                my $err = ($_->{code} ? $_->{code} . ': ' : '') .
-                  $_->{message};
-                $c->notify(error => $err);
-                # Log failure
-                $c->app->log->debug($err);
-              }
-              else {
-                $c->notify(error => $_);
-                $c->app->log->debug($_);
-              };
-            };
-
-            $c->app->log->debug(qq!Login fail: "$user"!);
-          }
-        )->finally(
-          sub {
-            # Redirect to slash
-            return $c->relative_redirect_to($fwd // 'index');
-          }
-        )
-
-        # Start IOLoop
-        ->wait;
-
-        return 1;
-      }
-    )->name('login');
-
-
-    # Log out of the session
-    $r->get('/user/logout')->to(
-      cb => sub {
-        my $c = shift;
-
-        # TODO: csrf-protection!
-
-        my $refresh_token = $c->session('auth_r');
-
-        # Revoke the token
-        state $url = Mojo::URL->new($c->korap->api)->path('oauth2/revoke');
-
-        $c->kalamar_ua->post_p($url => {} => form => {
-          client_id => $client_id,
-          client_secret => $client_secret,
-          token => $refresh_token,
-          token_type => 'refresh_token'
-        })->then(
-          sub {
-            my $tx = shift;
-            my $json = $tx->result->json;
-
-            my $promise;
-
-            # Response is fine
-            if ($tx->res->is_success) {
-              $c->app->log->info("Revocation was successful");
-              $c->notify(success => $c->loc('Auth_logoutSuccess'));
-
-              $c->stash(auth => undef);
-              $c->stash(auth_exp => undef);
-              $c->flash(handle_or_email => delete $c->session->{user});
-              delete $c->session->{auth};
-              delete $c->session->{auth_r};
-              delete $c->session->{auth_exp};
-              return Mojo::Promise->resolve;
-            }
-
-            # Token may be invalid
-            $c->notify('error', $c->loc('Auth_logoutFail'));
-
-            # There is a client error - refresh fails
-            if ($tx->res->is_client_error && $json) {
-
-              return Mojo::Promise->reject(
-                $json->{error_description}
-              );
-            };
-
-            # Resource may not be found (404)
-            return Mojo::Promise->reject
-
-          }
-        )->catch(
-          sub {
-            my $err = shift;
-
-            # Server may be irresponsible
-            $c->notify('error', $c->loc('Auth_logoutFail'));
-            return Mojo::Promise->reject($err);
-          }
-        )->finally(
-          sub {
-            return $c->redirect_to('index');
-          }
-        )->wait;
-      }
-    )->name('logout');
-
-
-    # If "experimental_registration" is set, open
-    # OAuth registration dialogues.
-    if ($param->{experimental_client_registration}) {
-
-      # Add settings
-      $app->navi->add(settings => (
-        $app->loc('Auth_oauthSettings'), 'oauth'
-      ));
-
-      # Route to oauth settings
-      $r->get('/settings/oauth')->to(
-        cb => sub {
-          my $c = shift;
-
-          _set_no_cache($c->res->headers);
-
-          unless ($c->auth->token) {
-            return $c->render(
-              template => 'exception',
-              msg => $c->loc('Auth_authenticationFail'),
-              status => 401
-            );
-          };
-
-          # Wait for async result
-          $c->render_later;
-
-          $c->auth->client_list_p->then(
-            sub {
-              $c->stash('client_list' => shift);
-            }
-          )->catch(
-            sub {
-              return;
-            }
-          )->finally(
-            sub {
-              return $c->render(template => 'auth/clients')
-            }
-          );
-        }
-      )->name('oauth-settings');
-
-      # Route to oauth client registration
-      $r->post('/settings/oauth/register')->to(
-        cb => sub {
-          my $c = shift;
-
-          _set_no_cache($c->res->headers);
-
-          my $v = $c->validation;
-
-          unless ($c->auth->token) {
-            return $c->render(
-              content => 'Unauthorized',
-              status => 401
-            );
-          };
-
-          $v->csrf_protect;
-          $v->required('name', 'trim', 'not_empty')->size(3, 255);
-          $v->required('type')->in('PUBLIC', 'CONFIDENTIAL');
-          $v->required('desc', 'trim', 'not_empty')->size(3, 255);
-          $v->optional('url', 'trim', 'not_empty')->like(qr/^(http|$)/i);
-          $v->optional('redirect_uri', 'trim', 'not_empty')->like(qr/^(http|$)/i);
-          $v->optional('src', 'not_empty');
-
-          $c->stash(template => 'auth/clients');
-
-          # Render with error
-          if ($v->has_error) {
-            if ($v->has_error('csrf_token')) {
-              $c->notify(error => $c->loc('Auth_csrfFail'));
-            }
-            else {
-              $c->notify(error => $c->loc('Auth_paramError'));
-            };
+          # Plugins need to be confidential
+          if ($type ne 'CONFIDENTIAL') {
+            $c->notify(error => $c->loc('Auth_confidentialRequired'));
             return $c->render;
-          } elsif ($c->req->is_limit_exceeded) {
+          }
+
+          # Source need to be a file upload
+          elsif (!ref $src || !$src->isa('Mojo::Upload')) {
+            $c->notify(error => $c->loc('Auth_jsonRequired'));
+            return $c->render;
+          };
+
+          # Uploads can't be too large
+          if ($src->size > 1_000_000) {
             $c->notify(error => $c->loc('Auth_fileSizeExceeded'));
             return $c->render;
           };
 
-          my $type = $v->param('type');
-          my $src = $v->param('src');
-          my $src_json;
+          # Check upload is not empty
+          if ($src->size > 0 && $src->filename ne '') {
 
-          my $json_obj = {
-            name         => $v->param('name'),
-            type         => $type,
-            description  => $v->param('desc'),
-            url          => $v->param('url'),
-            redirect_uri => $v->param('redirect_uri')
-          };
+            my $asset = $src->asset;
 
-          # Check plugin source
-          if ($src) {
+            # Check for json
+            eval {
+              $src_json = decode_json($asset->slurp);
+            };
 
-            # Plugins need to be confidential
-            if ($type ne 'CONFIDENTIAL') {
-              $c->notify(error => $c->loc('Auth_confidentialRequired'));
-              return $c->render;
-            }
-
-            # Source need to be a file upload
-            elsif (!ref $src || !$src->isa('Mojo::Upload')) {
+            if ($@ || !ref $src_json || ref $src_json ne 'HASH') {
               $c->notify(error => $c->loc('Auth_jsonRequired'));
               return $c->render;
             };
 
-            # Uploads can't be too large
-            if ($src->size > 1_000_000) {
-              $c->notify(error => $c->loc('Auth_fileSizeExceeded'));
-              return $c->render;
-            };
-
-            # Check upload is not empty
-            if ($src->size > 0 && $src->filename ne '') {
-
-              my $asset = $src->asset;
-
-              # Check for json
-              eval {
-                $src_json = decode_json($asset->slurp);
-              };
-
-              if ($@ || !ref $src_json || ref $src_json ne 'HASH') {
-                $c->notify(error => $c->loc('Auth_jsonRequired'));
-                return $c->render;
-              };
-
-              $json_obj->{source} = $src_json;
-            };
+            $json_obj->{source} = $src_json;
           };
-
-          # Wait for async result
-          $c->render_later;
-
-          # Register on server
-          state $url = Mojo::URL->new($c->korap->api)->path('oauth2/client/register');
-          $c->korap_request('POST', $url => {} => json => $json_obj)->then(
-            sub {
-              my $tx = shift;
-              my $result = $tx->result;
-
-              if ($result->is_error) {
-                my $json = $result->json;
-                if ($json && $json->{error}) {
-                  $c->notify(
-                    error => $json->{error} .
-                      ($json->{error_description} ? ': ' . $json->{error_description} : '')
-                  )
-                };
-
-                return Mojo::Promise->reject;
-              };
-
-              my $json = $result->json;
-
-              my $client_id = $json->{client_id};
-              my $client_secret = $json->{client_secret};
-
-              $c->stash('client_name' => $v->param('name'));
-              $c->stash('client_desc' => $v->param('desc'));
-              $c->stash('client_type' => $v->param('type'));
-              $c->stash('client_url'  => $v->param('url'));
-              $c->stash('client_src'  => $v->param('source'));
-              $c->stash('client_redirect_uri' => $v->param('redirect_uri'));
-              $c->stash('client_id' => $client_id);
-
-              if ($client_secret) {
-                $c->stash('client_secret' => $client_secret);
-              };
-
-              $c->notify(success => $c->loc('Auth_en_registerSuccess'));
-
-              return $c->render(template => 'auth/client');
-            }
-          )->catch(
-            sub {
-              $c->notify('error' => $c->loc('Auth_en_registerFail'));
-            }
-          )->finally(
-            sub {
-              return $c->redirect_to('settings' => { scope => 'oauth' });
-            }
-          );
-        }
-      )->name('oauth-register');
-
-
-      # Unregister client page
-      $r->get('/settings/oauth/:client_id/unregister')->to(
-        cb => sub {
-          my $c = shift;
-          _set_no_cache($c->res->headers);
-          $c->render(template => 'auth/unregister');
-        }
-      )->name('oauth-unregister');
-
-
-      # Unregister client
-      $r->post('/settings/oauth/:client_id/unregister')->to(
-        cb => sub {
-          my $c = shift;
-          _set_no_cache($c->res->headers);
-
-          my $v = $c->validation;
-
-          unless ($c->auth->token) {
-            return $c->render(
-              content => 'Unauthorized',
-              status => 401
-            );
-          };
-
-          $v->csrf_protect;
-          $v->required('client-name', 'trim')->size(3, 255);
-
-          # Render with error
-          if ($v->has_error) {
-            if ($v->has_error('csrf_token')) {
-              $c->notify(error => $c->loc('Auth_csrfFail'));
-            }
-            else {
-              $c->notify(error => $c->loc('Auth_paramError'));
-            };
-            return $c->redirect_to('oauth-settings');
-          };
-
-          my $client_id =     $c->stash('client_id');
-          my $client_name =   $v->param('client-name');
-          my $client_secret = $v->param('client-secret');
-
-          # Get list of registered clients
-          my $r_url = Mojo::URL->new($c->korap->api)->path('oauth2/client/deregister/')->path(
-            $client_id
-          );
-
-          my $send = {};
-
-          if ($client_secret) {
-            $send->{client_secret} = $client_secret;
-          };
-
-          # Get the list of all clients
-          return $c->korap_request(delete => $r_url, {} => form => $send)->then(
-            sub {
-              my $tx = shift;
-
-              # Response is fine
-              if ($tx->res->is_success) {
-                # Okay
-                $c->notify(success => 'Successfully deleted ' . $client_name);
-              }
-              else {
-
-                # Failure
-                my $json = $tx->result->json;
-                if ($json && $json->{error_description}) {
-                  $c->notify(error => $json->{error_description});
-                } else {
-                  $c->notify(error => $c->loc('Auth_responseError'));
-                };
-              };
-
-              return $c->redirect_to('oauth-settings');
-            }
-          );
-        }
-      )->name('oauth-unregister-post');
-
-
-      # OAuth Client authorization
-      $r->get('/settings/oauth/authorize')->to(
-        cb => sub {
-          my $c = shift;
-
-          _set_no_cache($c->res->headers);
-
-          my $v = $c->validation;
-          $v->required('client_id');
-          $v->optional('scope');
-          $v->optional('state');
-          $v->optional('redirect_uri');
-
-          # Redirect with error
-          if ($v->has_error) {
-            $c->notify(error => $c->loc('Auth_paramError'));
-            return $c->redirect_to;
-          };
-
-          foreach (qw!scope client_id state redirect_uri!) {
-            $c->stash($_, $v->param($_));
-          };
-
-          # Wait for async result
-          $c->render_later;
-
-          my $client_id = $v->param('client_id');
-
-          my $client_information = $c->auth->client_list_p->then(
-            sub {
-              my $clients = shift;
-              foreach (@$clients) {
-                if ($_->{client_id} eq $client_id) {
-                  $c->stash(client_name => $_->{'client_name'});
-                  $c->stash(client_type => $_->{'client_type'});
-                  $c->stash(client_desc => $_->{'client_description'});
-                  $c->stash(client_url => $_->{'client_url'});
-                  $c->stash(redirect_uri_server => $_->{'client_redirect_uri'});
-                  last;
-                };
-              };
-            }
-          )->catch(
-            sub {
-              $c->stash(client_type => 'PUBLIC');
-              $c->stash(client_name => $v->param('client_id'));
-              return;
-            }
-          )->finally(
-            sub {
-
-              # Get auth token
-              my $auth_token = $c->auth->token;
-
-              # User is not logged in - log in before!
-              unless ($auth_token) {
-                return $c->render(template => 'auth/login');
-              };
-
-              # Grant authorization
-              return $c->render(template => 'auth/grant_scope');
-            }
-          );
-        }
-      )->name('oauth-grant-scope');
-
-
-      # OAuth Client authorization
-      # This will return a location information including some info
-      $r->post('/settings/oauth/authorize')->to(
-        cb => sub {
-          my $c = shift;
-
-          _set_no_cache($c->res->headers);
-
-          # It's necessary that it's clear this was triggered by
-          # KorAP and not by the client!
-          my $v = $c->validation;
-          $v->csrf_protect;
-          $v->required('client_id');
-          $v->optional('scope');
-          $v->optional('state');
-          $v->optional('redirect_uri');
-
-          # WARN! SIGN THIS TO PREVENT OPEN REDIRECT ATTACKS!
-          $v->required('redirect_uri_server');
-
-          # Render with error
-          if ($v->has_error) {
-            my $url = Mojo::URL->new($v->param('redirect_uri_server') // $c->url_for('index'));
-
-            if ($v->has_error('csrf_token')) {
-              $url->query([error_description => $c->loc('Auth_csrfFail')]);
-            }
-            else {
-              $url->query([error_description => $c->loc('Auth_paramError')]);
-            };
-
-            return $c->redirect_to($url);
-          };
-
-          state $r_url = Mojo::URL->new($c->korap->api)->path('oauth2/authorize');
-          $c->stash(redirect_uri_server => Mojo::URL->new($v->param('redirect_uri_server')));
-
-          return $c->korap_request(post => $r_url, {} => form => {
-            response_type => 'code',
-            client_id => $v->param('client_id'),
-            redirect_uri => $v->param('redirect_uri'),
-            state => $v->param('state'),
-            scope => $v->param('scope'),
-          })->then(
-            sub {
-              my $tx = shift;
-
-              # Check for location header with code in redirects
-              my $loc;
-              foreach (@{$tx->redirects}) {
-                $loc = $_->res->headers->header('Location');
-
-                my $url = Mojo::URL->new($loc);
-
-                if ($url->query->param('code')) {
-                  last;
-                } elsif (my $err = $url->query->param('error_description')) {
-                  return Mojo::Promise->reject($err);
-                }
-              };
-
-              return Mojo::Promise->resolve($loc) if $loc;
-
-              # Failed redirect, but location set
-              if ($tx->res->headers->location) {
-                my $url = Mojo::URL->new($tx->res->headers->location);
-                if (my $err = $url->query->param('error_description'))  {
-                  return Mojo::Promise->reject($err);
-                };
-              };
-
-              # No location code
-              return Mojo::Promise->reject('no location response');
-            }
-          )->catch(
-            sub {
-              my $err_msg = shift;
-              my $url = $c->stash('redirect_uri_server');
-              if ($err_msg) {
-                $url = $url->query([error_description => $err_msg]);
-              };
-              return Mojo::Promise->resolve($url);
-            }
-          )->then(
-            sub {
-              my $loc = shift;
-              return $c->redirect_to($loc);
-            }
-          )->wait;
-          return $c->rendered;
-        }
-      )->name('oauth-grant-scope-post');
-
-
-      # Show information of a client
-      $r->get('/settings/oauth/:client_id')->to(
-        cb => sub {
-          my $c = shift;
-
-          _set_no_cache($c->res->headers);
-
-          $c->render_later;
-
-          $c->auth->client_list_p->then(
-            sub {
-              my $json = shift;
-
-              my ($item) = grep {
-                $c->stash('client_id') eq $_->{client_id}
-              } @$json;
-
-              unless ($item) {
-                return Mojo::Promise->reject;
-              };
-
-              $c->stash(client_name => $item->{client_name});
-              $c->stash(client_desc => $item->{client_description});
-              $c->stash(client_url  => $item->{client_url});
-              $c->stash(client_type => ($item->{client_type} // 'PUBLIC'));
-              $c->stash(client_src  => encode_json($item->{source})) if $item->{source};
-
-              $c->auth->token_list_p($c->stash('client_id'));
-            }
-          )->then(
-            sub {
-              my $json = shift;
-
-              $c->stash(tokens => $json);
-
-              return Mojo::Promise->resolve;
-            }
-          )->catch(
-            sub {
-              return $c->reply->not_found;
-            }
-          )->finally(
-            sub {
-              return $c->render(template => 'auth/client')
-            }
-          );
-
-          return;
-        }
-      )->name('oauth-tokens');
-    };
-
-
-    # Ask if new token should be issued
-    $r->get('/settings/oauth/:client_id/token')->to(
-      cb => sub {
-        my $c = shift;
-        _set_no_cache($c->res->headers);
-        $c->render(template => 'auth/issue-token');
-      }
-    )->name('oauth-issue-token');
-
-
-    # Ask if a token should be revoked
-    $r->post('/settings/oauth/:client_id/token/revoke')->to(
-      cb => sub {
-        shift->render(template => 'auth/revoke-token');
-      }
-    )->name('oauth-revoke-token');
-
-
-    # Issue new token
-    $r->post('/settings/oauth/:client_id/token')->to(
-      cb => sub {
-        my $c = shift;
-        _set_no_cache($c->res->headers);
-
-        my $v = $c->validation;
-
-        unless ($c->auth->token) {
-          return $c->render(
-            content => 'Unauthorized',
-            status => 401
-          );
         };
 
-        $v->csrf_protect;
-        $v->optional('client-secret');
-        $v->required('name', 'trim');
+        # Wait for async result
+        $c->render_later;
 
-        # Render with error
-        if ($v->has_error) {
-          if ($v->has_error('csrf_token')) {
-            $c->notify(error => $c->loc('Auth_csrfFail'));
-          }
-          else {
-            $c->notify(error => $c->loc('Auth_paramError'));
-          };
-          return $c->redirect_to('oauth-settings')
-        };
-
-        # Get authorization token
-        state $r_url = Mojo::URL->new($c->korap->api)->path('oauth2/authorize');
-        my $client_id = $c->stash('client_id');
-        my $name = $v->param('name');
-        my $redirect_url = $c->url_for->query({name => $name});
-
-        return $c->korap_request(post => $r_url, {} => form => {
-          response_type => 'code',
-          client_id => $client_id,
-          redirect_uri => $redirect_url,
-          # TODO: State
-        })->then(
+        # Register on server
+        state $url = Mojo::URL->new($c->korap->api)->path('oauth2/client/register');
+        $c->korap_request('POST', $url => {} => json => $json_obj)->then(
           sub {
             my $tx = shift;
+            my $result = $tx->result;
 
-            # Strip the token from the location header of the fake redirect
-            # TODO: Alternatively redirect!
-            my ($code, $scope, $loc, $name);
-            foreach (@{$tx->redirects}) {
-              $loc = $_->res->headers->header('Location');
-              if (index($loc, 'code') > 0) {
-                my $q = Mojo::URL->new($loc)->query;
-                $code  = $q->param('code');
-                $scope = $q->param('scope');
-                $name  = $q->param('name');
-                last;
+            if ($result->is_error) {
+              my $json = $result->json;
+              if ($json && $json->{error}) {
+                $c->notify(
+                  error => $json->{error} .
+                    ($json->{error_description} ? ': ' . $json->{error_description} : '')
+                  )
               };
+
+              return Mojo::Promise->reject;
             };
 
-            # Fine!
-            if ($code) {
-              return Mojo::Promise->resolve(
-                $client_id,
-                $redirect_url,
-                $code,
-                $scope,
-                $name
-              );
+            my $json = $result->json;
+
+            my $client_id = $json->{client_id};
+            my $client_secret = $json->{client_secret};
+
+            $c->stash('client_name' => $v->param('name'));
+            $c->stash('client_desc' => $v->param('desc'));
+            $c->stash('client_type' => $v->param('type'));
+            $c->stash('client_url'  => $v->param('url'));
+            $c->stash('client_src'  => $v->param('source'));
+            $c->stash('client_redirect_uri' => $v->param('redirect_uri'));
+            $c->stash('client_id' => $client_id);
+
+            if ($client_secret) {
+              $c->stash('client_secret' => $client_secret);
             };
-            return Mojo::Promise->reject;
-          }
-        )->then(
-          sub {
-            my ($client_id, $redirect_url, $code, $scope, $name) = @_;
 
-            # Get OAuth access token
-            state $r_url = Mojo::URL->new($c->korap->api)->path('oauth2/token');
-            return $c->kalamar_ua->post_p($r_url, {} => form => {
-              client_id => $client_id,
-              # NO CLIENT_SECRET YET SUPPORTED
-              grant_type => 'authorization_code',
-              code => $code,
-              redirect_uri => $redirect_url
-            })->then(
-              sub {
-                my $tx = shift;
-                my $json = $tx->res->json;
+            $c->notify(success => $c->loc('Auth_en_registerSuccess'));
 
-                if ($tx->res->is_error) {
-                  $c->notify(error => 'Unable to fetch new token');
-                  return Mojo::Promise->reject;
-                };
-
-                $c->notify(success => 'New access token created');
-
-                $c->redirect_to('oauth-tokens' => { client_id => $client_id })
-              }
-            )->catch(
-              sub {
-                my $err_msg = shift;
-
-                # Only raised in case of connection errors
-                if ($err_msg) {
-                  $c->notify(error => { src => 'Backend' } => $err_msg)
-                };
-
-                $c->render(
-                  status => 400,
-                  template => 'failure'
-                );
-              }
-            )
-
-            # Start IOLoop
-            ->wait;
-
+            return $c->render(template => 'auth/client');
           }
         )->catch(
           sub {
-            my $err_msg = shift;
-
-            # Only raised in case of connection errors
-            if ($err_msg) {
-              $c->notify(error => { src => 'Backend' } => $err_msg)
-            };
-
-            return $c->render(
-              status => 400,
-              template => 'failure'
-            );
+            $c->notify('error' => $c->loc('Auth_en_registerFail'));
           }
-        )
-
-        # Start IOLoop
-        ->wait;
-
-        return 1;
+        )->finally(
+          sub {
+            return $c->redirect_to('settings' => { scope => 'oauth' });
+          }
+        );
       }
-    )->name('oauth-issue-token-post');
+    )->name('oauth-register');
 
 
-    # Revoke token
-    $r->delete('/settings/oauth/:client_id/token')->to(
+    # Unregister client page
+    $r->get('/settings/oauth/:client_id/unregister')->to(
       cb => sub {
         my $c = shift;
+        _set_no_cache($c->res->headers);
+        $c->render(template => 'auth/unregister');
+      }
+    )->name('oauth-unregister');
+
+
+    # Unregister client
+    $r->post('/settings/oauth/:client_id/unregister')->to(
+      cb => sub {
+        my $c = shift;
+        _set_no_cache($c->res->headers);
 
         my $v = $c->validation;
 
@@ -1465,9 +1022,7 @@
         };
 
         $v->csrf_protect;
-        $v->required('token', 'trim');
-        $v->optional('name', 'trim');
-        my $private_client_id = $c->stash('client_id');
+        $v->required('client-name', 'trim')->size(3, 255);
 
         # Render with error
         if ($v->has_error) {
@@ -1477,263 +1032,497 @@
           else {
             $c->notify(error => $c->loc('Auth_paramError'));
           };
-          return $c->redirect_to('oauth-tokens', client_id => $private_client_id);
+          return $c->redirect_to('oauth-settings');
         };
 
-        # Revoke token using super client privileges
-        state $r_url = Mojo::URL->new($c->korap->api)->path('oauth2/revoke/super');
+        my $client_id =     $c->stash('client_id');
+        my $client_name =   $v->param('client-name');
+        my $client_secret = $v->param('client-secret');
 
-        my $token = $v->param('token');
+        # Get list of registered clients
+        my $r_url = Mojo::URL->new($c->korap->api)->path('oauth2/client/deregister/')->path(
+          $client_id
+        );
 
-        return $c->korap_request(post => $r_url, {} => form => {
-          super_client_id => $client_id,
-          super_client_secret => $client_secret,
-          token => $token
-        })->then(
+        my $send = {};
+
+        if ($client_secret) {
+          $send->{client_secret} = $client_secret;
+        };
+
+        # Get the list of all clients
+        return $c->korap_request(delete => $r_url, {} => form => $send)->then(
           sub {
             my $tx = shift;
 
             # Response is fine
             if ($tx->res->is_success) {
-              $c->notify(success => $c->loc('Auth_revokeSuccess'));
-              return Mojo::Promise->resolve;
-            };
-
-            return Mojo::Promise->reject;
-          }
-        )->catch(
-          sub {
-            my $err_msg = shift;
-            if ($err_msg) {
-              $c->notify(error => { src => 'Backend' } => $err_msg );
+              # Okay
+              $c->notify(success => 'Successfully deleted ' . $client_name);
             }
             else {
-              $c->notify(error => $c->loc('Auth_revokeFail'));
+
+              # Failure
+              my $json = $tx->result->json;
+              if ($json && $json->{error_description}) {
+                $c->notify(error => $json->{error_description});
+              } else {
+                $c->notify(error => $c->loc('Auth_responseError'));
+              };
             };
+
+            return $c->redirect_to('oauth-settings');
           }
-        )->finally(
-          sub {
-            return $c->redirect_to('oauth-tokens', client_id => $private_client_id);
-          }
-        )
-
-        # Start IOLoop
-        ->wait;
+        );
       }
-    )->name('oauth-revoke-token-delete');
-  }
+    )->name('oauth-unregister-post');
 
-  # Use JWT login
-  else {
 
-    deprecated 'JWT flow is deprecated in favor of OAuth2 flow';
-
-    # Inject authorization to all korap requests
-    $app->hook(
-      before_korap_request => sub {
-        my ($c, $tx) = @_;
-        my $h = $tx->req->headers;
-
-        # If the request already has an Authorization
-        # header, respect it
-        unless ($h->authorization) {
-
-          # Get valid auth token and set as header
-          if (my $auth_token = $c->auth->token) {
-            $h->authorization($auth_token);
-          };
-        };
-      }
-    );
-
-    # Password flow with JWT
-    $r->post('/user/login')->to(
+    # OAuth Client authorization
+    $r->get('/settings/oauth/authorize')->to(
       cb => sub {
         my $c = shift;
 
-        # Validate input
-        my $v = $c->validation;
-        $v->required('handle_or_email', 'trim');
-        $v->required('pwd', 'trim');
-        $v->csrf_protect;
-        $v->optional('fwd')->closed_redirect;
+        _set_no_cache($c->res->headers);
 
-        my $user = check_decode($v->param('handle_or_email'));
-        unless ($user) {
-          $c->notify(error => $c->loc('Auth_invalidChar'));
-          $c->param(handle_or_email => '');
-          return $c->relative_redirect_to('index');
+        my $v = $c->validation;
+        $v->required('client_id');
+        $v->optional('scope');
+        $v->optional('state');
+        $v->optional('redirect_uri');
+
+        # Redirect with error
+        if ($v->has_error) {
+          $c->notify(error => $c->loc('Auth_paramError'));
+          return $c->redirect_to;
         };
 
-        my $fwd = $v->param('fwd');
+        foreach (qw!scope client_id state redirect_uri!) {
+          $c->stash($_, $v->param($_));
+        };
 
-        # Set flash for redirect
-        $c->flash(handle_or_email => $user);
+        # Wait for async result
+        $c->render_later;
 
-        if ($v->has_error || index($user, ':') >= 0) {
-          if ($v->has_error('fwd')) {
-            $c->notify(error => $c->loc('Auth_openRedirectFail'));
+        my $client_id = $v->param('client_id');
+
+        my $client_information = $c->auth->client_list_p->then(
+          sub {
+            my $clients = shift;
+            foreach (@$clients) {
+              if ($_->{client_id} eq $client_id) {
+                $c->stash(client_name => $_->{'client_name'});
+                $c->stash(client_type => $_->{'client_type'});
+                $c->stash(client_desc => $_->{'client_description'});
+                $c->stash(client_url => $_->{'client_url'});
+                $c->stash(redirect_uri_server => $_->{'client_redirect_uri'});
+                last;
+              };
+            };
           }
-          elsif ($v->has_error('csrf_token')) {
-            $c->notify(error => $c->loc('Auth_csrfFail'));
+        )->catch(
+          sub {
+            $c->stash(client_type => 'PUBLIC');
+            $c->stash(client_name => $v->param('client_id'));
+            return;
+          }
+        )->finally(
+          sub {
+
+            # Get auth token
+            my $auth_token = $c->auth->token;
+
+            # User is not logged in - log in before!
+            unless ($auth_token) {
+              return $c->render(template => 'auth/login');
+            };
+
+            # Grant authorization
+            return $c->render(template => 'auth/grant_scope');
+          }
+        );
+      }
+    )->name('oauth-grant-scope');
+
+
+    # OAuth Client authorization
+    # This will return a location information including some info
+    $r->post('/settings/oauth/authorize')->to(
+      cb => sub {
+        my $c = shift;
+
+        _set_no_cache($c->res->headers);
+
+        # It's necessary that it's clear this was triggered by
+        # KorAP and not by the client!
+        my $v = $c->validation;
+        $v->csrf_protect;
+        $v->required('client_id');
+        $v->optional('scope');
+        $v->optional('state');
+        $v->optional('redirect_uri');
+
+        # WARN! SIGN THIS TO PREVENT OPEN REDIRECT ATTACKS!
+        $v->required('redirect_uri_server');
+
+        # Render with error
+        if ($v->has_error) {
+          my $url = Mojo::URL->new($v->param('redirect_uri_server') // $c->url_for('index'));
+
+          if ($v->has_error('csrf_token')) {
+            $url->query([error_description => $c->loc('Auth_csrfFail')]);
           }
           else {
-            $c->notify(error => $c->loc('Auth_loginFail'));
+            $url->query([error_description => $c->loc('Auth_paramError')]);
           };
 
-          return $c->relative_redirect_to($fwd // 'index');
-        }
+          return $c->redirect_to($url);
+        };
 
-        my $pwd = $v->param('pwd');
+        state $r_url = Mojo::URL->new($c->korap->api)->path('oauth2/authorize');
+        $c->stash(redirect_uri_server => Mojo::URL->new($v->param('redirect_uri_server')));
 
-        $c->app->log->debug("Login from user $user");
-
-        my $url = Mojo::URL->new($c->korap->api)->path('auth/apiToken');
-
-        # Korap request for login
-        $c->korap_request('get', $url, {
-
-          # Set authorization header
-          Authorization => 'Basic ' . b("$user:$pwd")->b64_encode->trim,
-
+        return $c->korap_request(post => $r_url, {} => form => {
+          response_type => 'code',
+          client_id => $v->param('client_id'),
+          redirect_uri => $v->param('redirect_uri'),
+          state => $v->param('state'),
+          scope => $v->param('scope'),
         })->then(
           sub {
             my $tx = shift;
 
-            # Get the java token
-            my $jwt = $tx->result->json;
+            # Check for location header with code in redirects
+            my $loc;
+            foreach (@{$tx->redirects}) {
+              $loc = $_->res->headers->header('Location');
 
-            # No java web token
-            unless ($jwt) {
-              $c->notify(error => 'Response is no valid JWT (remote)');
-              return;
-            };
+              my $url = Mojo::URL->new($loc);
 
-            # There is an error here
-            # Dealing with errors here
-            if (my $error = $jwt->{error} // $jwt->{errors}) {
-              if (ref $error eq 'ARRAY') {
-                foreach (@$error) {
-                  unless ($_->[1]) {
-                    $c->notify(error => $c->loc('Auth_loginFail'));
-                  }
-                  else {
-                    $c->notify(error => $_->[0] . ($_->[1] ? ': ' . $_->[1] : ''));
-                  };
-                };
+              if ($url->query->param('code')) {
+                last;
+              } elsif (my $err = $url->query->param('error_description')) {
+                return Mojo::Promise->reject($err);
               }
-              else {
-                $c->notify(error => 'There is an unknown JWT error');
-              };
-              return;
             };
 
-            # TODO: Deal with user return values.
-            my $auth = $jwt->{token_type} . ' ' . $jwt->{token};
+            return Mojo::Promise->resolve($loc) if $loc;
 
-            $c->app->log->debug(qq!Login successful: "$user"!);
+            # Failed redirect, but location set
+            if ($tx->res->headers->location) {
+              my $url = Mojo::URL->new($tx->res->headers->location);
+              if (my $err = $url->query->param('error_description'))  {
+                return Mojo::Promise->reject($err);
+              };
+            };
 
-            $user = $jwt->{username} ? $jwt->{username} : $user;
-
-            # Set session info
-            $c->session(user => $user);
-            $c->session(auth => $auth);
-
-            # Set stash info
-            $c->stash(user => $user);
-            $c->stash(auth => $auth);
-            $c->notify(success => $c->loc('Auth_loginSuccess'));
+            # No location code
+            return Mojo::Promise->reject('no location response');
           }
         )->catch(
           sub {
-            my $e = shift;
-
-            # Notify the user
-            $c->notify(
-              error =>
-                ($e->{code} ? $e->{code} . ': ' : '') .
-                $e->{message} . ' for Login (remote)'
-              );
-
-            # Log failure
-            $c->app->log->debug(
-              ($e->{code} ? $e->{code} . ' - ' : '') .
-                $e->{message}
-              );
-
-            $c->app->log->debug(qq!Login fail: "$user"!);
-            $c->notify(error => $c->loc('Auth_loginFail'));
+            my $err_msg = shift;
+            my $url = $c->stash('redirect_uri_server');
+            if ($err_msg) {
+              $url = $url->query([error_description => $err_msg]);
+            };
+            return Mojo::Promise->resolve($url);
           }
-        )->finally(
+        )->then(
           sub {
-
-            # Redirect to slash
-            return $c->relative_redirect_to($fwd // 'index');
+            my $loc = shift;
+            return $c->redirect_to($loc);
           }
-        )
-
-        # Start IOLoop
-        ->wait;
-
-        return 1;
+        )->wait;
+        return $c->rendered;
       }
-    )->name('login');
+    )->name('oauth-grant-scope-post');
 
 
-    # Log out of the session
-    $r->get('/user/logout')->to(
+    # Show information of a client
+    $r->get('/settings/oauth/:client_id')->to(
       cb => sub {
         my $c = shift;
 
-        # TODO: csrf-protection!
+        _set_no_cache($c->res->headers);
 
-        # Log out of the system
-        my $url = Mojo::URL->new($c->korap->api)->path('auth/logout');
+        $c->render_later;
 
-        $c->korap_request(
-          'get', $url
+        $c->auth->client_list_p->then(
+          sub {
+            my $json = shift;
+
+            my ($item) = grep {
+              $c->stash('client_id') eq $_->{client_id}
+            } @$json;
+
+            unless ($item) {
+              return Mojo::Promise->reject;
+            };
+
+            $c->stash(client_name => $item->{client_name});
+            $c->stash(client_desc => $item->{client_description});
+            $c->stash(client_url  => $item->{client_url});
+            $c->stash(client_type => ($item->{client_type} // 'PUBLIC'));
+            $c->stash(client_src  => encode_json($item->{source})) if $item->{source};
+
+            $c->auth->token_list_p($c->stash('client_id'));
+          }
         )->then(
-          # Logged out
           sub {
-            my $tx = shift;
-            # Clear cache
-            # ?? Necesseary
-            # $c->chi('user')->remove($c->auth->token);
+            my $json = shift;
 
-            # TODO:
-            #   Revoke refresh token!
-            #   based on auth token!
-            # my $refresh_token = $c->chi('user')->get('refr_' . $c->auth->token);
-            # $c->auth->revoke_token($refresh_token)
+            $c->stash(tokens => $json);
 
-            # Expire session
-            $c->session(user => undef);
-            $c->session(auth => undef);
-            $c->notify(success => $c->loc('Auth_logoutSuccess'));
+            return Mojo::Promise->resolve;
           }
-
         )->catch(
-          # Something went wrong
           sub {
-            # my $err_msg = shift;
-            $c->notify('error', $c->loc('Auth_logoutFail'));
+            return $c->reply->not_found;
           }
-
         )->finally(
-          # Redirect
           sub {
-            return $c->redirect_to('index');
+            return $c->render(template => 'auth/client')
           }
-        )
+        );
 
-        # Start IOLoop
-        ->wait;
-
-        return 1;
+        return;
       }
-    )->name('logout');
+    )->name('oauth-tokens');
   };
 
+
+  # Ask if new token should be issued
+  $r->get('/settings/oauth/:client_id/token')->to(
+    cb => sub {
+      my $c = shift;
+      _set_no_cache($c->res->headers);
+      $c->render(template => 'auth/issue-token');
+    }
+  )->name('oauth-issue-token');
+
+
+  # Ask if a token should be revoked
+  $r->post('/settings/oauth/:client_id/token/revoke')->to(
+    cb => sub {
+      shift->render(template => 'auth/revoke-token');
+    }
+  )->name('oauth-revoke-token');
+
+
+  # Issue new token
+  $r->post('/settings/oauth/:client_id/token')->to(
+    cb => sub {
+      my $c = shift;
+      _set_no_cache($c->res->headers);
+
+      my $v = $c->validation;
+
+      unless ($c->auth->token) {
+        return $c->render(
+          content => 'Unauthorized',
+          status => 401
+        );
+      };
+
+      $v->csrf_protect;
+      $v->optional('client-secret');
+      $v->required('name', 'trim');
+
+      # Render with error
+      if ($v->has_error) {
+        if ($v->has_error('csrf_token')) {
+          $c->notify(error => $c->loc('Auth_csrfFail'));
+        }
+        else {
+          $c->notify(error => $c->loc('Auth_paramError'));
+        };
+        return $c->redirect_to('oauth-settings')
+      };
+
+      # Get authorization token
+      state $r_url = Mojo::URL->new($c->korap->api)->path('oauth2/authorize');
+      my $client_id = $c->stash('client_id');
+      my $name = $v->param('name');
+      my $redirect_url = $c->url_for->query({name => $name});
+
+      return $c->korap_request(post => $r_url, {} => form => {
+        response_type => 'code',
+        client_id => $client_id,
+        redirect_uri => $redirect_url,
+        # TODO: State
+      })->then(
+        sub {
+          my $tx = shift;
+
+          # Strip the token from the location header of the fake redirect
+          # TODO: Alternatively redirect!
+          my ($code, $scope, $loc, $name);
+          foreach (@{$tx->redirects}) {
+            $loc = $_->res->headers->header('Location');
+            if (index($loc, 'code') > 0) {
+              my $q = Mojo::URL->new($loc)->query;
+              $code  = $q->param('code');
+              $scope = $q->param('scope');
+              $name  = $q->param('name');
+              last;
+            };
+          };
+
+          # Fine!
+          if ($code) {
+            return Mojo::Promise->resolve(
+              $client_id,
+              $redirect_url,
+              $code,
+              $scope,
+              $name
+            );
+          };
+          return Mojo::Promise->reject;
+        }
+      )->then(
+        sub {
+          my ($client_id, $redirect_url, $code, $scope, $name) = @_;
+
+          # Get OAuth access token
+          state $r_url = Mojo::URL->new($c->korap->api)->path('oauth2/token');
+          return $c->kalamar_ua->post_p($r_url, {} => form => {
+            client_id => $client_id,
+            # NO CLIENT_SECRET YET SUPPORTED
+            grant_type => 'authorization_code',
+            code => $code,
+            redirect_uri => $redirect_url
+          })->then(
+            sub {
+              my $tx = shift;
+              my $json = $tx->res->json;
+
+              if ($tx->res->is_error) {
+                $c->notify(error => 'Unable to fetch new token');
+                return Mojo::Promise->reject;
+              };
+
+              $c->notify(success => 'New access token created');
+
+              $c->redirect_to('oauth-tokens' => { client_id => $client_id })
+            }
+          )->catch(
+            sub {
+              my $err_msg = shift;
+
+              # Only raised in case of connection errors
+              if ($err_msg) {
+                $c->notify(error => { src => 'Backend' } => $err_msg)
+              };
+
+              $c->render(
+                status => 400,
+                template => 'failure'
+              );
+            }
+          )
+
+          # Start IOLoop
+          ->wait;
+
+        }
+      )->catch(
+        sub {
+          my $err_msg = shift;
+
+          # Only raised in case of connection errors
+          if ($err_msg) {
+            $c->notify(error => { src => 'Backend' } => $err_msg)
+          };
+
+          return $c->render(
+            status => 400,
+            template => 'failure'
+          );
+        }
+      )
+
+      # Start IOLoop
+      ->wait;
+
+      return 1;
+    }
+  )->name('oauth-issue-token-post');
+
+
+  # Revoke token
+  $r->delete('/settings/oauth/:client_id/token')->to(
+    cb => sub {
+      my $c = shift;
+
+      my $v = $c->validation;
+
+      unless ($c->auth->token) {
+        return $c->render(
+          content => 'Unauthorized',
+          status => 401
+        );
+      };
+
+      $v->csrf_protect;
+      $v->required('token', 'trim');
+      $v->optional('name', 'trim');
+      my $private_client_id = $c->stash('client_id');
+
+      # Render with error
+      if ($v->has_error) {
+        if ($v->has_error('csrf_token')) {
+          $c->notify(error => $c->loc('Auth_csrfFail'));
+        }
+        else {
+          $c->notify(error => $c->loc('Auth_paramError'));
+        };
+        return $c->redirect_to('oauth-tokens', client_id => $private_client_id);
+      };
+
+      # Revoke token using super client privileges
+      state $r_url = Mojo::URL->new($c->korap->api)->path('oauth2/revoke/super');
+
+      my $token = $v->param('token');
+
+      return $c->korap_request(post => $r_url, {} => form => {
+        super_client_id => $client_id,
+        super_client_secret => $client_secret,
+        token => $token
+      })->then(
+        sub {
+          my $tx = shift;
+
+          # Response is fine
+          if ($tx->res->is_success) {
+            $c->notify(success => $c->loc('Auth_revokeSuccess'));
+            return Mojo::Promise->resolve;
+          };
+
+          return Mojo::Promise->reject;
+        }
+      )->catch(
+        sub {
+          my $err_msg = shift;
+          if ($err_msg) {
+            $c->notify(error => { src => 'Backend' } => $err_msg );
+          }
+          else {
+            $c->notify(error => $c->loc('Auth_revokeFail'));
+          };
+        }
+      )->finally(
+        sub {
+          return $c->redirect_to('oauth-tokens', client_id => $private_client_id);
+        }
+      )
+
+      # Start IOLoop
+      ->wait;
+    }
+  )->name('oauth-revoke-token-delete');
+
   $app->log->info('Successfully registered Auth plugin');
 };
 
@@ -1796,13 +1585,6 @@
 The client secret of Kalamar to be send with every OAuth 2.0
 management request.
 
-=item B<oauth2>
-
-Initially L<Kalamar-Plugin-Auth> was based on JWT. This parameter
-is historically used to switch between oauth2 and jwt. It is expected
-to be deprecated in the future, but for the moment it is required
-to be set to a true value.
-
 =item B<experimental_client_registration>
 
 Activates the oauth client registration flow.
@@ -1811,7 +1593,7 @@
 
 =head2 COPYRIGHT AND LICENSE
 
-Copyright (C) 2015-2020, L<IDS Mannheim|http://www.ids-mannheim.de/>
+Copyright (C) 2015-2022, L<IDS Mannheim|http://www.ids-mannheim.de/>
 Author: L<Nils Diewald|http://nils-diewald.de/>
 
 Kalamar is developed as part of the L<KorAP|http://korap.ids-mannheim.de/>
diff --git a/t/plugin/auth.t b/t/plugin/auth.t
deleted file mode 100644
index db19a36..0000000
--- a/t/plugin/auth.t
+++ /dev/null
@@ -1,243 +0,0 @@
-use Mojo::Base -strict;
-use Test::More;
-use Test::Mojo;
-use Mojo::File qw/path/;
-use Data::Dumper;
-
-
-#####################
-# Start Fake server #
-#####################
-my $mount_point = '/realapi/';
-$ENV{KALAMAR_API} = $mount_point;
-
-my $t = Test::Mojo->new('Kalamar' => {
-  Kalamar => {
-    plugins => ['Auth'],
-  },
-  'Kalamar-Auth' => {
-    jwt => 1
-  }
-});
-
-# Mount fake backend
-# Get the fixture path
-my $fixtures_path = path(Mojo::File->new(__FILE__)->dirname, '..', 'server');
-my $fake_backend = $t->app->plugin(
-  Mount => {
-    $mount_point =>
-      $fixtures_path->child('mock.pl')
-  }
-);
-# Configure fake backend
-$fake_backend->pattern->defaults->{app}->log($t->app->log);
-
-my $q = qr!(?:\"|&quot;)!;
-
-$t->get_ok('/realapi/v1.0')
-  ->status_is(200)
-  ->content_is('Fake server available');
-
-$t->get_ok('/?q=Baum')
-  ->status_is(200)
-  ->text_like('h1 span', qr/KorAP: Find .Baum./i)
-  ->text_like('#total-results', qr/\d+$/)
-  ->content_like(qr/${q}authorized${q}:null/)
-  ->element_exists_not('div.button.top a')
-  ->element_exists_not('aside.active')
-  ->element_exists_not('aside.off')
-  ;
-
-$t->get_ok('/')
-  ->status_is(200)
-  ->element_exists('form[action=/user/login] input[name=handle_or_email]')
-  ->element_exists('aside.active')
-  ->element_exists_not('aside.off')
-  ;
-
-$t->post_ok('/user/login' => form => { handle_or_email => 'test', pwd => 'fail' })
-  ->status_is(302)
-  ->header_is('Location' => '/');
-
-$t->get_ok('/')
-  ->status_is(200)
-  ->element_exists('div.notify-error')
-  ->text_is('div.notify-error', 'Bad CSRF token')
-  ->element_exists('input[name=handle_or_email][value=test]')
-  ->element_exists_not('div.button.top a')
-  ;
-
-$t->post_ok('/user/login' => form => { handle_or_email => 'test', pwd => 'pass' })
-  ->status_is(302)
-  ->header_is('Location' => '/');
-
-my $csrf = $t->get_ok('/')
-  ->status_is(200)
-  ->element_exists('div.notify-error')
-  ->text_is('div.notify-error', 'Bad CSRF token')
-  ->element_exists_not('div.button.top a')
-  ->tx->res->dom->at('input[name=csrf_token]')->attr('value')
-  ;
-
-$t->post_ok('/user/login' => form => {
-  handle_or_email => 'test',
-  pwd => 'ldaperr',
-  csrf_token => $csrf
-})
-  ->status_is(302)
-  ->content_is('')
-  ->header_is('Location' => '/');
-
-$csrf = $t->get_ok('/')
-  ->status_is(200)
-  ->element_exists('div.notify-error')
-  ->text_is('div.notify-error', '2022: LDAP Authentication failed due to unknown user or password!')
-  ->element_exists('input[name=handle_or_email][value=test]')
-  ->element_exists_not('div.button.top a')
-  ->tx->res->dom->at('input[name=csrf_token]')->attr('value')
-  ;
-
-$t->post_ok('/user/login' => form => {
-  handle_or_email => 'test',
-  pwd => 'unknown',
-  csrf_token => $csrf
-})
-  ->status_is(302)
-  ->content_is('')
-  ->header_is('Location' => '/');
-
-$csrf = $t->get_ok('/')
-  ->status_is(200)
-  ->element_exists('div.notify-error')
-  ->text_is('div.notify-error', 'Access denied')
-  ->element_exists('input[name=handle_or_email][value=test]')
-  ->element_exists_not('div.button.top a')
-  ->tx->res->dom->at('input[name=csrf_token]')->attr('value')
-  ;
-
-$t->post_ok('/user/login' => form => {
-  handle_or_email => 'test',
-  pwd => 'pass',
-  csrf_token => $csrf
-})
-  ->status_is(302)
-  ->content_is('')
-  ->header_is('Location' => '/');
-
-$t->get_ok('/')
-  ->status_is(200)
-  ->element_exists_not('div.notify-error')
-  ->element_exists('div.notify-success')
-  ->text_is('div.notify-success', 'Login successful')
-  ->element_exists('aside.off')
-  ->element_exists_not('aside.active')
-  ->element_exists_not('aside.settings')
-  ;
-
-# Now the user is logged in and should be able to
-# search with authorization
-$t->get_ok('/?q=Baum')
-  ->status_is(200)
-  ->text_like('h1 span', qr/KorAP: Find .Baum./i)
-  ->text_like('#total-results', qr/\d+$/)
-  ->element_exists_not('div.notify-error')
-  ->content_like(qr/${q}authorized${q}:${q}test${q}/)
-  ->element_exists('div.button.top a')
-  ->element_exists('div.button.top a.logout[title~="test"]')
-  ;
-
-# Logout
-$t->get_ok('/user/logout')
-  ->status_is(302)
-  ->header_is('Location' => '/');
-
-$t->get_ok('/')
-  ->status_is(200)
-  ->element_exists_not('div.notify-error')
-  ->element_exists('div.notify-success')
-  ->text_is('div.notify-success', 'Logout successful')
-  ;
-
-$t->get_ok('/?q=Baum')
-  ->status_is(200)
-  ->text_like('h1 span', qr/KorAP: Find .Baum./i)
-  ->text_like('#total-results', qr/\d+$/)
-  ->content_like(qr/${q}authorized${q}:null/)
-  ;
-
-# Get redirect
-my $fwd = $t->get_ok('/?q=Baum&ql=poliqarp')
-  ->status_is(200)
-  ->element_exists_not('div.notify-error')
-  ->tx->res->dom->at('input[name=fwd]')->attr('value')
-  ;
-
-is($fwd, '/?q=Baum&ql=poliqarp', 'Redirect is valid');
-
-$t->post_ok('/user/login' => form => {
-  handle_or_email => 'test',
-  pwd => 'pass',
-  csrf_token => $csrf,
-  fwd => 'http://bad.example.com/test'
-})
-  ->status_is(302)
-  ->header_is('Location' => '/');
-
-$t->get_ok('/')
-  ->status_is(200)
-  ->element_exists('div.notify-error')
-  ->element_exists_not('div.notify-success')
-  ->text_is('div.notify-error', 'Redirect failure')
-  ;
-
-$t->post_ok('/user/login' => form => {
-  handle_or_email => 'test',
-  pwd => 'pass',
-  csrf_token => $csrf,
-  fwd => $fwd
-})
-  ->status_is(302)
-  ->header_is('Location' => '/?q=Baum&ql=poliqarp');
-
-
-done_testing;
-__END__
-
-
-# Login mit falschem Nutzernamen:
-# 400 und:
-{"errors":[[2022,"LDAP Authentication failed due to unknown user or password!"]]}
-
-
-
-ok(!$c->user->get('details'), 'User not logged in');
-
-# Login with user credentials
-ok($c->user->login('kustvakt', 'kustvakt2015'), 'Login with demo user');
-is($c->stash('user'), 'kustvakt', 'Kustvakt is logged in');
-like($c->stash('auth'), qr/^api_token /, 'Kustvakt is logged in');
-
-my $details = $c->user->get('details');
-is($details->{email}, 'kustvakt@ids-mannheim.de', 'Email');
-is($details->{firstName}, 'Kustvakt', 'Firstname');
-is($details->{lastName}, 'KorAP', 'Lastname');
-is($details->{country}, 'Germany', 'Country');
-is($details->{address}, 'Mannheim', 'Address');
-is($details->{username}, 'kustvakt', 'Username');
-is($details->{institution}, 'IDS Mannheim', 'Institution');
-
-my $settings = $c->user->get('settings');
-is($settings->{username}, 'kustvakt', 'Username');
-
-# ok($c->user->set(details => { firstName => 'Me' }), 'Set first name');
-#ok($c->user->set(details => {
-#  firstName => 'Akron',
-#  lastName => 'Fuxfell'
-#}), 'Set first name');
-
-# diag Dumper $c->user->get('info');
-
-ok(1,'Fine');
-
-done_testing;
-__END__