diff --git a/module/finc/src/finc/ILS/Driver/FincILS.php b/module/finc/src/finc/ILS/Driver/FincILS.php
index e8989d339b4df54be9889a83682df51a3a581d96..0c147c2aa44fcfdecaf991d4a0df675cd339babb 100644
--- a/module/finc/src/finc/ILS/Driver/FincILS.php
+++ b/module/finc/src/finc/ILS/Driver/FincILS.php
@@ -26,8 +26,7 @@
  * @link     https://vufind.org/wiki/development:plugins:ils_drivers Wiki
  */
 namespace finc\ILS\Driver;
-use VuFind\Exception\ILS as ILSException,
-    VuFindSearch\Query\Query, VuFindSearch\Service as SearchService,
+use VuFindSearch\Query\Query, VuFindSearch\Service as SearchService,
     ZfcRbac\Service\AuthorizationServiceAwareInterface,
     ZfcRbac\Service\AuthorizationServiceAwareTrait,
     Zend\Log\LoggerAwareInterface as LoggerAwareInterface,
@@ -507,8 +506,10 @@ class FincILS extends PAIA implements LoggerAwareInterface
                         $password,
                         $username
                     );
-                } catch (ILSException $e) {
-                    $this->debug('Session expired, login again', 'info');
+                } catch (Exception $e) {
+                    // TODO? $this->debug('Session expired, login again', 'info');
+                    // all error handling is done in paiaHandleErrors so pass on the excpetion
+                    throw $e;
                 }
             }
 
@@ -520,8 +521,9 @@ class FincILS extends PAIA implements LoggerAwareInterface
                         $username
                     );
                 }
-            } catch (ILSException $e) {
-                throw new ILSException($e->getMessage());
+            } catch (Exception $e) {
+                // all error handling is done in paiaHandleErrors so pass on the excpetion
+                throw $e;
             }
         } else {
             return parent::patronLogin($username, $password);
@@ -568,9 +570,15 @@ class FincILS extends PAIA implements LoggerAwareInterface
         }
 
         if (!isset($itemsResponse) || $itemsResponse == null) {
-            $itemsResponse = $this->paiaGetAsArray(
-                'core/'.$patron['cat_username'].'/items'
-            );
+            try {
+                $itemsResponse = $this->paiaGetAsArray(
+                    'core/'.$patron['cat_username'].'/items'
+                );
+            } catch (Exception $e) {
+                // all error handling is done in paiaHandleErrors so pass on the excpetion
+                throw $e;
+            }
+
             if ($this->paiaCacheEnabled) {
                 $this->putCachedData($patron['cat_username'] . '_items', $itemsResponse);
             }
diff --git a/module/finc/src/finc/ILS/Driver/PAIA.php b/module/finc/src/finc/ILS/Driver/PAIA.php
index 4e402a69116f4d0e3b9892cff9a0bfbd68a25431..4bb7097b646b2b0af0af68a89f05184fbbf7a82d 100644
--- a/module/finc/src/finc/ILS/Driver/PAIA.php
+++ b/module/finc/src/finc/ILS/Driver/PAIA.php
@@ -31,7 +31,8 @@
  */
 
 namespace finc\ILS\Driver;
-use VuFind\Exception\ILS as ILSException;
+use VuFind\Exception\Auth as AuthException,
+    VuFind\Exception\ILS as ILSException;
 
 /**
  * PAIA ILS Driver for VuFind to get patron information
@@ -99,6 +100,19 @@ class PAIA extends DAIA
         '5' => 'rejected',
     ];
 
+    /**
+     * PAIA scopes as defined in http://gbv.github.io/paia/paia.html#access-tokens-and-scopes
+     */
+    const SCOPE_READ_PATRON = 'read_patron';
+    const SCOPE_UPDATE_PATRON = 'update_patron';
+    const SCOPE_UPDATE_PATRON_NAME = 'update_patron_name';
+    const SCOPE_UPDATE_PATRON_EMAIL = 'update_patron_email';
+    const SCOPE_UPDATE_PATRON_ADDRESS = 'update_patron_address';
+    const SCOPE_READ_FEES = 'read_fees';
+    const SCOPE_READ_ITEMS = 'read_items';
+    const SCOPE_WRITE_ITEMS = 'write_items';
+    const SCOPE_CHANGE_PASSWORD = 'change_password';
+
     /**
      * Constructor
      *
@@ -242,6 +256,11 @@ class PAIA extends DAIA
      */
     public function cancelHolds($cancelDetails)
     {
+        // check if user has appropriate scope
+        if (!$this->paiaCheckScope(self::SCOPE_WRITE_ITEMS)) {
+            throw new ILSException('You are not entitled to write items.');
+        }
+
         $it = $cancelDetails['details'];
         $items = [];
         foreach ($it as $item) {
@@ -254,7 +273,7 @@ class PAIA extends DAIA
             $array_response = $this->paiaPostAsArray(
                 'core/'.$patron['cat_username'].'/cancel', $post_data
             );
-        } catch (ILSException $e) {
+        } catch (Exception $e) {
             $this->debug($e->getMessage());
             return [
                 'success' => false,
@@ -319,6 +338,11 @@ class PAIA extends DAIA
      */
     public function changePassword($details)
     {
+        // check if user has appropriate scope
+        if (!$this->paiaCheckScope(self::SCOPE_CHANGE_PASSWORD)) {
+            throw new ILSException('You are not entitled to change password.');
+        }
+
         $post_data = [
             "patron"       => $details['patron']['cat_username'],
             "username"     => $details['patron']['cat_username'],
@@ -330,7 +354,7 @@ class PAIA extends DAIA
             $array_response = $this->paiaPostAsArray(
                 'auth/change', $post_data
             );
-        } catch (ILSException $e) {
+        } catch (Exception $e) {
             $this->debug($e->getMessage());
             return [
                 'success' => false,
@@ -431,9 +455,19 @@ class PAIA extends DAIA
      */
     public function getMyFines($patron)
     {
-        $fees = $this->paiaGetAsArray(
-            'core/'.$patron['cat_username'].'/fees'
-        );
+        // check if user has appropriate scope
+        if (!$this->paiaCheckScope(self::SCOPE_READ_FEES)) {
+            throw new ILSException('You are not entitled to read fees.');
+        }
+
+        try {
+            $fees = $this->paiaGetAsArray(
+                'core/'.$patron['cat_username'].'/fees'
+            );
+        } catch (Exception $e) {
+            // all error handling is done in paiaHandleErrors so pass on the excpetion
+            throw $e;
+        }
 
         // PAIA simple data type money: a monetary value with currency (format
         // [0-9]+\.[0-9][0-9] [A-Z][A-Z][A-Z]), for instance 0.80 USD.
@@ -556,7 +590,7 @@ class PAIA extends DAIA
                 'expires'    => isset($patron['expires'])
                     ? $this->convertDate($patron['expires']) : null,
                 'statuscode' => isset($patron['status']) ? $patron['status'] : null,
-                'canWrite'   => in_array('write_items', $this->getSession()->scope),
+                'canWrite'   => in_array(self::SCOPE_WRITE_ITEMS, $this->getSession()->scope),
             ];
         }
         return [];
@@ -692,8 +726,10 @@ class PAIA extends DAIA
                     $this->paiaGetUserDetails($session->patron),
                     $password
                 );
-            } catch (ILSException $e) {
-                $this->debug('Session expired, login again', ['info' => 'info']);
+            } catch (Exception $e) {
+                // TODO? $this->debug('Session expired, login again', ['info' => 'info']);
+                // all error handling is done in paiaHandleErrors so pass on the excpetion
+                throw $e;
             }
         }
         try {
@@ -703,8 +739,71 @@ class PAIA extends DAIA
                     $password
                 );
             }
-        } catch (ILSException $e) {
-            throw new ILSException($e->getMessage());
+        } catch (Exception $e) {
+            // all error handling is done in paiaHandleErrors so pass on the excpetion
+            throw $e;
+        }
+    }
+
+    /**
+     * Handle PAIA request errors and throw appropriate exception.
+     *
+     * @param array $error Array containing error messages
+     * @throws AuthException
+     * @throws ILSException
+     */
+    protected function paiaHandleErrors($array)
+    {
+        // TODO: also have exception contain content of 'error' as for at least
+        //       error code 403 two differing errors are possible
+        //       (cf.  http://gbv.github.io/paia/paia.html#request-errors)
+        if (isset($array['error'])) {
+            switch ($array['error']) {
+                // cf. http://gbv.github.io/paia/paia.html#request-errors
+                // error        code    error_description
+                // access_denied 	403 	Wrong or missing credentials to get an access token
+                case 'access_denied':
+                    throw new AuthException(
+                        isset($array['error_description'])
+                            ? $array['error_description'] : $array['error'],
+                        isset($array['code']) ? $array['code'] : ''
+                    );
+                // not_found 	404 	Unknown request URL or unknown patron. Implementations SHOULD first check authentication and prefer error invalid_grant or access_denied to prevent leaking patron identifiers.
+                case 'not_found':
+
+                // not_implemented 	501 	Known but unsupported request URL (for instance a PAIA auth server server may not implement http://example.org/core/change)
+                case 'not_implemented':
+
+                // invalid_request 	405 	Unexpected HTTP verb
+                // invalid_request 	400 	Malformed request (for instance error parsing JSON, unsupported request content type, etc.)
+                // invalid_request 	422 	The request parameters could be parsed but they don’t match the request method (for instance missing fields, invalid values, etc.)
+                case 'invalid_request':
+
+                // invalid_grant 	401 	The access token was missing, invalid, or expired
+                case 'invalid_grant':
+
+                // insufficient_scope 	403 	The access token was accepted but it lacks permission for the request
+                case 'insufficient_scope':
+
+                // internal_error 	500 	An unexpected error occurred. This error corresponds to a bug in the implementation of a PAIA auth/core server
+                case 'internal_error':
+
+                // service_unavailable 	503 	The request couldn’t be serviced because of a temporary failure
+                case 'service_unavailable':
+
+                // bad_gateway 	502 	The request couldn’t be serviced because of a backend failure (for instance the library system’s database)
+                case 'bad_gateway':
+
+                // gateway_timeout 	504 	The request couldn’t be serviced because of a backend failure'
+                case 'gateway_timeout':
+
+                default:
+                    throw new ILSException(
+                        isset($array['error_description'])
+                            ? $array['error_description'] : $array['error'],
+                        isset($array['code']) ? $array['code'] : ''
+                    );
+            }
         }
     }
 
@@ -760,6 +859,11 @@ class PAIA extends DAIA
      */
     public function placeHold($holdDetails)
     {
+        // check if user has appropriate scope
+        if (!$this->paiaCheckScope(self::SCOPE_WRITE_ITEMS)) {
+            throw new ILSException('You are not entitled to write items.');
+        }
+
         $item = $holdDetails['item_id'];
         $patron = $holdDetails['patron'];
 
@@ -774,7 +878,7 @@ class PAIA extends DAIA
             $array_response = $this->paiaPostAsArray(
                 'core/'.$patron['cat_username'].'/request', $post_data
             );
-        } catch (ILSException $e) {
+        } catch (Exception $e) {
             $this->debug($e->getMessage());
             return [
                 'success' => false,
@@ -852,6 +956,11 @@ class PAIA extends DAIA
      */
     public function renewMyItems($details)
     {
+        // check if user has appropriate scope
+        if (!$this->paiaCheckScope(self::SCOPE_WRITE_ITEMS)) {
+            throw new ILSException('You are not entitled to write items.');
+        }
+
         $it = $details['details'];
         $items = [];
         foreach ($it as $item) {
@@ -864,7 +973,7 @@ class PAIA extends DAIA
             $array_response = $this->paiaPostAsArray(
                 'core/'.$patron['cat_username'].'/renew', $post_data
             );
-        } catch (ILSException $e) {
+        } catch (Exception $e) {
             $this->debug($e->getMessage());
             return [
                 'success' => false,
@@ -953,15 +1062,25 @@ class PAIA extends DAIA
      */
     protected function paiaGetItems($patron, $filter = [])
     {
+        // check if user has appropriate scope
+        if (!$this->paiaCheckScope(self::SCOPE_READ_ITEMS)) {
+            throw new ILSException('You are not entitled to read items.');
+        }
+
         // check for existing data in cache
         if ($this->paiaCacheEnabled) {
             $itemsResponse = $this->getCachedData($patron['cat_username'] . '_items');
         }
 
         if (!isset($itemsResponse) || $itemsResponse == null) {
-            $itemsResponse = $this->paiaGetAsArray(
-                'core/'.$patron['cat_username'].'/items'
-            );
+            try {
+                $itemsResponse = $this->paiaGetAsArray(
+                    'core/'.$patron['cat_username'].'/items'
+                );
+            } catch (Exception $e) {
+                // all error handling is done in paiaHandleErrors so pass on the excpetion
+                throw $e;
+            }
             if ($this->paiaCacheEnabled) {
                 $this->putCachedData($patron['cat_username'] . '_items', $itemsResponse);
             }
@@ -1092,7 +1211,7 @@ class PAIA extends DAIA
             $result['item_id'] = (isset($doc['item']) ? $doc['item'] : '');
 
             $result['cancel_details']
-                = (isset($doc['cancancel']) && $doc['cancancel'])
+                = (isset($doc['cancancel']) && $doc['cancancel'] && $this->paiaCheckScope(self::SCOPE_WRITE_ITEMS))
                 ? $result['item_id'] : '';
 
             // edition (0..1) URI of a the document (no particular copy)
@@ -1181,7 +1300,7 @@ class PAIA extends DAIA
             $result['item_id'] = (isset($doc['item']) ? $doc['item'] : '');
 
             $result['cancel_details']
-                = (isset($doc['cancancel']) && $doc['cancancel'])
+                = (isset($doc['cancancel']) && $doc['cancancel'] && $this->paiaCheckScope(self::SCOPE_WRITE_ITEMS))
                 ? $result['item_id'] : '';
 
             // edition (0..1) URI of a the document (no particular copy)
@@ -1242,14 +1361,14 @@ class PAIA extends DAIA
         foreach ($items as $doc) {
             $result = [];
             // canrenew (0..1) whether a document can be renewed (bool)
-            $result['renewable'] = (isset($doc['canrenew'])
-                ? $doc['canrenew'] : false);
+            $result['renewable'] = (isset($doc['canrenew']) && $this->paiaCheckScope(self::SCOPE_WRITE_ITEMS))
+                ? $doc['canrenew'] : false;
 
             // item (0..1) URI of a particular copy
             $result['item_id'] = (isset($doc['item']) ? $doc['item'] : '');
 
             $result['renew_details']
-                = (isset($doc['canrenew']) && $doc['canrenew'])
+                = (isset($doc['canrenew']) && $doc['canrenew'] && $this->paiaCheckScope(self::SCOPE_WRITE_ITEMS))
                 ? $result['item_id'] : '';
 
             // edition (0..1)  URI of a the document (no particular copy)
@@ -1411,11 +1530,14 @@ class PAIA extends DAIA
     {
         $responseArray = json_decode($file, true);
 
+        // if we have an error response handle it accordingly (any will throw an
+        // exception at the moment) and pass on the resulting exception
         if (isset($responseArray['error'])) {
-            throw new ILSException(
-                $responseArray['error'],
-                $responseArray['code']
-            );
+            try {
+                $this->paiaHandleErrors($responseArray);
+            } catch (Exception $e) {
+                throw $e;
+            }
         }
 
         return $responseArray;
@@ -1438,9 +1560,9 @@ class PAIA extends DAIA
 
         try {
             $responseArray = $this->paiaParseJsonAsArray($responseJson);
-        } catch (ILSException $e) {
-            $this->debug($e->getCode() . ':' . $e->getMessage());
-            return [];
+        } catch (Exception $e) {
+            // all error handling is done in paiaHandleErrors so pass on the excpetion
+            throw $e;
         }
 
         return $responseArray;
@@ -1466,9 +1588,8 @@ class PAIA extends DAIA
         try {
             $responseArray = $this->paiaParseJsonAsArray($responseJson);
         } catch (ILSException $e) {
-            $this->debug($e->getCode() . ':' . $e->getMessage());
-            /* TODO: do not return empty array, this causes eventually confusion */
-            return [];
+            // all error handling is done in paiaHandleErrors so pass on the excpetion
+            throw $e;
         }
 
         return $responseArray;
@@ -1491,20 +1612,19 @@ class PAIA extends DAIA
             "username"   => $username,
             "password"   => $password,
             "grant_type" => "password",
-            "scope"      => "read_patron read_fees read_items write_items " .
-                            "change_password"
+            "scope"      => self::SCOPE_READ_PATRON . " " .
+                self::SCOPE_READ_FEES . " " .
+                self::SCOPE_READ_ITEMS . " " .
+                self::SCOPE_WRITE_ITEMS . " " .
+                self::SCOPE_CHANGE_PASSWORD
         ];
         $responseJson = $this->paiaPostRequest('auth/login', $post_data);
 
         try {
             $responseArray = $this->paiaParseJsonAsArray($responseJson);
-        } catch (ILSException $e) {
-            if ($e->getMessage() === 'access_denied') {
-                return false;
-            }
-            throw new ILSException(
-                $e->getCode() . ':' . $e->getMessage()
-            );
+        } catch (Exception $e) {
+            // all error handling is done in paiaHandleErrors so pass on the excpetion
+            throw $e;
         }
 
         if (!isset($responseArray['access_token'])) {
@@ -1548,6 +1668,11 @@ class PAIA extends DAIA
      */
     protected function paiaGetUserDetails($patron)
     {
+        // check if user has appropriate scope
+        if (!$this->paiaCheckScope(self::SCOPE_READ_PATRON)) {
+            throw new ILSException('You are not entitled to read patron.');
+        }
+
         $responseJson = $this->paiaGetRequest(
             'core/' . $patron, $this->getSession()->access_token
         );
@@ -1555,13 +1680,23 @@ class PAIA extends DAIA
         try {
             $responseArray = $this->paiaParseJsonAsArray($responseJson);
         } catch (ILSException $e) {
-            throw new ILSException(
-                $e->getMessage(), $e->getCode()
-            );
+            // all error handling is done in paiaHandleErrors so pass on the excpetion
+            throw $e;
         }
         return $this->paiaParseUserDetails($patron, $responseArray);
     }
 
+    /**
+     * Checks if the current scope is set for active session.
+     *
+     * @return boolean
+     */
+    protected function paiaCheckScope($scope)
+    {
+        return (!empty($scope) && is_array($this->getSession()->scope))
+            ? in_array($scope, $this->getSession()->scope) : false;
+    }
+
     /**
      * Check if storage retrieval request available
      *
@@ -1599,7 +1734,7 @@ class PAIA extends DAIA
         if (
             isset($patron['status']) && $patron['status']  == 0
             && isset($patron['expires']) && $patron['expires'] > date('Y-m-d')
-            && in_array('write_items', $this->getSession()->scope)
+            && in_array(self::SCOPE_WRITE_ITEMS, $this->getSession()->scope)
         ) {
             return true;
         }