hoverfly icon indicating copy to clipboard operation
hoverfly copied to clipboard

JWT matcher

Open qcastel opened this issue 5 years ago • 1 comments

Is your feature request related to a problem? Please describe. A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

We got some request that instead of containing a JSON payload, contains a JWT payload. What would be interesting is to offer a way to introspect the JWT token and compare the payload and or header. As the payload and header of a JWT are base64 JSON, we could fallback to a json matcher.

Describe the solution you'd like

Here is an example of JWT:

eyJraWQiOiJlcGwyT09Mem82WHlCWExDeFR4dUZ6SnJEQ0kiLCJhbGciOiJQUzI1NiJ9.eyJ0b2tlbl9lbmRwb2ludF9hdXRoX3NpZ25pbmdfYWxnIjoiUFMyNTYiLCJyZXF1ZXN0X29iamVjdF9lbmNyeXB0aW9uX2FsZyI6IlJTQS1PQUVQLTI1NiIsImdyYW50X3R5cGVzIjpbImF1dGhvcml6YXRpb25fY29kZSIsInJlZnJlc2hfdG9rZW4iLCJjbGllbnRfY3JlZGVudGlhbHMiXSwiYXBwbGljYXRpb25fdHlwZSI6IndlYiIsImlzcyI6IjNjUEZiangwdVRsVWdFeWREaTQ3U3YiLCJyZWRpcmVjdF91cmlzIjpbImh0dHBzOlwvXC9sb2NhbC1hdXRoLnlhcGlseS5jb206ODA4MVwvIl0sInRva2VuX2VuZHBvaW50X2F1dGhfbWV0aG9kIjoicHJpdmF0ZV9rZXlfand0IiwiYXVkIjoiMDAxNTgwMDAwMTA0MVJFQUFZIiwic29mdHdhcmVfaWQiOiIzY1BGYmp4MHVUbFVnRXlkRGk0N1N2Iiwic29mdHdhcmVfc3RhdGVtZW50IjoiZXlKaGJHY2lPaUpRVXpJMU5pSXNJbXRwWkNJNklraDZZVGwyTldKblJFcGpUMjVvWTFWYU4wSk5kMkpUVEY4MFRsWXdaMU5HZGtscVlWTllaRU10TVdNOUlpd2lkSGx3SWpvaVNsZFVJbjAuZXlKcGMzTWlPaUpQY0dWdVFtRnVhMmx1WnlCTWRHUWlMQ0pwWVhRaU9qRTFPREl4T1Rrek9USXNJbXAwYVNJNklqVmtOamMyTkdJMllqVTBZVFEwTTJFaUxDSnpiMlowZDJGeVpWOWxiblpwY205dWJXVnVkQ0k2SW5OaGJtUmliM2dpTENKemIyWjBkMkZ5WlY5dGIyUmxJam9pVkdWemRDSXNJbk52Wm5SM1lYSmxYMmxrSWpvaU0yTlFSbUpxZURCMVZHeFZaMFY1WkVScE5EZFRkaUlzSW5OdlpuUjNZWEpsWDJOc2FXVnVkRjlwWkNJNklqTmpVRVppYW5nd2RWUnNWV2RGZVdSRWFUUTNVM1lpTENKemIyWjBkMkZ5WlY5amJHbGxiblJmYm1GdFpTSTZJbEYxWlc1MGFXNGdWR1Z6ZENJc0luTnZablIzWVhKbFgyTnNhV1Z1ZEY5a1pYTmpjbWx3ZEdsdmJpSTZJa1p2Y2lCMFpYTjBJSEIxY25CdmMyVnpJaXdpYzI5bWRIZGhjbVZmZG1WeWMybHZiaUk2TVM0eExDSnpiMlowZDJGeVpWOWpiR2xsYm5SZmRYSnBJam9pYUhSMGNITTZMeTkzZDNjdWVXRndhV3g1TG1OdmJTSXNJbk52Wm5SM1lYSmxYM0psWkdseVpXTjBYM1Z5YVhNaU9sc2lhSFIwY0hNNkx5OWtaWFpsYkc5d2JXVnVkQzFoZFhSb0xubGhjR2xzZVM1amIyMHZJaXdpYUhSMGNITTZMeTl6ZEdGbmFXNW5MV0YxZEdndWVXRndhV3g1TG1OdmJTOGlMQ0pvZEhSd2N6b3ZMMnh2WTJGc0xXRjFkR2d1ZVdGd2FXeDVMbU52YlRvNE1EZ3lMeUlzSW1oMGRIQnpPaTh2WVhWMGFDNTVZWEJwYkhrdVkyOXRMeUlzSW1oMGRIQnpPaTh2Ykc5allXd3RZWEJwTG5saGNHbHNlUzVqYjIwNk9EQTRNUzhpTENKb2RIUndjem92TDJ4dlkyRnNMV0YxZEdndWVXRndhV3g1TG1OdmJUbzRNRGd4THlKZExDSnpiMlowZDJGeVpWOXliMnhsY3lJNld5SkJTVk5RSWl3aVVFbFRVQ0pkTENKdmNtZGhibWx6WVhScGIyNWZZMjl0Y0dWMFpXNTBYMkYxZEdodmNtbDBlVjlqYkdGcGJYTWlPbnNpWVhWMGFHOXlhWFI1WDJsa0lqb2lUMEpIUWxJaUxDSnlaV2RwYzNSeVlYUnBiMjVmYVdRaU9pSlZibXR1YjNkdU1EQXhOVGd3TURBd01UQXpWVUZ5UVVGTklpd2ljM1JoZEhWeklqb2lRV04wYVhabElpd2lZWFYwYUc5eWFYTmhkR2x2Ym5NaU9sdDdJbTFsYldKbGNsOXpkR0YwWlNJNklrZENJaXdpY205c1pYTWlPbHNpUVVsVFVDSXNJbEJKVTFBaVhYMHNleUp0WlcxaVpYSmZjM1JoZEdVaU9pSkpSU0lzSW5KdmJHVnpJanBiSWtGSlUxQWlMQ0pRU1ZOUUlsMTlMSHNpYldWdFltVnlYM04wWVhSbElqb2lUa3dpTENKeWIyeGxjeUk2V3lKQlNWTlFJaXdpVUVsVFVDSmRmVjE5TENKemIyWjBkMkZ5WlY5c2IyZHZYM1Z5YVNJNkltaDBkSEJ6T2k4dmQzZDNMbmxoY0dsc2VTNWpiMjBpTENKdmNtZGZjM1JoZEhWeklqb2lRV04wYVhabElpd2liM0puWDJsa0lqb2lNREF4TlRnd01EQXdNVEF6VlVGeVFVRk5JaXdpYjNKblgyNWhiV1VpT2lKWllYQnBiSGtnVEhSa0lpd2liM0puWDJOdmJuUmhZM1J6SWpwYmV5SnVZVzFsSWpvaVZHVmphRzVwWTJGc0lpd2laVzFoYVd3aU9pSnplWE4wWlcxeksyOXdaVzVpWVc1cmFXNW5RSGxoY0dsc2VTNWpiMjBpTENKd2FHOXVaU0k2SWlzME5EYzBNamt6TWpFME5qVWlMQ0owZVhCbElqb2lWR1ZqYUc1cFkyRnNJbjBzZXlKdVlXMWxJam9pUW5WemFXNWxjM01pTENKbGJXRnBiQ0k2SW5ONWMzUmxiWE1yYjNCbGJtSmhibXRwYm1kQWVXRndhV3g1TG1OdmJTSXNJbkJvYjI1bElqb2lLelEwTnpReU9UTXlNVFEyTlNJc0luUjVjR1VpT2lKQ2RYTnBibVZ6Y3lKOVhTd2liM0puWDJwM2EzTmZaVzVrY0c5cGJuUWlPaUpvZEhSd2N6b3ZMMnRsZVhOMGIzSmxMbTl3Wlc1aVlXNXJhVzVuZEdWemRDNXZjbWN1ZFdzdk1EQXhOVGd3TURBd01UQXpWVUZ5UVVGTkx6QXdNVFU0TURBd01ERXdNMVZCY2tGQlRTNXFkMnR6SWl3aWIzSm5YMnAzYTNOZmNtVjJiMnRsWkY5bGJtUndiMmx1ZENJNkltaDBkSEJ6T2k4dmEyVjVjM1J2Y21VdWIzQmxibUpoYm10cGJtZDBaWE4wTG05eVp5NTFheTh3TURFMU9EQXdNREF4TUROVlFYSkJRVTB2Y21WMmIydGxaQzh3TURFMU9EQXdNREF4TUROVlFYSkJRVTB1YW5kcmN5SXNJbk52Wm5SM1lYSmxYMnAzYTNOZlpXNWtjRzlwYm5RaU9pSm9kSFJ3Y3pvdkwydGxlWE4wYjNKbExtOXdaVzVpWVc1cmFXNW5kR1Z6ZEM1dmNtY3VkV3N2TURBeE5UZ3dNREF3TVRBelZVRnlRVUZOTHpOalVFWmlhbmd3ZFZSc1ZXZEZlV1JFYVRRM1UzWXVhbmRyY3lJc0luTnZablIzWVhKbFgycDNhM05mY21WMmIydGxaRjlsYm1Sd2IybHVkQ0k2SW1oMGRIQnpPaTh2YTJWNWMzUnZjbVV1YjNCbGJtSmhibXRwYm1kMFpYTjBMbTl5Wnk1MWF5OHdNREUxT0RBd01EQXhNRE5WUVhKQlFVMHZjbVYyYjJ0bFpDOHpZMUJHWW1wNE1IVlViRlZuUlhsa1JHazBOMU4yTG1wM2EzTWlMQ0p6YjJaMGQyRnlaVjl3YjJ4cFkzbGZkWEpwSWpvaWFIUjBjSE02THk5M2QzY3VlV0Z3YVd4NUxtTnZiU0lzSW5OdlpuUjNZWEpsWDNSdmMxOTFjbWtpT2lKb2RIUndjem92TDNkM2R5NTVZWEJwYkhrdVkyOXRJaXdpYzI5bWRIZGhjbVZmYjI1ZlltVm9ZV3htWDI5bVgyOXlaeUk2SWxsaGNHbHNlU0JNVkVRaWZRLlFxamZJSGVHTDNzNTlkSXNaV0cybTZNWERtbmNJNHU2RWFiTWx6VTJNSTU0T2hHOG1Xb0l3Q016N2tsQm5LTHFzLWtReTlEblhJS0xCcWJQNHlITTBiNEpBczNoZVZEZFdvdlE2dl9ncU00eHBVdXpER0h0VmtxaUJMczVNMWlNdEpRSU9KRlNCNkk1U2ZXQk9OTHBQbFNmWTZQWG54WmVlTExQYkMzeDhDRmZFUHFSYkxkN3I1Z09qMkpBQjg5ZDNmX0x1X0JGMTJnVVAxSjhicWlETDN2QWk3RFFhTHFScXdkRndKajdwbC1ES3ExYnVxQlNXRmFMQVdhaDR5NGg3UGxXSktZa0xpZ0FQOUlCcHdjaVFyZW8xbTl0MHBHb25lM29KNGJUT2o2eWMtbDhwN0JCY2h0QmZYVTRGLUJkTFJDaGs1MjEwYkd1RDdqUmN4ZkNkdyIsInNjb3BlIjoiYWNjb3VudHMgcGF5bWVudHMgb3BlbmlkIiwicmVxdWVzdF9vYmplY3Rfc2lnbmluZ19hbGciOiJQUzI1NiIsImV4cCI6MTU4MjE5OTY4NywiaWF0IjoxNTgyMTk5Mzg3LCJyZXF1ZXN0X29iamVjdF9lbmNyeXB0aW9uX2VuYyI6IkExMjhDQkMtSFMyNTYiLCJqdGkiOiJlOTcyNjY2Mi1hOWU4LTRmY2ItODYzNy1lOTZlNDU3MmM2MmIiLCJyZXNwb25zZV90eXBlcyI6WyJjb2RlIGlkX3Rva2VuIl0sImlkX3Rva2VuX3NpZ25lZF9yZXNwb25zZV9hbGciOiJQUzI1NiJ9.aXWSP_Ta9b6zeCYwLlpmlZiMuWguT5jHn7MJZQQe2DQUD8Pukhe0FDw_ytaAofCIAY5EOy3s_q4_WV00XvvNbNW7xvlM53tlj-gCkHJI4CbriB7qWH9S9awcCOV4X3Gj4EVhfRFfg8xNPuv0fwevNFBMwGnJZna4AZOQqLcSC8G6aZEsfq5r9O8BmC4Vobj3y4I2rxNwBCWtEmdTW4Cxtc0oA3QXMQG7CVCM_9ItGSDvs3crSKaRtuMaUDlv0zIByHYhCGwDKAacq5NalPs0N-gpoKfcOvv987MMGNJ5Mph--wM6TfJDO1D8zQHD0hvKiFjHAW9z43Z_H6wOCEDlHg

Decoded, it would look like this (you can use https://jwt.io/)

(header)

{
  "kid": "epl2OOLzo6XyBXLCxTxuFzJrDCI",
  "alg": "PS256"
}

.(payload)

{
  "token_endpoint_auth_signing_alg": "PS256",
  "request_object_encryption_alg": "RSA-OAEP-256",
  "grant_types": [
    "authorization_code",
    "refresh_token",
    "client_credentials"
  ],
  "application_type": "web",
  "iss": "3cPFbjx0uTlUgEydDi47Sv",
  "redirect_uris": [
    "https://local-auth.yapily.com:8081/"
  ],
  "token_endpoint_auth_method": "private_key_jwt",
  "aud": "0015800001041REAAY",
  "software_id": "3cPFbjx0uTlUgEydDi47Sv",
  "software_statement": "eyJhbGciOiJQUzI1NiIsImtpZCI6Ikh6YTl2NWJnREpjT25oY1VaN0JNd2JTTF80TlYwZ1NGdklqYVNYZEMtMWM9IiwidHlwIjoiSldUIn0.eyJpc3MiOiJPcGVuQmFua2luZyBMdGQiLCJpYXQiOjE1ODIxOTkzOTIsImp0aSI6IjVkNjc2NGI2YjU0YTQ0M2EiLCJzb2Z0d2FyZV9lbnZpcm9ubWVudCI6InNhbmRib3giLCJzb2Z0d2FyZV9tb2RlIjoiVGVzdCIsInNvZnR3YXJlX2lkIjoiM2NQRmJqeDB1VGxVZ0V5ZERpNDdTdiIsInNvZnR3YXJlX2NsaWVudF9pZCI6IjNjUEZiangwdVRsVWdFeWREaTQ3U3YiLCJzb2Z0d2FyZV9jbGllbnRfbmFtZSI6IlF1ZW50aW4gVGVzdCIsInNvZnR3YXJlX2NsaWVudF9kZXNjcmlwdGlvbiI6IkZvciB0ZXN0IHB1cnBvc2VzIiwic29mdHdhcmVfdmVyc2lvbiI6MS4xLCJzb2Z0d2FyZV9jbGllbnRfdXJpIjoiaHR0cHM6Ly93d3cueWFwaWx5LmNvbSIsInNvZnR3YXJlX3JlZGlyZWN0X3VyaXMiOlsiaHR0cHM6Ly9kZXZlbG9wbWVudC1hdXRoLnlhcGlseS5jb20vIiwiaHR0cHM6Ly9zdGFnaW5nLWF1dGgueWFwaWx5LmNvbS8iLCJodHRwczovL2xvY2FsLWF1dGgueWFwaWx5LmNvbTo4MDgyLyIsImh0dHBzOi8vYXV0aC55YXBpbHkuY29tLyIsImh0dHBzOi8vbG9jYWwtYXBpLnlhcGlseS5jb206ODA4MS8iLCJodHRwczovL2xvY2FsLWF1dGgueWFwaWx5LmNvbTo4MDgxLyJdLCJzb2Z0d2FyZV9yb2xlcyI6WyJBSVNQIiwiUElTUCJdLCJvcmdhbmlzYXRpb25fY29tcGV0ZW50X2F1dGhvcml0eV9jbGFpbXMiOnsiYXV0aG9yaXR5X2lkIjoiT0JHQlIiLCJyZWdpc3RyYXRpb25faWQiOiJVbmtub3duMDAxNTgwMDAwMTAzVUFyQUFNIiwic3RhdHVzIjoiQWN0aXZlIiwiYXV0aG9yaXNhdGlvbnMiOlt7Im1lbWJlcl9zdGF0ZSI6IkdCIiwicm9sZXMiOlsiQUlTUCIsIlBJU1AiXX0seyJtZW1iZXJfc3RhdGUiOiJJRSIsInJvbGVzIjpbIkFJU1AiLCJQSVNQIl19LHsibWVtYmVyX3N0YXRlIjoiTkwiLCJyb2xlcyI6WyJBSVNQIiwiUElTUCJdfV19LCJzb2Z0d2FyZV9sb2dvX3VyaSI6Imh0dHBzOi8vd3d3LnlhcGlseS5jb20iLCJvcmdfc3RhdHVzIjoiQWN0aXZlIiwib3JnX2lkIjoiMDAxNTgwMDAwMTAzVUFyQUFNIiwib3JnX25hbWUiOiJZYXBpbHkgTHRkIiwib3JnX2NvbnRhY3RzIjpbeyJuYW1lIjoiVGVjaG5pY2FsIiwiZW1haWwiOiJzeXN0ZW1zK29wZW5iYW5raW5nQHlhcGlseS5jb20iLCJwaG9uZSI6Iis0NDc0MjkzMjE0NjUiLCJ0eXBlIjoiVGVjaG5pY2FsIn0seyJuYW1lIjoiQnVzaW5lc3MiLCJlbWFpbCI6InN5c3RlbXMrb3BlbmJhbmtpbmdAeWFwaWx5LmNvbSIsInBob25lIjoiKzQ0NzQyOTMyMTQ2NSIsInR5cGUiOiJCdXNpbmVzcyJ9XSwib3JnX2p3a3NfZW5kcG9pbnQiOiJodHRwczovL2tleXN0b3JlLm9wZW5iYW5raW5ndGVzdC5vcmcudWsvMDAxNTgwMDAwMTAzVUFyQUFNLzAwMTU4MDAwMDEwM1VBckFBTS5qd2tzIiwib3JnX2p3a3NfcmV2b2tlZF9lbmRwb2ludCI6Imh0dHBzOi8va2V5c3RvcmUub3BlbmJhbmtpbmd0ZXN0Lm9yZy51ay8wMDE1ODAwMDAxMDNVQXJBQU0vcmV2b2tlZC8wMDE1ODAwMDAxMDNVQXJBQU0uandrcyIsInNvZnR3YXJlX2p3a3NfZW5kcG9pbnQiOiJodHRwczovL2tleXN0b3JlLm9wZW5iYW5raW5ndGVzdC5vcmcudWsvMDAxNTgwMDAwMTAzVUFyQUFNLzNjUEZiangwdVRsVWdFeWREaTQ3U3YuandrcyIsInNvZnR3YXJlX2p3a3NfcmV2b2tlZF9lbmRwb2ludCI6Imh0dHBzOi8va2V5c3RvcmUub3BlbmJhbmtpbmd0ZXN0Lm9yZy51ay8wMDE1ODAwMDAxMDNVQXJBQU0vcmV2b2tlZC8zY1BGYmp4MHVUbFVnRXlkRGk0N1N2Lmp3a3MiLCJzb2Z0d2FyZV9wb2xpY3lfdXJpIjoiaHR0cHM6Ly93d3cueWFwaWx5LmNvbSIsInNvZnR3YXJlX3Rvc191cmkiOiJodHRwczovL3d3dy55YXBpbHkuY29tIiwic29mdHdhcmVfb25fYmVoYWxmX29mX29yZyI6IllhcGlseSBMVEQifQ.QqjfIHeGL3s59dIsZWG2m6MXDmncI4u6EabMlzU2MI54OhG8mWoIwCMz7klBnKLqs-kQy9DnXIKLBqbP4yHM0b4JAs3heVDdWovQ6v_gqM4xpUuzDGHtVkqiBLs5M1iMtJQIOJFSB6I5SfWBONLpPlSfY6PXnxZeeLLPbC3x8CFfEPqRbLd7r5gOj2JAB89d3f_Lu_BF12gUP1J8bqiDL3vAi7DQaLqRqwdFwJj7pl-DKq1buqBSWFaLAWah4y4h7PlWJKYkLigAP9IBpwciQreo1m9t0pGone3oJ4bTOj6yc-l8p7BBchtBfXU4F-BdLRChk5210bGuD7jRcxfCdw",
  "scope": "accounts payments openid",
  "request_object_signing_alg": "PS256",
  "exp": 1582199687,
  "iat": 1582199387,
  "request_object_encryption_enc": "A128CBC-HS256",
  "jti": "e9726662-a9e8-4fcb-8637-e96e4572c62b",
  "response_types": [
    "code id_token"
  ],
  "id_token_signed_response_alg": "PS256"
}

. (Signature)

aXWSP_Ta9b6zeCYwLlpmlZiMuWguT5jHn7MJZQQe2DQUD8Pukhe0FDw_ytaAofCIAY5EOy3s_q4_WV00XvvNbNW7xvlM53tlj-gCkHJI4CbriB7qWH9S9awcCOV4X3Gj4EVhfRFfg8xNPuv0fwevNFBMwGnJZna4AZOQqLcSC8G6aZEsfq5r9O8BmC4Vobj3y4I2rxNwBCWtEmdTW4Cxtc0oA3QXMQG7CVCM_9ItGSDvs3crSKaRtuMaUDlv0zIByHYhCGwDKAacq5NalPs0N-gpoKfcOvv987MMGNJ5Mph--wM6TfJDO1D8zQHD0hvKiFjHAW9z43Z_H6wOCEDlHg

What would be nice, is having a way to compare the new JWT I send with the one stored in the body in the simulation.json. As the header and payload are JSON, we could use the JSON matcher that you implemented.

Tips

Some claims don't really make sense to be compared. Like iat, exp, which are time based. The signature as well, you could propose something very sophisticated to very the signature but sounds overkill. I would therefore bypass the signature and concentrate on the header/payload comparaison.

Additional context The JWT standard is defined here: JWT: https://tools.ietf.org/html/rfc7519 JWS: https://tools.ietf.org/html/rfc7515 You can decode JWTs using https://jwt.io/, this website also suggest libraries you could use. A good GO library: https://github.com/SermoDigital/jose

qcastel avatar Feb 20 '20 14:02 qcastel

both of your feature requests need to be based on the matcher chaining https://github.com/SpectoLabs/hoverfly/issues/761 which is still undone.

tommysitu avatar Feb 25 '20 23:02 tommysitu

@tommysitu you can assign this to me. I can work on this now.

kapishmalik avatar Jan 09 '23 16:01 kapishmalik

@kapishmalik I also have a case where I need this instead of reverting to another solution or creating a middleware. Let me know if you need help or if I should pick this up. It would also be nice to maybe add a helper that can generate a JWT based on a payload

petkostas avatar Jan 11 '23 09:01 petkostas

@petkostas in a day or two, I will add PR for this. Can you just brief me more about your requirements.

kapishmalik avatar Jan 11 '23 15:01 kapishmalik

@petkostas feature is there on master. You can go ahead and use it. Let me know incase you face any issues or any suggestions.

https://docs.hoverfly.io/en/latest/pages/reference/hoverfly/request_matchers.html#jwt-matcher

kapishmalik avatar Jan 12 '23 09:01 kapishmalik

@kapishmalik awesome mate! Will surely do as I am currently working on a case which needs it. Thanks for the contribution!

petkostas avatar Jan 12 '23 09:01 petkostas