JWT icon indicating copy to clipboard operation
JWT copied to clipboard

Kotlin JWT 🔑 implementation (Json Web Token) as required by APNs 🔔 (Apple Push Notifications) or Sign in with Apple 🍏

Release

JWT

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.

Algorithms supported

  • ES256
  • RS256

Dependency

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>

Creating JWT

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)

Decoding JWT

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

Verifying

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) 

Usage with APNs

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'

Documentation

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.