hoverfly
hoverfly copied to clipboard
JWT matcher
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
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 you can assign this to me. I can work on this now.
@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 in a day or two, I will add PR for this. Can you just brief me more about your requirements.
@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 awesome mate! Will surely do as I am currently working on a case which needs it. Thanks for the contribution!