JWT tokens in Kotlin: complete guide with the jjwt library
In modern web development, JWT (JSON Web Tokens) have become the standard for authentication and information exchange between client and server. In the Kotlin ecosystem, especially when building backend applications with Spring Boot or Ktor, the need to work with JWT often arises. In this article, we will explore the io.jsonwebtoken (jjwt) library — the most popular tool for working with JWT in Java/Kotlin.
What is JWT?
JWT is a compact, URL-safe way to represent claims between two parties. The token consists of three parts separated by dots:
- Header — contains the token type and signing algorithm (e.g., HS256, RS256).
- Payload — contains the claims:
sub(subject),iat(issued at),exp(expiration time), and custom data. - Signature — created using a secret key or a key pair.
Adding the library
To work with JWT in Kotlin, we will use jjwt (io.jsonwebtoken). Add the dependency to build.gradle.kts:
dependencies { implementation("io.jsonwebtoken:jjwt-api:0.12.6") runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.6") runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.6") // for JSON parsing}Generating a JWT token
To create a token, you need to specify a secret key, set claims, and sign the token. Example in Kotlin:
import io.jsonwebtoken.Jwtsimport io.jsonwebtoken.security.Keysimport java.util.Dateimport javax.crypto.SecretKey
fun generateToken(userId: String, secret: String): String { val key: SecretKey = Keys.hmacShaKeyFor(secret.toByteArray()) val now = Date() val expiration = Date(now.time + 3600_000) // +1 hour return Jwts.builder() .subject(userId) .issuedAt(now) .expiration(expiration) .claim("role", "USER") .signWith(key) .compact()}Verifying and parsing JWT
When receiving a token from the client, you need to verify its signature and expiration. Use Jwts.parser():
import io.jsonwebtoken.JwtExceptionimport io.jsonwebtoken.Jwtsimport io.jsonwebtoken.security.Keysimport javax.crypto.SecretKey
fun validateToken(token: String, secret: String): Boolean { return try { val key: SecretKey = Keys.hmacShaKeyFor(secret.toByteArray()) Jwts.parser() .verifyWith(key) .build() .parseSignedClaims(token) true } catch (e: JwtException) { false }}
fun extractUserId(token: String, secret: String): String? { return try { val key: SecretKey = Keys.hmacShaKeyFor(secret.toByteArray()) val claims = Jwts.parser() .verifyWith(key) .build() .parseSignedClaims(token) .payload claims.subject } catch (e: Exception) { null }}Working with custom claims
You can add any arbitrary data to the payload. For example, store the user's email or role:
fun generateTokenWithCustomClaims(userId: String, email: String, secret: String): String { val key = Keys.hmacShaKeyFor(secret.toByteArray()) return Jwts.builder() .subject(userId) .claim("email", email) .claim("isAdmin", false) .issuedAt(Date()) .expiration(Date(System.currentTimeMillis() + 3600_000)) .signWith(key) .compact()}
fun extractCustomClaim(token: String, secret: String): String? { val key = Keys.hmacShaKeyFor(secret.toByteArray()) Also in library
Internationalization (i18n) in Go (Golang): the go-i18n library
Moment.js / Day.js for JavaScript (Node.js): Complete Guide with Examples
Caching in PHP: Redis, Memcached, and APCu — Complete Guide