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
}
}