How to verify a JWT with RS256 signature in Deno?

半腔热情 提交于 2021-02-08 10:11:21

问题


I want to verify a signature from a Google JWT which uses RS256 as signature algorithm as of right now (Certs from Google: https://www.googleapis.com/oauth2/v3/certs), and the only libary which i could find for Deno handles HS256 (https://deno.land/x/djwt).

I am really not into the whole Cipher game, maybe anybody got an idea how i can verify the signature maybe there already is something with an example? I really don't know what i need to hash with SHA-256 or how i use RSA, when i try to look up how to implement this, I see a lot of technical explanation but no real examples on what to do with what.

I usually just used Googles Scriptpackage on Node see: https://developers.google.com/identity/sign-in/web/backend-auth

I have functions to hash with SHA-256 but nothing about RSA?


回答1:


Support for RS256 is available since version 1.6 of djwt.

However, here I'm using a crypto module named God Crypto to verify a RS256 signed token. God Crypto has a function to parse a JSON Web Key (JWK), a feature that we need to verify a JWT provided by Google.

First I show a short example with hardcoded values for the JWK and a token that I made up for this demonstration:

import { RSA, encode } from "https://deno.land/x/god_crypto@v1.4.8/mod.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"
}

// parse the JWK to RSA Key
const publicKey = RSA.parseKey(pubJWK)
const rsa = new RSA(publicKey)

// split the token into it's parts for verifcation
const [headerb64, payloadb64, signatureb64] = jwt.split(".")

// verify the signature based on the given public key
console.log(await rsa.verify(
    encode.base64url(signatureb64),
    headerb64 + "." + payloadb64,
    { algorithm: "rsassa-pkcs1-v1_5", hash: "sha256" },
  ))

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.

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 { RSA, encode } from "https://deno.land/x/god_crypto@v1.4.8/mod.ts";
import { decode } from "https://deno.land/x/djwt@v2.2/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.2 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) {
    const publicKey = RSA.parseKey(pubjwk)
    const rsa = new RSA(publicKey)

    // split the token into it's parts for verifcation
    const [headerb64, payloadb64, signatureb64] = jwt.split(".")

    // verify the signature based on the given public key
    console.log(await rsa.verify(
        encode.base64url(signatureb64),
        headerb64 + "." + payloadb64,
        { algorithm: "rsassa-pkcs1-v1_5", hash: "sha256" },
    ))
}
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 rsa.verify(), algorithm: "rsassa-pkcs1-v1_5"is used, which is ithe 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.

Parts of the verification code are based on examples from the God Crypto Github page




回答2:


Let's try this code, more details visit this page jwt authentication in Deno

import { Context } from "https://deno.land/x/oak/mod.ts";
import users from "./users.ts";
import { makeJwt, setExpiration, Jose, Payload } from "https://deno.land/x/djwt/create.ts"
import key from './key.ts'

const header: Jose = {
  alg: "HS256",
  typ: "JWT",
}

export const login = async (ctx: Context) => {
  const {value} = await ctx.request.body();
  for (const user of users) {
    if (value.username === user.username && value.password === user.password) {
      const payload: Payload = {
        iss: user.username,
        exp: setExpiration(new Date().getTime() + 60000),
      }

      // Create JWT and send it to user
      const jwt = makeJwt({key, header, payload});
      if (jwt) {
        ctx.response.status = 200;
        ctx.response.body = {
          id: user.id,
          username: user.username,
          jwt,
        }
      } else {
        ctx.response.status = 500;
        ctx.response.body = {
          message: 'Internal server error'
        }
      }
      return;
    }
  }

  ctx.response.status = 422;
  ctx.response.body = {
    message: 'Invalid username or password'
  };
};


来源:https://stackoverflow.com/questions/62228271/how-to-verify-a-jwt-with-rs256-signature-in-deno

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!