Client side verifiable Access token (Session ID) by JWT-RS512


Pros of JWT

  • validate the access token without accessing the database
  • get user ID etc from access token without accessing database

Pros of JWT-RS

  • Even outside the source of the access token, verify validity and acquire user ID etc using public key

How to generate RSA key

$ openssl genrsa 4096 > prikey.txt
$ # Generate public key
$ openssl rsa -pubout < prikey.txt > pubkey.txt
$ # Convert secret key to Java readable format
$ openssl pkcs8 -topk8 -inform PEM -in prikey.txt -out private_key.txt -nocrypt

Remarks on version of Java VM

In the case of Mac, if jdk1.8.0_25 installed as a standard is used, only up to RSA128 can be used. In order to use RSA512, you need to install jdk1.8.0_172.

Dependencies(Kotlin / Java)

// build.gradle
dependencies {
  compile 'com.auth0:java-jwt:3.4.0'
}

Read the RSA key(Kotlin / Java)

import java.security.interfaces.RSAPrivateKey
import java.security.interfaces.RSAPublicKey
import java.security.KeyFactory
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.X509EncodedKeySpec
import java.util.Base64


fun loadRSAKeyPair(publicKeyContent: String, privateKeyContent: String): Pair<RSAPublicKey, RSAPrivateKey> {
  // Remove -----BEGIN ... KEY-----, -----END ... KEY-----
  val pub = publicKeyContent.replace("^-+.+?-$".toRegex(RegexOption.MULTILINE), "").replace("\\s".toRegex(RegexOption.MULTILINE), "")
  val pri = privateKeyContent.replace("^-+.+?-$".toRegex(RegexOption.MULTILINE), "").replace("\\s".toRegex(RegexOption.MULTILINE), "")

  val kf = KeyFactory.getInstance("RSA")

  val keySpecPKCS8 = PKCS8EncodedKeySpec(Base64.getDecoder().decode(pri))
  val privateKey = kf.generatePrivate(keySpecPKCS8) as RSAPrivateKey

  val keySpecX509 = X509EncodedKeySpec(Base64.getDecoder().decode(pub))
  val publicKey = kf.generatePublic(keySpecX509) as RSAPublicKey
  return Pair(pubKey, privateKey)
}

fun loadRSAPublicKey(publicKeyContent: String): RSAPublicKey {
  val pub = publicKeyContent.replace("^-+.+?-$".toRegex(RegexOption.MULTILINE), "").replace("\\s".toRegex(RegexOption.MULTILINE), "")
  val kf = KeyFactory.getInstance("RSA")
  val keySpecX509 = X509EncodedKeySpec(Base64.getDecoder().decode(pub))
  return kf.generatePublic(keySpecX509) as RSAPublicKey
}

Generate Access Token(Kotlin / Java)

import com.auth0.jwt.algorithms.Algorithm
import com.auth0.jwt.JWT

fun generateAccessToken(user: User, issuer: String, publicKeyContent: String, privateKeyContent: String): String {
  val (publicKey, privateKey) = loadRSAKeyPair(publicKeyContent, privateKeyContent)
  val algorithm = Algorithm.RSA512(publicKey, privateKey)
  // expiration date (Overflow if you do not use Long)
  val expireMilliSec = 60L * 60L * 1L * 1000L
  return JWT.create()
          .withIssuer(issuer)          
          .withExpiresAt(Date(Date().time + expireMilliSec))
          .withClaim("uid", user.id)
          .sign(algorithm)
}

Verify Access Token(Kotlin / Java)

import com.auth0.jwt.exceptions.TokenExpiredException
import com.auth0.jwt.exceptions.JWTVerificationException

private fun getUserId(accessToken:String):Long {
  val jwt = decodeAccessToken(accessToken, issuer, publicKeyContent) ?: return null
  return jwt.getClaim("uid")?.asLong()
}

private fun decodeAccessToken(accessToken: String, issuer: String, publicKeyContent: String): DecodedJWT? {
  val publicKey = loadRSAPublicKey(publicKeyContent)
  val algorithm = Algorithm.RSA512(publicKey, null)
  val verifier = JWT.require(algorithm)
          .withIssuer(issuer)
          .build()
  try {
      return verifier.verify(accessToken)
  } catch (ex: TokenExpiredException) {
      return null
  } catch (ex: JWTVerificationException) {
      return null
  }
}

Dependencies(Node.js)

$ yarn add jsonwebtoken

Verify Access Token(Node.js)

const jwt = require('jsonwebtoken')
const util = require('util')

const verifyAccessToken = async (accessToken) => {
  if (!accessToken) { return null }
  try {
    const token = await util.promisify(jwt.verify)(accessToken, publicKeyContent, {
      algorithms: 'RS512'
    })
    return {
      userId: token.uid,
    }
  } catch (ex) {
    return null
  }
}

See also