diff --git a/classes/IdTokenBuilder.php b/classes/IdTokenBuilder.php
index cef3321aa36d6ca8752660bf89e1570ddc276ca6..1ea499de588ce879559ed0653029fbc4d0702f88 100644
--- a/classes/IdTokenBuilder.php
+++ b/classes/IdTokenBuilder.php
@@ -29,7 +29,7 @@ class IdTokenBuilder {
             'iss' => $this->locator->config->jwtIssuer,
             'sub' => $user->id,
             'iat' => time(),
-            'exp' => time() + 120,
+            'exp' => time() + 3600,
             'name' => $user->getCompleteName()
         );
 
diff --git a/classes/OAuth2RequestHandler.php b/classes/OAuth2RequestHandler.php
index c481f9816b2fbc87aafda7e32702184a75ad62b6..bdee406f5070014dba03239a4c99702753012758 100644
--- a/classes/OAuth2RequestHandler.php
+++ b/classes/OAuth2RequestHandler.php
@@ -2,6 +2,8 @@
 
 namespace RAP;
 
+use \Firebase\JWT\JWT;
+
 class OAuth2RequestHandler {
 
     private $locator;
@@ -166,7 +168,33 @@ class OAuth2RequestHandler {
         }
 
         $accessToken = $this->locator->getAccessTokenDAO()->getAccessToken($bearer_token);
-        if ($accessToken->expired) {
+        if ($accessToken === null) {
+            $this->attemptJWTTokenValidation($bearer_token);
+        } else if ($accessToken->expired) {
+            throw new UnauthorizedException("Access token is expired");
+        }
+    }
+
+    private function attemptJWTTokenValidation($jwt): void {
+
+        $jwtParts = explode('.', $jwt);
+        if (count($jwtParts) === 0) {
+            throw new UnauthorizedException("Invalid token");
+        }
+
+        $header = JWT::jsonDecode(JWT::urlsafeB64Decode($jwtParts[0]));
+        if (!isset($header->kid)) {
+            throw new UnauthorizedException("Invalid token: missing kid in header");
+        }
+
+        $keyPair = $this->locator->getJWKSDAO()->getRSAKeyPairById($header->kid);
+        if ($keyPair === null) {
+            throw new UnauthorizedException("Invalid kid: no key found");
+        }
+
+        try {
+            JWT::decode($jwt, $keyPair->publicKey, [$keyPair->alg]);
+        } catch (\Firebase\JWT\ExpiredException $ex) {
             throw new UnauthorizedException("Access token is expired");
         }
     }
diff --git a/classes/datalayer/JWKSDAO.php b/classes/datalayer/JWKSDAO.php
index c2821ea90b72ff65153f4fd525a63cea481065ea..54b0ce011e95dec26c1a2598addda7b0256af1fd 100644
--- a/classes/datalayer/JWKSDAO.php
+++ b/classes/datalayer/JWKSDAO.php
@@ -6,6 +6,8 @@ interface JWKSDAO {
 
     public function getRSAKeyPairs(): array;
 
+    public function getRSAKeyPairById(string $id): ?RSAKeyPair;
+
     public function insertRSAKeyPair(RSAKeyPair $keyPair): RSAKeyPair;
 
     public function getNewestKeyPair(): RSAKeyPair;
diff --git a/classes/datalayer/mysql/MySQLJWKSDAO.php b/classes/datalayer/mysql/MySQLJWKSDAO.php
index cb4cf4b50e0054261103893ae8240e3631903d58..4d6c713bbda6430531c63cd2bc7362bedf009a13 100644
--- a/classes/datalayer/mysql/MySQLJWKSDAO.php
+++ b/classes/datalayer/mysql/MySQLJWKSDAO.php
@@ -43,6 +43,23 @@ class MySQLJWKSDAO extends BaseMySQLDAO implements JWKSDAO {
         return $keyPairs;
     }
 
+    public function getRSAKeyPairById(string $id): ?RSAKeyPair {
+
+        $dbh = $this->getDBHandler();
+
+        $query = "SELECT id, private_key, public_key, alg, creation_time FROM rsa_keypairs WHERE id = :id";
+
+        $stmt = $dbh->prepare($query);
+        $stmt->bindParam(':id', $id);
+        $stmt->execute();
+
+        foreach ($stmt->fetchAll() as $row) {
+            return $this->getRSAKeyPairFromResultRow($row);
+        }
+
+        return null;
+    }
+
     public function getNewestKeyPair(): RSAKeyPair {
         $dbh = $this->getDBHandler();
 
diff --git a/index.php b/index.php
index 76b64a8f0b93095e552aede99c5664a41cd8c680..5fd9f6db83a294a0fec9534fe1ad28f1439779be 100644
--- a/index.php
+++ b/index.php
@@ -37,12 +37,14 @@ Flight::map('error', function($ex) {
         http_response_code(401);
         echo "Unauthorized: " . $ex->message;
     } else if ($ex instanceof \Exception) {
+        http_response_code(500);
         if ($ex->getMessage() !== null) {
             echo $ex->getMessage();
         } else {
             echo $ex->getTraceAsString();
         }
     } else {
+        http_response_code(500);
         throw $ex;
     }
 });