Verifying webhook request signature for Google Conversation v3 API - actions-on-google

When building a Conversational Action with the new Actions SDK or Action Builder, you can define a webhook to handle business logic. This webhook then receives fulfillment requests with the following headers, among others:
Google-Actions-API-Version: "3"
Google-Assistant-Signature: "eyJhbGciOiJSUzI1NiIsImtpZC..."
How should that signature be verified? It's a JWT claim, but the key ID with which it was signed does not exist in the GCP account linked with the Action, and is not mentioned in the new Actions SDK documentation or in the Node.js fulfillment library documentation.

The signature is a JSON Web Token, which is an encoded way of transmitting some assertions that have been signed in a verifiable way. There are libraries that will both decode and verify JWTs. The general steps (some of which you can cache or shortcut) are:
Decode the header to get the kid (key id) and the payload to get the iss (issuer) fields. You'll also want the nbf (not before) and exp (expiration) fields to verify this was set recently and the aud field to verify that it matches your Google Cloud project ID.
Based on the issuer, access the well known openid configuration. Since the issuer is "https://accounts.google.com" you can access this at "https://accounts.google.com/.well-known/openid-configuration"
From the configuration document, you want the jwks_uri field, which is the URL to get the current JWT certificates. For Google, this is probably "https://www.googleapis.com/oauth2/v3/certs"
The certificate document should contain an array of keys. You want a key with the kid that matches the kid from the JWT. Note that these keys change frequently, but as long as you're within the window of the nbf and exp fields from the signature header, the key should exist in the certificate document.
With all this, you can then verify the signature portion of the JWT.

Related

How to get the Username with OpenID Connect on ADFS Server

I just realized that the userinfoendpoint doesn't give any useful info about the logged in user.
Now, I saw that you can get an id_token from ADFS tokenendpoint. This id_token is actually a JWT which contains the unique_name.
Now my question is, if it is safe to use this info without signature validation (since we don't have the private key of the HS256 algorithm and validation is as far as I understood, a thing for the issuer not for the client).
• There are two scenarios to your question of whether it is safe to use this info from an ID_token. First, your client, i.e., the application or authorization server will validate the token just like it validates the access token for any tampering. Also, it can validate the issuer to ensure that the correct issuer has sent back the token. Since there are many libraries to validate an ID token, you should use them for ID token validation purposes.
• Also, only confidential client applications should validate an ID token as their token holds a secret while public applications don’t benefit from validating an ID token as there is always a risk of a malicious user who can intercept and edit the keys used for validation of the token.
• As you are speaking of the case when you don’t have the private key to the algorithm used for encrypting the ID token, it must be a confidential client application. Thus, validating the ID token on the lines of access token and confirming the below claims are validated by your token validation library, you can then use the information retrieved from these claims for your purpose. The claims that need to be validated are as follows: -
Timestamps: the iat, nbf, and exp timestamps should all fall before or after the current time, as appropriate.
Audience: the aud claim should match the app ID for your application.
Nonce: the nonce claim in the payload must match the nonce parameter passed into the /authorize endpoint during the initial request.
Please find the below documentation link for more in-depth details on validating the tokens and the inherent process involved in it: -
https://learn.microsoft.com/en-us/azure/active-directory/develop/access-tokens#validating-tokens
No, sadly the userinfo endpoint only gives you a "sub".
You can validate the JWT yourself.
You should always check the signature.

Jwt.io self populating the signature

I am very new to jwt and signature validation. I had a very basic query.
I am generating a token from MSAL(AAD).
When I use the token in jwt.io, I can see that it automatically populates the secret key and marks the signature as verified. How does jwt.io know about this?
From generating token point of view, I didn't mention anywhere explicitly to generate the token with any secret.
You don't show the details of your token here (which is ok), but I assume the token has a kid and maybe also a jku in the header.
The kidis the Key Id, and the jku the JSON Web Key Set URL.
Under that URL (you can paste it to your browser to see) you can find a set of JWKs (JSON Web Keys), basically a collection of public keys in a special format. In case of tokens issued by AAD you the JWKS_URI can be found on https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration.
JWT.io can read this information and find the key with the given kid and verify the token based on that.
This Q/A explains the verification of JWTs issued by AAD in more detail.

Is payload tampering possible when making request to resource server with JWT?

As we know the JWT is signed with secret key so the token itself can not be tampered but the payload we send to resource server with JWT can be plain text/json/xml/query string so how can we protect payload from tampering?
The signature is exactly what prevents the payload from being tampered. The payload cannot be modified without invalidating the signature.
Let me also clarify that JSON Web Token (JWT) is an open standard that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. JWT is a generic name for the following types of token:
JSON Web Signature (JWS): The payload is encoded and signed so the integrity of the claims can be verified.
JSON Web Encryption (JWE): They payload is encrypted so the claims are hidden from other parties.
If you intend to prevent the payload from being tampered, then use JWS. If you want to hide the payload from other parties, then use JWE.
tl;dr To keep your payload (and token in general) safe all you need to do is use a strong secret and signing algorithm and verify the signature before trusting the contents.
JWT is signed with secret key, but the payload can be plain text/json/xml/query
You seem to be confused on what a JWT is. There is no separate JWT and payload, one is part of the other. JWT consists of Header, Payload and Signature. Signature is created over both - the header and payload parts and additionally your secret key. This means that if either piece is tampered with the signature can not be verified.
From JWT introduction:
Do note that for signed tokens this information [header and payload], though protected against tampering, is readable by anyone. Do not put secret information in the payload or header elements of a JWT unless it is encrypted.
To see for yourself you can use JWT debugger. When you change the decoded header or payload their base64 values change and therefore the signature changes with them. If you were to copy-paste the old signature value into new JWT it will show invalid signature error.
Unfortunately, there is no standard way to ensure that the request body has not been tampered during the transport using http headers.
AFAIK and among all the authentication schemes listed by the IANA, none of them have such feature.
However if your project is limited to a few number of clients or if you provide a detailed documentation, you can implement your own request signature mechanism.
I recommend you to read more about the following initiatives. Theses ones could help you in that implementation:
AWS Signature Version 4: certainly the most interesting resource. It also allows POST (maybe PUT and PATCH) requests to be signed
Hawk: same as above. Not widely used though.
OAuth 2.0 Message Authentication Code (MAC) Tokens. This one is abandonned and do not ensure the body has not been modified.
JWT by itself is not tamper proof. To make it secure, it must be transformed with these 2 steps:
Sign with Sender's private key
Encrypt with Receiver's public key
Signing with sender's private key ensures that any unauthorized modification of token can be detected.
Encrypting with Receiver's public key will ensure that the token achieves secrecy and only the intended receiver can see the token content.
JWT signed by Sender is called JWS.
Encrypted JWS is called JWE.
More information:
https://dzone.com/articles/securing-spring-boot-microservices-with-json-web-t

PayPal id_token verification

I'm implementing "Log In with PayPal" functionality. Should be fairly simple, but unfortunately PayPal lacks documentation (only some basic things are described).
I get authorization_code using "Log In with PayPal" widget. Than I make a request to https://api.sandbox.paypal.com/v1/identity/openidconnect/tokenservice and get JSON with access_token, refresh_token and id_token. I want to get unique user identifier, so I decode id_token, but I'm not able to verify signature of my id_token. Id_token header contains {"alg": "HS256", "typ": "JWT"}.
What secret should I use? Tried my app's secret (same that I use to access tokenservice), but it didn't work (using https://jwt.io/ debugger).
PayPal is not OpenID connect compliant. This answer explains the reasons.
PayPal expose a configuration endpoint - https://www.paypalobjects.com/.well-known/openid-configuration.It only support HS256 and does not support or mention about RS256. This could be due to a proprietary validation for ID token. For example a id token used as a bearer token.
Alternative solution would be to invoke userinfo endpoint as described by document. This endpoint can be invoked using access token and document says it would return the user_id
user_id - identifier for the end-user at the issuer.
Although it seems that user_id found in the userinfo is different from sub that could be extracted from id_token.
How to find public keys if signing algorithm is RS256
ID token is a JWT. As you have found out, it contains a JWS signature which acts as a MAC.
This signature is a signed using a private key. The receiver of the id token can validate the token using the public key. To find the public key, openid conenct specify a discovery document. You can read more about this from the specification
In the discovery document, you get a special endpoint to infer the configuration .well-known/openid-configuration. And one of the must have metadata of the configuration response is jwk_url
jwks_uri
REQUIRED. URL of the OP's JSON Web Key Set [JWK] document. This
contains the signing key(s) the RP uses to validate signatures from
the OP. The JWK Set MAY also contain the Server's encryption key(s),
which are used by RPs to encrypt requests to the Server. When both
signing and encryption keys are made available, a use (Key Use)
parameter value is REQUIRED for all keys in the referenced JWK Set to
indicate each key's intended usage. Although some algorithms allow the
same key to be used for both signatures and encryption, doing so is
NOT RECOMMENDED, as it is less secure. The JWK x5c parameter MAY be
used to provide X.509 representations of keys provided. When used, the
bare key values MUST still be present and MUST match those in the
certificate.

What is the purpose of the --oidc-client-id parameter when setting Kubernetes up to use OpenID Connect?

The Kubernetes documentation related to OpenID Connect mentions that as part of setting things up you need to supply some parameters to the API server:
--oidc-client-id: A client id that all tokens must be issued for.
There is no other explanation about how this would map to, say, something returned by the OpenID Connect-conformant Google identity provider.
I don't know what this parameter value will be used for. Will it match against something in the decoded JWT token?
It looks like the id_token returned by the Google identity provider might contain something, once decoded, in its aud field (aud is apparently short for "audience"). Is this what the --oidc-client-id should match? Am I way off?
This can be explained from the kubernetes documentation on id tokens.
As you can see, identity provider is a separate system. For example this can be MS Azure AD or Google as you have shown.
When you register for a identity provider, you get important things in return. client id is one such important parameter. if you are aware of the openid connect flow, you need to provide this client id when you follow the flow. If the flow is complete, you will return an id token. An id token has one must have claim, aud which is the audience that token was issued for.
When you validate an id token you MUST verify you are in the audience list. More can be found from the spec.
Quoting from specification,
The Client MUST validate that the aud (audience) Claim contains its client_id value registered at the Issuer identified by the iss (issuer) Claim as an audience
Now, kubernetes uses bearer tokens. Here the tokens used are id tokens. To validate the token it should know specifically the audience. This enables the API server to validate the token is issued for the particular client who made the call. Thus authorising the call to to success.