Lightweight Kotlin JWT implementation (Json Web Token) designed for Apple, as required by APNs (Apple Push Notification Service) or Sign in with Apple (including JWT verification via JWK), for use on Kotlin powered backend servers. Eases the process of creating & verifying the token based on your credentials.
No other dependencies required.
- ES256
- RS256
Requires Java 14.
Add the following to your build.gradle file:
allprojects {
repositories {
maven { url 'https://jitpack.io' }
}
}
dependencies {
implementation 'com.github.PhilJay:JWT:1.2.6'
}
Or add the following to your pom.xml:
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependency>
<groupId>com.github.PhilJay</groupId>
<artifactId>JWT</artifactId>
<version>1.2.6</version>
</dependency>
Create required encoders, decoders and JSON Mapper (e.g. Gson or equivalent). These are later used to properly encode or decode the token header and payload.
val gson = GsonBuilder().create()
// generic JSON encoder
val jsonEncoder = object : JsonEncoder<JWTAuthHeader, JWTAuthPayload> {
override fun toJson(header: JWTAuthHeader): String {
return gson.toJson(header, JWTAuthHeader::class.java)
}
override fun toJson(payload: JWTAuthPayload): String {
return gson.toJson(payload, JWTAuthPayload::class.java)
}
}
// Base64 encoder using apache commons
private val encoder = object : Base64Encoder {
override fun encodeURLSafe(bytes: ByteArray): String {
return Base64.encodeBase64URLSafeString(bytes)
}
override fun encode(bytes: ByteArray): String {
return Base64.encodeBase64String(bytes)
}
}
// Base64 decoder using apache commons
private val decoder = object : Base64Decoder {
override fun decode(bytes: ByteArray): ByteArray {
return Base64.decodeBase64(bytes)
}
override fun decode(string: String): ByteArray {
return Base64.decodeBase64(string)
}
}
Create the Apple JWT token by providing your teamId, keyId and secret (private key excluding header and footer). The teamId can be obtained from the developer member center. The keyId can be obtained when you create your secret (private key).
val token = JWT.tokenApple("teamId", "keyId", "secret", jsonEncoder, encoder, decoder)
Create any JWT token by providing the required algorithm, header, payload and secret (private key):
val header = JWTAuthHeader(...)
val payload = JWTAuthPayload(...)
val token = JWT.token(Algorithm.ES256, header, payload, "secret", jsonEncoder, encoder, decoder)
If you want to decode a JWT String, create a JSON decoder:
private val jsonDecoder = object : JsonDecoder<JWTAuthHeader, JWTAuthPayload> {
override fun headerFrom(json: String): JWTAuthHeader {
return gson.fromJson(json, JWTAuthHeader::class.java)
}
override fun payloadFrom(json: String): JWTAuthPayload {
return gson.fromJson(json, JWTAuthPayload::class.java)
}
}
Use the json decoder to decode your token String:
val tokenString = "ey..." // a valid JWT as a String
val t: JWTToken<JWTAuthHeader, JWTAuthPayload>? = JWT.decode(tokenString, jsonDecoder, decoder)
// conveniently access properties of the token...
val issuer = t?.payload?.iss
In order to verify a JWT received from Sign in with Apple, securely transmit it to your backend, then obtain a JWK (Json Web Key) from Apple and use it as a public key for verification:
val jwk: JWKObject = ... // fetch current JWK (public key) from Apple endpoint
val tokenString = "ey..." // the token to validate / verify (obtained from Sign in with Apple)
// turns JWK into RSA public key, returns true if validation is successful
val valid = JWT.verify(tokenString, jwk, decoder)
Include the token in the authentication header when you make yor push notification request to APNs:
'authentication' 'bearer $token'
If you are sending pushes to iOS 13+ devices, also include the apns-push-type
header:
'apns-push-type' 'alert' // possible values are 'alert' or 'background'
For a detailed guide, please visit the APNs documentation page by Apple as well as the verifying users and generating tokens pages for Sign in with Apple. jwt.io is a good page for "debugging" tokens.