https://github.com/touchlab/kjwt
https://github.com/touchlab/kjwt
Last synced: about 1 month ago
JSON representation
- Host: GitHub
- URL: https://github.com/touchlab/kjwt
- Owner: touchlab
- License: apache-2.0
- Created: 2026-03-09T13:07:14.000Z (2 months ago)
- Default Branch: main
- Last Pushed: 2026-04-03T22:57:47.000Z (about 1 month ago)
- Last Synced: 2026-04-04T00:30:42.547Z (about 1 month ago)
- Language: Kotlin
- Homepage: https://touchlab.github.io/kjwt/
- Size: 2.2 MB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE.txt
Awesome Lists containing this project
README
# KJWT

[](https://search.maven.org/artifact/co.touchlab/kjwt)




A JSON Web Token (JWT) is a compact, URL-safe standard for representing data to be transferred between two parties. The
data within a JWT - referred to as claims - is encoded as a JSON object that serves as the payload for either a JSON Web
Signature (JWS) or a JSON Web Encryption (JWE) structure. This design allows claims to be digitally signed for
integrity (using a Message Authentication Code or MAC) and/or encrypted for privacy.
The most common JWT format is a string composed of three dot-separated segments: header, payload, and signature. This
structure represents a JWS, as defined by RFC 7515. In this format, the header and payload are Base64URL-encoded,
meaning they can be easily decoded by anyone to reveal their contents. The third segment, the signature, is also
Base64URL-encoded and is used to verify that the first two parts of the token have not been tampered with.
While less common, JWTs can also follow the JWE format defined by RFC 7516, which consists of five dot-separated parts:
header, encrypted key, initialization vector, ciphertext, and authentication tag. Unlike a JWS, where data is merely
signed, a JWE encrypts the data. While the header remains decodable, the remaining segments are encrypted and cannot be
read without the appropriate cryptographic key.
### How about the other formats and RFCs?
The JWT is just a part of the JOSE family of standards. JOSE stands for JSON Object Signing and Encryption, and it
groups several related RFCs that define how to sign, encrypt, and manage keys for JSON data. To support all of these
features, the JOSE family includes a few different standards, such as:
- [RFC 7519 (JWT)](https://datatracker.ietf.org/doc/html/rfc7519) - JSON Web Token
- [RFC 7515 (JWS)](https://datatracker.ietf.org/doc/html/rfc7515) - JSON Web Signature
- [RFC 7516 (JWE)](https://datatracker.ietf.org/doc/html/rfc7516) - JSON Web Encryption
- [RFC 7517 (JWK)](https://datatracker.ietf.org/doc/html/rfc7517) - JSON Web Key
- [RFC 7518 (JWA)](https://datatracker.ietf.org/doc/html/rfc7518) - JSON Web Algorithms
- [RFC 7520 (JOSE)](https://datatracker.ietf.org/doc/html/rfc7520) - JSON Object Signing and Encryption (JOSE)
- [RFC 7638 (JWK Thumbprint)](https://datatracker.ietf.org/doc/html/rfc7638) - JWK Thumbprint
While the JWT is a specific format for representing claims (payload), the JOSE standards provide the tools and
specifications for creating, signing, encrypting, and managing those claims in a secure and interoperable way. The JWT
is just one of the possible formats for representing claims, and it is designed to be compact and URL-safe.
When we first conceived KJWT, our goal was to support the JWS format of JWTs. However, as we developed the library, we
realized that supporting the full range of JOSE standards would provide a more robust solution for users.
Therefore, we decided to implement support for JWS, JWE, JWK, and JWA in addition to JWT. This allows KJWT to be a
comprehensive library for working with JSON Web Tokens and related standards.
That said, our plan is not to implement all the RFCs. We will focus our efforts on implementing the ones that are
necessary and relevant for JWT use cases. Some RFCs that are not relevant, or that explicitly state they should not be
used with JWTs, may not be implemented. One example is the
[RFC-7797 - JSON Web Signature (JWS) Unencoded Payload Option](https://www.rfc-editor.org/rfc/rfc7797.html) which
states in section 7 that it should not be used with JWTs.
## Features
As of now, the library supports the following operations:
| Operations | Signing Algorithms | Encryption Algorithms | Platforms |
|-----------------|--------------------|---------------------------|---------------------------------------------|
| ✅ Sign | ✅ HS256 | ✅ RSA-OAEP `(alg)` | ✅ JVM (incl. Android) |
| ✅ Verify | ✅ HS384 | ✅ RSA-OAEP-256 `(alg)` | ✅ JS (node + browser)⁴ |
| ✅ `iss` check¹ | ✅ HS512 | ✅ dir `(alg)` | ✅ wasmJs (node + browser)⁴ |
| ✅ `sub` check¹ | ✅ RS256 | ❌ A128KW `(alg)` | ❌ wasmWasi⁵ |
| ✅ `aud` check¹ | ✅ RS384 | ❌ A192KW `(alg)` | ✅ iOS (arm64, x64, simulatorArm64)⁶ |
| ✅ `exp` check | ✅ RS512 | ❌ A256KW `(alg)` | ✅ macOS (x64, arm64)⁶ |
| ✅ `nbf` check | ✅ ES256 | ❌ ECDH-ES `(alg)` | ✅ watchOS (x64, arm32, arm64, sim, device)⁶ |
| ⚠️ `iat` check² | ❌ ES256K | ✅ A128GCM `(enc)` | ✅ tvOS (x64, arm64, sim)⁶ |
| ⚠️ `jti` check² | ✅ ES384 | ⚠️ A192GCM `(enc)`⁴ | ✅ Linux (x64, arm64) |
| ❌ `typ` check | ✅ ES512 | ✅ A256GCM `(enc)` | ✅ Windows/MinGW (x64) |
| | ✅ PS256³ | ✅ A128CBC-HS256 `(enc)` | ✅ Android Native (x64, x86, arm64, arm32) |
| | ✅ PS384³ | ⚠️ A192CBC-HS384 `(enc)`⁴ | |
| | ✅ PS512³ | ✅ A256CBC-HS512 `(enc)` | |
| | ❌ EdDSA | | |
> ¹ Opt-in: call `requireIssuer()` / `requireSubject()` / `requireAudience()` on the parser builder. A missing claim
> throws `MissingClaimException`; a mismatched value throws `IncorrectClaimException`.
>
> ² Accessible via `payload.issuedAtOrNull` / `payload.jwtIdOrNull` but not automatically validated. Use the generic
`requireClaim()` for custom validation.
>
> ³ PS256 / PS384 / PS512 are not supported by Android's default JDK security provider. Register BouncyCastle as the
> security provider to enable them. Android Native targets use OpenSSL3 and are unaffected.
>
> ⁴ JS and wasmJs use WebCrypto, which does not support 192-bit AES keys. `A192GCM` and `A192CBC-HS384` are unavailable
> on these platforms.
>
> ⁵ wasmWasi has no `cryptography-kotlin` provider. The library compiles for this target but all cryptographic
> operations throw at runtime.
>
> ⁶ Apple targets (iOS, macOS, watchOS, tvOS): use `cryptography-provider-optimal` for full algorithm support (CryptoKit
> for AES-GCM; Apple/CommonCrypto for AES-CBC+HMAC and RSA). `cryptography-provider-openssl3-prebuilt` also supports all
> algorithms and is a good choice when a single consistent provider is needed across Apple, Linux, and Android Native.
> With only `cryptography-provider-cryptokit`, RSA and AES-CBC algorithms are unavailable. With only
> `cryptography-provider-apple`, AES-GCM algorithms are unavailable.
---
## Setup
Add the library to your project and register a cryptography provider. The `cryptography-provider-optimal` artifact
auto-registers on startup and is the recommended choice:
```kotlin
// build.gradle.kts
dependencies {
implementation("co.touchlab:kjwt-core:")
// For usage with `Cryptography-Kotlin`, include the processor and the providers for it:
implementation("co.touchlab:kjwt-cryptography-kotlin-processor:")
implementation("dev.whyoleg.cryptography:cryptography-provider-optimal:")
// Optional: Some extensions exist for the cryptography-kotlin library
implementation("co.touchlab:kjwt-cryptography-kotlin-processor-ext:")
// Mover providers will be added soon
}
```
### Snapshot builds
Every merge to `main` is automatically published to the Maven snapshot repository. To use a snapshot version, add the
repository and use the `-SNAPSHOT` suffix:
```kotlin
// settings.gradle.kts
dependencyResolutionManagement {
repositories {
maven("https://central.sonatype.com/repository/maven-snapshots") {
mavenContent {
includeModuleByRegex("co\\.touchlab", "kjwt.*")
}
}
}
}
```
```kotlin
// build.gradle.kts
dependencies {
implementation("co.touchlab:kjwt:-SNAPSHOT") {
isChanging = true
}
}
```
## Why another library?
There are two common reference points in the KMP ecosystem:
[Signum](https://a-sit-plus.github.io/signum/) is a community KMP library that supports many cryptographic operations,
including JWT. It is more feature-rich and supports many algorithms, but it requires Android SDK 30+ — a hard limit
for apps that need to support older devices.
[Cryptography Kotlin](https://whyoleg.github.io/cryptography-kotlin/) takes a different approach: it is a low-level
library whose goal is to expose cryptographic primitives across KMP targets, not to implement protocols on top of them.
It is an excellent building block, but it does not give you a JWT API.
KJWT fills that gap. It provides a high-level, easy-to-use JWT API (JWS and JWE) that works across all major KMP
platforms, with a migration-friendly design for teams moving from JJWT on the JVM.
We started by building KJWT directly on top of Cryptography Kotlin, and it remains the recommended crypto backend.
Over time, however, we decoupled the JWT logic from any specific crypto implementation. The cryptographic layer is now
pluggable: if your project already uses a different library for cryptography, you can write a thin processor for it and
plug it into KJWT without changing anything else. This makes KJWT useful even in projects where the crypto stack is
already decided.
One concrete motivation for this design was hardware-backed cryptography. Signum supports hardware-backed operations,
which is a compelling feature — but it comes at the cost of requiring Android SDK 30+. With KJWT's pluggable
architecture, you can implement a processor backed by hardware security when your app's minimum SDK allows it, while
users on older Android versions continue to use a standard software-backed provider. The JWT layer stays the same
regardless of which processor is underneath.
Going forward, we plan to ship first-party processors for hardware-backed cryptography and other provider types, so
you get the benefits of those integrations without having to implement them yourself.
Aren't there enough standards already?
[Yes.](https://xkcd.com/927/)
## Usage
All actions were designed to be chainable, and start from the `Jwt` (`import co.touchlab.kjwt.Jwt`) object. It is the
entrypoint for most JWT operations. In that object, you will find methods to `build` and `parse` JWTs.
### Building a JWT
The most common usage of JWTs is to generate signed tokens. You can achieve this by using the `.signWith(...)` method
when building a JWT.
```kotlin
val signingKey = SigningAlgorithm.HS256.newKey()
val token: JwtInstance = Jwt.builder()
.subject("1234567890")
.signWith(signingKey)
```
The result of the operation is a `JwtInstance` object. That object is a Kotlin representation of the JWT. You can use
it to access the defined claims and headers, as well as generate the famous compact version of the JWT.
```kotlin
val token: JwtInstance = // build the token as shown above
val serialized: String = token.compact()
// This call will generate the string version of the JWT, in the compact format
// Note: the compact format is the one split by the dots, with the header, payload and signature encoded in Base64URL
// format. It will look like this:
// .. 👈This is the compact format template 👇And this is a real example
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.yaquqtp1qJ9uDVaWMdRtujneuFqIBkImorBu9hdLVl4
```
### Parsing a JWT
Another common usage of JWTs is to verify the authenticity of a token. If you are a backend application, you need to
ensure that the user hasn't modified the token on their side. That is achieved by verifying the signature of the token.
```kotlin
val compactToken: String = //
val jwtParser = Jwt.parser()
.verifyWith(signingKey)
.build()
val parsedToken = jwtParser.parse(compactToken)
```
Note that if the content has been changed on the client side, the signature will be invalid and the parsing will throw
an
exception. If the parse succeeds, the token is valid and ready to be used.
```kotlin
// Use content from the JWT:
val subject = parsedToken.payload.subjectOrNull
```
### Keys
As you probably noticed, we skipped the keys part in the previous examples. The main reason for that is that we do not
implement any cryptographic operations. Instead, we rely on
the [Cryptography Kotlin](https://github.com/whyoleg/cryptography-kotlin) library. It's an amazing and robust library
that provides a wide range of cryptographic operations, and providers for most of the Kotlin Multiplatform targets.
KJWT ships extension functions on each algorithm object that generate or decode keys without
requiring you to touch the `cryptography-kotlin` API directly:
```kotlin
import co.touchlab.kjwt.model.algorithm.SigningAlgorithm
// Generate a new random HMAC key
val key = SigningAlgorithm.HS256.newKey()
// Decode an HMAC key from existing bytes
val key = SigningAlgorithm.HS256.parse(myKeyBytes)
// Generate an RSA key pair (also available for RS384/RS512, PS*, ES*)
val key = SigningAlgorithm.RS256.newKey()
// Decode individual RSA/ECDSA keys
val key = SigningAlgorithm.RS256.parsePublicKey(pemBytes) // verify only
val key = SigningAlgorithm.RS256.parsePrivateKey(pemBytes) // sign only
val key = SigningAlgorithm.RS256.parseKeyPair(pubPem, privPem)
```
The returned `SigningKey` can be passed directly to `signWith` or `verifyWith`:
```kotlin
val key = SigningAlgorithm.HS256.parse(myKeyBytes)
val token: JwtInstance = Jwt.builder()
.subject("1234567890")
.signWith(key) // Use the key to sign the token
val jwtParser = Jwt.parser()
.verifyWith(key) // Use the key to verify the token
.build()
val compactToken: String = // ...
val parsedToken = jwtParser.parse(compactToken) // Token will get verified using the key used in the builder
```
If you prefer to work with `cryptography-kotlin` directly, you can also construct keys manually
using its API and pass them to `signWith` / `verifyWith`. For a full reference of all key helper
overloads, see the [usage guide](docs/usage.md#keys).
### More features
For a more detailed list of features, check out the usage documentation available at the [docs](docs/USAGE.md).