5

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

2
  • If this is for testing, can you share private key? Aug 15, 2019 at 11:38
  • I'm afraid I can't share the private key Aug 15, 2019 at 12:52

2 Answers 2

6

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 headerAndPayload = '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(headerAndPayload), bsignature, publicKey));
4
  • Thank you this is the step I managed to miss. It's working perfectly now. Aug 15, 2019 at 16:31
  • I think "payload" is a misleading name for the string variable. It confused me when trying to verify my JWT. And I can see that it confused also Bryan Anderson. IMO It should be named "headerAndPayload" or "headerDotPayload" Sep 14, 2022 at 10:05
  • why did you need to do .rightPad(math.mod(signature.length() + (math.mod(4 - signature.length(), 4)), 4)) ?
    – compski
    May 3 at 3:33
  • @compski See the paragraph about padding in RFC linked above
    – identigral
    May 3 at 4:01
-1

Simon,

Can you follow this code block and double check your results. I just copied the data provided by JWT.io by default. So replace with your data where needed and let us know your results

String algorithmName = 'RSA-SHA256';
String privateKey = 'MIIEogIBAAKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQABAoIBACiARq2wkltjtcjskFvZ7w1JAORHbEufEO1Eu27zOIlqbgyAcAl7q+/1bip4Z/x1IVES84/yTaM8p0goamMhvgry/mS8vNi1BN2SAZEnb/7xSxbflb70bX9RHLJqKnp5GZe2jexw+wyXlwaM+bclUCrh9e1ltH7IvUrRrQnFJfh+is1fRon9Co9Li0GwoN0x0byrrngU8Ak3Y6D9D8GjQA4Elm94ST3izJv8iCOLSDBmzsPsXfcCUZfmTfZ5DbUDMbMxRnSo3nQeoKGC0Lj9FkWcfmLcpGlSXTO+Ww1L7EGq+PT3NtRae1FZPwjddQ1/4V905kyQFLamAA5YlSpE2wkCgYEAy1OPLQcZt4NQnQzPz2SBJqQN2P5u3vXl+zNVKP8w4eBv0vWuJJF+hkGNnSxXQrTkvDOIUddSKOzHHgSg4nY6K02ecyT0PPm/UZvtRpWrnBjcEVtHEJNpbU9pLD5iZ0J9sbzPU/LxPmuAP2Bs8JmTn6aFRspFrP7W0s1Nmk2jsm0CgYEAyH0X+jpoqxj4efZfkUrg5GbSEhf+dZglf0tTOA5bVg8IYwtmNk/pniLG/zI7c+GlTc9BBwfMr59EzBq/eFMI7+LgXaVUsM/sS4Ry+yeK6SJx/otIMWtDfqxsLD8CPMCRvecC2Pip4uSgrl0MOebl9XKp57GoaUWRWRHqwV4Y6h8CgYAZhI4mh4qZtnhKjY4TKDjxQYufXSdLAi9v3FxmvchDwOgn4L+PRVdMwDNms2bsL0m5uPn104EzM6w1vzz1zwKz5pTpPI0OjgWN13Tq8+PKvm/4Ga2MjgOgPWQkslulO/oMcXbPwWC3hcRdr9tcQtn9Imf9n2spL/6EDFId+Hp/7QKBgAqlWdiXsWckdE1Fn91/NGHsc8syKvjjk1onDcw0NvVi5vcba9oGdElJX3e9mxqUKMrw7msJJv1MX8LWyMQC5L6YNYHDfbPF1q5L4i8j8mRex97UVokJQRRA452V2vCO6S5ETgpnad36de3MUxHgCOX3qL382Qx9/THVmbma3YfRAoGAUxL/Eu5yvMK8SAt/dJK6FedngcM3JEFNplmtLYVLWhkIlNRGDwkg3I5Ky18Ae9n7dHVueyslrb6weq7dTkYDi3iOYRW8HRkIQh06wEdbxt0shTzAJvvCQfrBjg/3747WSsf/zBTcHihTRBdAv6OmdhV4/dD5YBfLAkLrd+mX7iE=';
String publicKey = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQAB';
Blob privateKeyBlob = EncodingUtil.base64Decode(privateKey);
Blob publicKeyBlob = EncodingUtil.base64Decode(publicKey);
Blob input = Blob.valueOf('{"sub":"1234567890","name":"John Doe","admin":true,"iat":1516239022}');
Blob signature = Crypto.sign(algorithmName, input, privateKeyBlob);
Boolean verified = Crypto.verify(algorithmName, input, signature, publicKeyBlob);

System.debug(verified);

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .