How do I decode a JWT token using an RSA public key in PEM format?

Mike Chamberlain picture Mike Chamberlain · Mar 2, 2016 · Viewed 14.8k times · Source

My Java application is receiving a JWT. I have the public key in PEM format:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAixn0CGu8/M4txn4pdp8K
m8RQfVa+cHX25/a5sPmzP49u7YlQsRvtOexzgdwDcfUJm3hHMZcbZBtrHKsS8q4Q
QtGQioyVml8EaLuFNFYisaIEldVyRbXFG54FNp03vSU9ImS/cOiM9swo+1w5JgWO
F9efy7JO40LA9E7lv64COUYjFhrn+HRZuKoblL19+Sj49FyXexAUS29UM9PfIdY6
ar1FA8cxzPqW7EkXZ0Mua3IzNnYcjMvUL9TJwoLAAz9S1Tv4Is5jupy9UXkuJ4r8
Jx9DqI3Q3ur0VekYSd5tnTI4K+no9ABCFVv7+6Q45Ec2eB0xMwlqI+phcGhGMVCX
1QIDAQAB
-----END PUBLIC KEY-----

I know I can use a JwtConsumer to verify and decode the JWT:

JwtConsumer jwtConsumer = new JwtConsumerBuilder()
    .setRequireExpirationTime()
    .setVerificationKey(publicKey) // what do I pass here?
    .build();

But how do I convert my PEM file into a format understood by .setVerificationKey()?

Answer

Mike Chamberlain picture Mike Chamberlain · Mar 2, 2016

I had to first Base64 decode the public key to its constituent bytes, then encode the bytes in X509 format.

Here is a complete example showing this, and how to read some claims from the decoded JWT:

package jwt;

import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.consumer.JwtConsumer;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import sun.misc.BASE64Decoder;

import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Map;

public class JwtExample {

    public void decodeJwt(String jwt) throws Exception {

        // read public key from a file or config or something
        String publicKeyPEM =
            "-----BEGIN PUBLIC KEY-----\n" +
                "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAixn0CGu8/M4txn4pdp8K\n" +
                "m8RQfVa+cHX25/a5sPmzP49u7YlQsRvtOexzgdwDcfUJm3hHMZcbZBtrHKsS8q4Q\n" +
                "QtGQioyVml8EaLuFNFYisaIEldVyRbXFG54FNp03vSU9ImS/cOiM9swo+1w5JgWO\n" +
                "F9efy7JO40LA9E7lv64COUYjFhrn+HRZuKoblL19+Sj49FyXexAUS29UM9PfIdY6\n" +
                "ar1FA8cxzPqW7EkXZ0Mua3IzNnYcjMvUL9TJwoLAAz9S1Tv4Is5jupy9UXkuJ4r8\n" +
                "Jx9DqI3Q3ur0VekYSd5tnTI4K+no9ABCFVv7+6Q45Ec2eB0xMwlqI+phcGhGMVCX\n" +
                "1QIDAQAB\n" +
                "-----END PUBLIC KEY-----";

        // decode to its constituent bytes
        publicKeyPEM = publicKeyPEM.replace("-----BEGIN PUBLIC KEY-----\n", "");
        publicKeyPEM = publicKeyPEM.replace("-----END PUBLIC KEY-----", "");
        BASE64Decoder base64Decoder = new BASE64Decoder();
        byte[] publicKeyBytes = base64Decoder.decodeBuffer(publicKeyPEM);

        // create a key object from the bytes
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PublicKey publicKey = keyFactory.generatePublic(keySpec);

        // create a JWT consumer
        JwtConsumer jwtConsumer = new JwtConsumerBuilder()
            .setRequireExpirationTime()
            .setVerificationKey(publicKey)
            .build();

        // validate and decode the jwt
        // eg: jwt = "eyJhbGciOiJSUzI1NiJ9.eyJ1c2VybmFtZSI6Ik1DaGFtYmU0IiwiZXhwIjoxNDU2OTEwODgzLCJzY29wZSI6WyJvcGVuaWQiLCJwMnAiLCJociIsImRhcyIsIm1lIl0sImNsaWVudF9pZCI6Im1vYmlsZSIsImp0aSI6ImNZcHBMYXltVzlmNXFBZk4ifQ.QqZI9vV8IznTjN-GtUSCri9-6HH6Yl1Oae6K8-d2yjQ4fysF5d3wStdL2kMazl7xeqbtSIsw-F5Aol9eHdGAu54b9IyBEM_QIasy0lnT8xFk0Zi36NJ-7yhl_89f6SB6TGimM59xUvzXxuAw3FzWM6TbiptInrCL2TXkhS69Gng-ANPeiSITUX5A1TDInssds6ZoSb7IOUMtxPGfrbO9sBjx8aJlIu9igkqk4OX5xBmxLp3icoo98I5v9Wt_Huu7eWKBfOskMSEav4X_m5_phbAZJ_F8nWRmcxk6O7hCQdawzegnhMxP2IPIhwlWRNX_8WxkNErq2fJgdazDf8pS_Q";
        JwtClaims jwtDecoded = jwtConsumer.processToClaims(jwt);
        Map<String, Object> jwtClaims = jwtDecoded.getClaimsMap();
        String username = (String) jwtClaims.get("username"); // "MChambe4"

        // ensure the required scope is claimed
        String requiredScope = "das";
        ArrayList scopes = (ArrayList) jwtClaims.get("scope");
        // ensure claims contains the required scope
        if (!scopes.stream().anyMatch(scope -> scope == requiredScope)) {
            throw new Exception("Required scope is not claimed: " + requiredScope);
        }
    }
}