[SalesForce] Using the Crypto.verify() method to verify a JWT signature

I am trying to validate a JWT in apex by using the Crypto.verify() method, however, it always returns false despite the fact that I know it is valid. I know it valid because a) I created it and b) I have used several other sites and pieces of code to check it.

This is the crux of the code that I am using;

Blob data = Blob.valueOf('eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FTTFOa1k1TVRCRFFURTFPRFkzUkRaRVJVUTJSa1JGTmpReU56STBNRUl4TVRnM04wSkdSZyJ9.eyJpc3MiOiJodHRwczovL3N0YWdpbmctbXktb2NmLmV1LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHw1ZDM4NjE0OWJkODJhNzBlZjIzNmZkNmYiLCJhdWQiOiJLbm5zU1Zjd3c3WDhGMVRBbmNxaHNjT3lSMk5Fb28wdyIsImlhdCI6MTU2NTc3MjQ3NywiZXhwIjoxNTY1ODA4NDc3LCJhdXRoX3RpbWUiOjE1NjU3NzI0NzcsImF0X2hhc2giOiJLNWdzMkd2OGZpdjkzeVVuWWt0ZkZRIiwibm9uY2UiOiJ6MUR4YnBHSUladk5QYlh5NH5UM2ZUdnZLUE9FdXlNWCJ9');
Blob signature = Blob.valueOf('bg0PGZwF5U4ruFXKz51qpMGEBpRhBb8FF_IIyJ2PlpBg7Xm2Rrc4MtjwgCW9GMQ3PYpep08Kv5N3YB1VALH2uPjNJKdLT9XEGNn-I6x4fujYmV1A5zNKWftIscH4xHW6L3RwdhsMFJ1Bs2vSG7uv9_scrCmVm_diC2S1F-KBUFxZMX-OCVLfHTjb3-VViCx_cjrrSGhZ7Kq5vmIicU_FDJUX7UiCJYHYqScz6fy12d7lHDdhYtXEDh7SjW8jvAZlWRmifq69uLMfhafm5EN7S5eVH-_9IaFdo2a7yYNrSrS64JH_j1Z9qsk9_gb7ksNakLzddZje3UGxNX-CPi_MDA');
Blob publicKey = EncodingUtil.base64Decode('MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2PP+4VYzd2fSC7FgL75mBoPIFu6bvvNzXZQ4kBG1FqAkEcT3EraTGHlNq/iWXFpph+XJfhje0kkSOn6PKYzUd+29ZOJwtBgo8sPyi0tafpCQrRZ9y18qgW/vSr3gX6O/lobMianmCc+C8Qv72ptUVc3DdbSXxwJAJ2zkIzlJnVPKjRsWmLYhP3WpathoAxERRGjGmNkxScr6GDsMTaPV2s6mZusWyk2pgFbhWXDKD6UVGc2Wx7/IU3mNWufqRvrCD3qf7MKDRK3ZprfWghJt7x1epnPUqP2jdgq0LVeO8/cQGTwXFVHH2VJexkZ8qypuKAoNNTNLKboodVaZgigDtQIDAQAB');

System.debug(Crypto.verify('RSA-SHA256', data, signature, publicKey));

I can't see what I'm doing wrong. Right now my working assumption is that the public key is incorrect. I have extracted it from an X509 certificate so I think that it is in the correct format. For reference the JWKS for the keys that signed this data can be found here https://staging-my-ocf.eu.auth0.com/.well-known/jwks.json

So my questions are;

  • Have I derived the key correctly and is it in the X509 format expected by Crypto.verify()?
  • If that is all good then what is making verify() always return false?

For reference, I have verified the JWT using both JWT.io and JSON Web Token Verifier

Best Answer

JWT is encoded using base64 url-safe scheme so you must decode the signature appropriately when you convert it into bytes, straight base64 decoding won't do.

URL-safe base64 encoding is defined in JWS spec (RFC 7515):

  Base64 encoding using the URL- and filename-safe character set
  defined in Section 5 of RFC 4648 [RFC4648], with all trailing '='
  characters omitted (as permitted by Section 3.2) and without the
  inclusion of any line breaks, whitespace, or other additional
  characters.

RFC has a decoding example in C#. Translating to Apex:

String payload = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FTTFOa1k1TVRCRFFURTFPRFkzUkRaRVJVUTJSa1JGTmpReU56STBNRUl4TVRnM04wSkdSZyJ9.eyJpc3MiOiJodHRwczovL3N0YWdpbmctbXktb2NmLmV1LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHw1ZDM4NjE0OWJkODJhNzBlZjIzNmZkNmYiLCJhdWQiOiJLbm5zU1Zjd3c3WDhGMVRBbmNxaHNjT3lSMk5Fb28wdyIsImlhdCI6MTU2NTc3MjQ3NywiZXhwIjoxNTY1ODA4NDc3LCJhdXRoX3RpbWUiOjE1NjU3NzI0NzcsImF0X2hhc2giOiJLNWdzMkd2OGZpdjkzeVVuWWt0ZkZRIiwibm9uY2UiOiJ6MUR4YnBHSUladk5QYlh5NH5UM2ZUdnZLUE9FdXlNWCJ9';
String signature = 'bg0PGZwF5U4ruFXKz51qpMGEBpRhBb8FF_IIyJ2PlpBg7Xm2Rrc4MtjwgCW9GMQ3PYpep08Kv5N3YB1VALH2uPjNJKdLT9XEGNn-I6x4fujYmV1A5zNKWftIscH4xHW6L3RwdhsMFJ1Bs2vSG7uv9_scrCmVm_diC2S1F-KBUFxZMX-OCVLfHTjb3-VViCx_cjrrSGhZ7Kq5vmIicU_FDJUX7UiCJYHYqScz6fy12d7lHDdhYtXEDh7SjW8jvAZlWRmifq69uLMfhafm5EN7S5eVH-_9IaFdo2a7yYNrSrS64JH_j1Z9qsk9_gb7ksNakLzddZje3UGxNX-CPi_MDA';
Blob publicKey = EncodingUtil.base64Decode('MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2PP+4VYzd2fSC7FgL75mBoPIFu6bvvNzXZQ4kBG1FqAkEcT3EraTGHlNq/iWXFpph+XJfhje0kkSOn6PKYzUd+29ZOJwtBgo8sPyi0tafpCQrRZ9y18qgW/vSr3gX6O/lobMianmCc+C8Qv72ptUVc3DdbSXxwJAJ2zkIzlJnVPKjRsWmLYhP3WpathoAxERRGjGmNkxScr6GDsMTaPV2s6mZusWyk2pgFbhWXDKD6UVGc2Wx7/IU3mNWufqRvrCD3qf7MKDRK3ZprfWghJt7x1epnPUqP2jdgq0LVeO8/cQGTwXFVHH2VJexkZ8qypuKAoNNTNLKboodVaZgigDtQIDAQAB');

Blob bsignature = EncodingUtil.base64Decode(signature
                .replace('-', '+')
                .replace('_', '/')
                .rightPad(math.mod(signature.length() + (math.mod(4 - signature.length(), 4)), 4))
                .replace(' ','='));

System.debug(Crypto.verify('RSA-SHA256', Blob.valueOf(payload), bsignature, publicKey));

If you're using Auth0, there might be a better way of accomplishing your end goal versus parsing a JWT yourself. (Post a new question with your scenario)

Related Topic