Support for RS256 is available since version 1.6 of djwt.
And since Deno version 1.12, RSA signatures can be verified with crypto.subtle, without the need to import external libraries.
First I show a short example with hardcoded values for the JWK and a token that I made up for this demonstration. The verification is done with the crypto.subtle functions:
import { decode } from "https://deno.land/[email protected]/encoding/base64url.ts"
const jwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IlctNjduZWt0WVRjOEpWWVBlV0g1c1dlN1JZVm5uMFN5NzQxZjhUT0pfQWMifQ.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.hiKxeC66LIyVKOXjiOk7iScFPy_5-ATw7hEfqGij8sBZmwXAeTPT5BRFYHitFKSXomGqmy_63LLvg4zbhcTTmNf8XIeDAuLsC32soO5woSByisswWHVf8BgxMkI_FPW_oEtEQ8Xv3FL_1rF9j9Oy3jIjgjqhFhXUtsSQWAeuGYH-OQljFwiuO5Bqexcw-H71OEWvQLQof_6KJ0viJyte8QEwEVridyO834-ppHzeaoW2sTvZ22ZNfxPCew0Ul2V_TxHTtO7ZuJCZ81EmeIV6dYJ2GrYh3UN1x1PHy4-tEn-PL4otlaO3PYOcXfCHxHa6xtPsquzPZJnB1Vq8zULLfQ"
// public key in JSON Web Key(JWK) format:
const pubJWK = {
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"kid": "W-67nektYTc8JVYPeWH5sWe7RYVnn0Sy741f8TOJ_Ac",
"alg": "RS256",
"n": "kFpGoVmBmmKepvBQiwq3hU9lIAuGsAPda4AVk712d3Z_QoS-5veGp4yltnyEFYyX867GOKDpbH7OF2uIjDg4-FPZwbuhiMscbkZzh25SQmfRtCT5ocUloQiopBcNAE-sd1p-ayUJWjhPrFoBrBLZHYxVEjY4JrWevQDj7kSeX7eJpud_VuZ77TNoIzj7d_iUuJUUlqF1ZF540igHKoVJJ6ujQLHh4ob8_izUuxX2iDq4h0VN3-uer59GsWw6OHgkOt85TsjMwYbeN9iw_7cNfLEYpSiH-sVHBCyKYQw7f8bKaChLxDRhUUTIEUUjGT9Ub_A3gOXq9TIi8BmbzrzVKQ"
}
// import the JWK to RSA Key
const key = await crypto.subtle.importKey(
"jwk",
pubJWK,
{name: "RSASSA-PKCS1-v1_5", hash: "SHA-256"},
true,
["verify"],
)
console.log(key)
// split the token into it's parts for verifcation
const [headerb64, payloadb64, signatureb64] = jwt.split(".")
const encoder = new TextEncoder()
const data = encoder.encode(headerb64 + '.' + payloadb64)
const signature = decode(signatureb64)
// verify the signature
const result = await crypto.subtle.verify("RSASSA-PKCS1-v1_5", key, signature, data);
console.log(result)
you can directly run the code above and get the
result
true
for successful verification.
The second example loads the JWKS (JSON Web Key Set) from the google certs endpoint, tries to find the matching key and then verifies the token when a matching key was found. Here I use djwt to verify the token after the key was imported.
The token header contains a key Id ("kid"), which identifies the key that should be used for verification.
{
"alg": "RS256",
"typ": "JWT",
"kid": "W-67nektYTc8JVYPeWH5sWe7RYVnn0Sy741f8TOJ_Ac"
}
import { verify, decode } from "https://deno.land/x/[email protected]/mod.ts"
// the JWT that we want to verify
const jwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IlctNjduZWt0WVRjOEpWWVBlV0g1c1dlN1JZVm5uMFN5NzQxZjhUT0pfQWMifQ.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.hiKxeC66LIyVKOXjiOk7iScFPy_5-ATw7hEfqGij8sBZmwXAeTPT5BRFYHitFKSXomGqmy_63LLvg4zbhcTTmNf8XIeDAuLsC32soO5woSByisswWHVf8BgxMkI_FPW_oEtEQ8Xv3FL_1rF9j9Oy3jIjgjqhFhXUtsSQWAeuGYH-OQljFwiuO5Bqexcw-H71OEWvQLQof_6KJ0viJyte8QEwEVridyO834-ppHzeaoW2sTvZ22ZNfxPCew0Ul2V_TxHTtO7ZuJCZ81EmeIV6dYJ2GrYh3UN1x1PHy4-tEn-PL4otlaO3PYOcXfCHxHa6xtPsquzPZJnB1Vq8zULLfQ"
// get the JSON Web Key Set (JWKS) from google certs endpoint
const certs = fetch("https://www.googleapis.com/oauth2/v3/certs");
var jwks = await certs.then((response) => {
return response.json()
})
// decode the JWT to get the key Id ('kid') from the header
// in Version 2.4 of djwt decode returns a 3 tuple instead of an object
const [ header, payload, signature ] = decode(jwt)
var keyId = Object(header).kid
// find the matching JSON Web Key (JWK)
var pubJWK = findJWKByKeyId(String(keyId))
// parse the JWK to RSA Key
if (pubJWK) {
// import the JWK to RSA Key
const key = await crypto.subtle.importKey(
"jwk",
pubJWK,
{name: "RSASSA-PKCS1-v1_5", hash: "SHA-256"},
true,
["verify"],
)
// verify the signature based on the given public key
try {
console.log(await verify(jwt, key))
}
catch (error)
{
console.log(error)
}
}
else
{
console.log("key with kid (" + keyId +") not found")
}
// function to find a certain JWK by its Key Id (kid)
function findJWKByKeyId(kid:string) {
return jwks.keys.find(
function(x:string){ return Object(x).kid == kid }
)
}
In the token header you see "alg": "RS256"
, but in crypto.subtle.importKey()
, {name: "RSASSA-PKCS1-v1_5", hash: "SHA-256"}
is used, which is the long form for the 'RS' in 'RS256', as Scott Brady explains.
As the given token (an example created on jwt.io) was not signed by Google, no matching key can be found and therefore it can't be verified. Use your own Google signed JWT to test the above code.
This post is a complete update of the original version, which was based on the now no longer maintained God Crypto library.