Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to verify a signed JWT with SubtleCrypto of the Web Crypto API?

I'm trying to verify the signature of a JWT using the SubtleCrypto interface of the Web Crypto API.

My code will not verify the token signature while the debug tool at JWT.io will and I don't know why. Here is my verify function:

function verify (jwToken, jwKey) {
  const partialToken = jwToken.split('.').slice(0, 2).join('.')
  const signaturePart = jwToken.split('.')[2]
  const encoder = new TextEncoder()
  return window.crypto.subtle
    .importKey('jwk', jwKey, { 
         name: 'RSASSA-PKCS1-v1_5', 
         hash: { name: 'SHA-256' } 
       }, false, ['verify'])
    .then(publicKey =>
      window.crypto.subtle.verify(
        { name: 'RSASSA-PKCS1-v1_5' },
        publicKey,
        encoder.encode(atob(signaturePart)),
        encoder.encode(partialToken)
      ).then(isValid => alert(isValid ? 'Valid token' : 'Invalid token'))
    )
}

I expected that code to work and provide a positive verification of a properly signed JWT. Instead the example code fail to verify the signed token. The example fail in Chrome 71 for me.

I have also set up some tests using the example data from RFC 7520.

like image 867
Anders Dahnielson Avatar asked Jan 27 '23 22:01

Anders Dahnielson


1 Answers

To verify a JWS with SubtleCrypto you need to be careful to encode and decode the data properly between binary and base64url representation. Unfortunately the standard implementation in the browser of btoa() and atob() are difficult to work with as they use "a Unicode string containing only characters in the range U+0000 to U+00FF, each representing a binary byte with values 0x00 to 0xFF respectively" as the representation of binary data.

A better solution to represent binary data in Javascript is to use ES6 object TypedArray and use a Javascript library (or write the encoder yourself) to convert them to base64url that honors RFC 4648.

Side note: The difference between base64 and base64url is the characters selected for value 62 and 63 in the standard, base64 encode them to + and / while base64url encode - and _.

An example of such a library in Javascript is rfc4648.js.

import { base64url } from 'rfc4648'

async function verify (jwsObject, jwKey) {
  const jwsSigningInput = jwsObject.split('.').slice(0, 2).join('.')
  const jwsSignature = jwsObject.split('.')[2]
  return window.crypto.subtle
    .importKey('jwk', jwKey, { 
         name: 'RSASSA-PKCS1-v1_5', 
         hash: { name: 'SHA-256' } 
       }, false, ['verify'])
    .then(key=>
      window.crypto.subtle.verify(
        { name: 'RSASSA-PKCS1-v1_5' },
        key,
        base64url.parse(jwsSignature, { loose: true }),
        new TextEncoder().encode(jwsSigningInput))
      ).then(isValid => alert(isValid ? 'Valid token' : 'Invalid token'))
    )
}
like image 72
Anders Dahnielson Avatar answered Jan 29 '23 12:01

Anders Dahnielson