diff --git a/classes/TokenExchanger.php b/classes/TokenExchanger.php
index f37841093eaca595dc0706e36f144bbfbfe05d2c..8aa7ac9c1967388b6492aa5e7eb6eb925362b0cb 100644
--- a/classes/TokenExchanger.php
+++ b/classes/TokenExchanger.php
@@ -59,6 +59,7 @@ class TokenExchanger {
 
         if ($params['resource'] !== null) {
             $claims['resource'] = $params['resource'];
+            $claims['jti'] = uniqid();
         }
         if ($params['audience'] !== null) {
             $claims['aud'] = $this->getAudienceClaim($params['audience']);
@@ -66,6 +67,9 @@ class TokenExchanger {
         if ($params['scope'] !== null) {
             $claims['scope'] = $params['scope'];
         }
+        if ($params['expires_in'] !== null) {
+            $claims['exp'] = time() + intval($params['expires_in']);
+        }
         
         $accessToken = $this->locator->getTokenBuilder()->generateToken($claims);
         
@@ -74,6 +78,7 @@ class TokenExchanger {
         $data['access_token'] = $accessToken;
         $data['issued_token_type'] = "urn:ietf:params:oauth:token-type:jwt";
         $data['token_type'] = 'Bearer';
+        $data['expires_in'] = $params['expires_in'] !== null ? $params['expires_in'] : 3600;
 
         return $data;
     }
diff --git a/include/front-controller.php b/include/front-controller.php
index 14a205bcc24d5fdd6e166e09c45f03679afa0b6d..41e2b5dda60690341df44c455ad528ca2ff42dcf 100644
--- a/include/front-controller.php
+++ b/include/front-controller.php
@@ -103,6 +103,7 @@ Flight::route('POST /auth/oauth2/token', function() {
         // For token exchange
         "resource" => filter_input(INPUT_POST, "resource", FILTER_SANITIZE_STRING),
         "audience" => filter_input(INPUT_POST, "audience", FILTER_SANITIZE_STRING),
+        "expires_in" => filter_input(INPUT_POST, "expires_in", FILTER_SANITIZE_NUMBER_INT),
         "subject_token" => filter_input(INPUT_POST, "subject_token", FILTER_SANITIZE_STRING),
         "subject_token_type" => filter_input(INPUT_POST, "subject_token_type", FILTER_SANITIZE_STRING)
     ];
diff --git a/tests/TokenExchangerTest.php b/tests/TokenExchangerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..7e201838610f0ae9fb2921afb183bb67bf26d307
--- /dev/null
+++ b/tests/TokenExchangerTest.php
@@ -0,0 +1,40 @@
+<?php
+
+use PHPUnit\Framework\TestCase;
+
+final class TokenExchangerTest extends TestCase {
+
+    public function testExchange() {
+        
+        $clientAuthCheckerStub = $this->createMock(\RAP\ClientAuthChecker::class);
+        
+        $tokenCheckerStub = $this->createMock(\RAP\TokenChecker::class);
+        $tokenCheckerStub->method('getValidTokenObject')->willReturn((object) [
+            "sub" => "user_id"
+        ]);
+        
+        $tokenBuilderStub = $this->createMock(\RAP\TokenBuilder::class);
+        $tokenBuilderStub->method('generateToken')->willReturn("new_token");
+        
+        $locatorStub = $this->createMock(\RAP\Locator::class);
+        $locatorStub->method('getClientAuthChecker')->willReturn($clientAuthCheckerStub);
+        $locatorStub->method('getTokenChecker')->willReturn($tokenCheckerStub);
+        $locatorStub->method('getTokenBuilder')->willReturn($tokenBuilderStub);
+                
+        $tokenExchanger = new \RAP\TokenExchanger($locatorStub);
+        
+        $params = [
+            "subject_token" => "subject_token",
+            "subject_token_type" => "Bearer",
+            "resource" => "resource",
+            "audience" => "audience",
+            "scope" => "scope",
+            "expires_in" => 1800
+        ];
+        
+        $result = $tokenExchanger->exchangeToken($params, []);
+        
+        $this->assertEquals("new_token", $result['access_token']);
+    }
+
+}