diff --git a/oauth2-server-jwt/pom.xml b/oauth2-server-jwt/pom.xml
new file mode 100644
index 0000000..9ccd3bd
--- /dev/null
+++ b/oauth2-server-jwt/pom.xml
@@ -0,0 +1,27 @@
+
+
+
+ kotlin-oauth2-server
+ nl.myndocs
+ 0.3.2-SNAPSHOT
+
+ 4.0.0
+
+ oauth2-server-jwt
+
+
+
+ nl.myndocs
+ oauth2-server-core
+ ${project.version}
+ provided
+
+
+ com.auth0
+ java-jwt
+ 3.5.0
+
+
+
\ No newline at end of file
diff --git a/oauth2-server-jwt/src/main/java/nl/myndocs/convert/DefaultJwtBuilder.kt b/oauth2-server-jwt/src/main/java/nl/myndocs/convert/DefaultJwtBuilder.kt
new file mode 100644
index 0000000..2b44c1e
--- /dev/null
+++ b/oauth2-server-jwt/src/main/java/nl/myndocs/convert/DefaultJwtBuilder.kt
@@ -0,0 +1,20 @@
+package nl.myndocs.convert
+
+import com.auth0.jwt.JWT
+import java.time.Instant
+import java.util.*
+
+object DefaultJwtBuilder : JwtBuilder {
+ override fun buildJwt(username: String?, clientId: String, requestedScopes: Set, expiresInSeconds: Long) =
+ JWT.create()
+ .withIssuedAt(Date.from(Instant.now()))
+ .withExpiresAt(
+ Date.from(
+ Instant.now()
+ .plusSeconds(expiresInSeconds)
+ )
+ )
+ .withClaim("client_id", clientId)
+ .withArrayClaim("scopes", requestedScopes.toTypedArray())
+ .let { withBuilder -> if (username != null) withBuilder.withClaim("username", username) else withBuilder }
+}
\ No newline at end of file
diff --git a/oauth2-server-jwt/src/main/java/nl/myndocs/convert/JwtAccessTokenConverter.kt b/oauth2-server-jwt/src/main/java/nl/myndocs/convert/JwtAccessTokenConverter.kt
new file mode 100644
index 0000000..a794724
--- /dev/null
+++ b/oauth2-server-jwt/src/main/java/nl/myndocs/convert/JwtAccessTokenConverter.kt
@@ -0,0 +1,32 @@
+package nl.myndocs.convert
+
+import com.auth0.jwt.algorithms.Algorithm
+import nl.myndocs.oauth2.token.AccessToken
+import nl.myndocs.oauth2.token.RefreshToken
+import nl.myndocs.oauth2.token.converter.AccessTokenConverter
+import java.time.Instant
+
+class JwtAccessTokenConverter(
+ private val algorithm: Algorithm,
+ private val accessTokenExpireInSeconds: Int = 3600,
+ private val jwtBuilder: JwtBuilder = DefaultJwtBuilder
+) : AccessTokenConverter {
+ override fun convertToToken(username: String?, clientId: String, requestedScopes: Set, refreshToken: RefreshToken?): AccessToken {
+ val jwtBuilder = jwtBuilder.buildJwt(
+ username,
+ clientId,
+ requestedScopes,
+ accessTokenExpireInSeconds.toLong()
+ )
+
+ return AccessToken(
+ jwtBuilder.sign(algorithm),
+ "bearer",
+ Instant.now().plusSeconds(accessTokenExpireInSeconds.toLong()),
+ username,
+ clientId,
+ requestedScopes,
+ refreshToken
+ )
+ }
+}
\ No newline at end of file
diff --git a/oauth2-server-jwt/src/main/java/nl/myndocs/convert/JwtBuilder.kt b/oauth2-server-jwt/src/main/java/nl/myndocs/convert/JwtBuilder.kt
new file mode 100644
index 0000000..800b09b
--- /dev/null
+++ b/oauth2-server-jwt/src/main/java/nl/myndocs/convert/JwtBuilder.kt
@@ -0,0 +1,7 @@
+package nl.myndocs.convert
+
+import com.auth0.jwt.JWTCreator
+
+interface JwtBuilder {
+ fun buildJwt(username: String?, clientId: String, requestedScopes: Set, expiresInSeconds: Long): JWTCreator.Builder
+}
\ No newline at end of file
diff --git a/oauth2-server-jwt/src/main/java/nl/myndocs/convert/JwtRefreshTokenConverter.kt b/oauth2-server-jwt/src/main/java/nl/myndocs/convert/JwtRefreshTokenConverter.kt
new file mode 100644
index 0000000..e633fb8
--- /dev/null
+++ b/oauth2-server-jwt/src/main/java/nl/myndocs/convert/JwtRefreshTokenConverter.kt
@@ -0,0 +1,29 @@
+package nl.myndocs.convert
+
+import com.auth0.jwt.algorithms.Algorithm
+import nl.myndocs.oauth2.token.RefreshToken
+import nl.myndocs.oauth2.token.converter.RefreshTokenConverter
+import java.time.Instant
+
+class JwtRefreshTokenConverter(
+ private val algorithm: Algorithm,
+ private val refreshTokenExpireInSeconds: Int = 86400,
+ private val jwtBuilder: JwtBuilder = DefaultJwtBuilder
+) : RefreshTokenConverter {
+ override fun convertToToken(username: String?, clientId: String, requestedScopes: Set): RefreshToken {
+ val jwtBuilder = jwtBuilder.buildJwt(
+ username,
+ clientId,
+ requestedScopes,
+ refreshTokenExpireInSeconds.toLong()
+ )
+
+ return RefreshToken(
+ jwtBuilder.sign(algorithm),
+ Instant.now().plusSeconds(refreshTokenExpireInSeconds.toLong()),
+ username,
+ clientId,
+ requestedScopes
+ )
+ }
+}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index d3bd6c0..61c237b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -25,6 +25,7 @@
oauth2-server-javalin
oauth2-server-sparkjava
oauth2-server-http4k
+ oauth2-server-jwt