Design: determine website -> content API auth system
We have previously decided that we should authenticate both the end user and the website itself.
We resolved end-user authentication in #117, so this issue focuses on the website ➡️ content API part.
Options
Give all user tokens API access
Description: This option would involve giving direct API access to anyone with a valid user token. No additional credentials would be required to access the API itself.
Tradeoffs: ✅ Easy to implement ❌ Insecure (users can impersonate the website and call the API directly)
Use GCP-minted tokens with Cloud Run's built-in authentication
Thanks to @dinagraves for this proposal!
Description
The website would programmatically mint a GCP access token (similar to what gcloud auth print-access-token does) and pass that in the Authorization header as shown here.
Tradeoffs ✅ Easy to implement if the client library generator supports header inclusion (cc @engelke) ✅ Demonstrates batteries-included behavior ❌ Same credential for all API calls (i.e. access token compromise ➡️ API compromise)
Use server-side secret or GCP-minted token to mint a per-call HMAC
Description
Use a secret shared between the Website and Content API to compute and verify Hash-based Message Authentication Codes (HMAC). Such an approach might look like this:
On the website:
hmac = hash(access_token + message *without* the hmac parameter)
hmaced_message = message.copy().set("hmac", hmac)
On the Content API:
message_hash = hash(strip_hmac(hmaced_message))
if message_hash != hmaced_message.hmac:
return 400, "Invalid message"
Tradeoffs ✅ Most secure ❌ Difficult to implement ❌ Does not support GCP's built-in authentication (since secret must not be transmitted between client and server)
Recommendation
We recommend using the GCP-minted token with Cloud Run's built-in authentication.
I did some research to refresh my memory, and I found some interesting materials.
- Give all user tokens API access
You recommend against this. I agree. From ID Token and Access Token: What's the Difference?:
- The diagram for Access tokens is a closer match
- "In a delegated authorization scenario where a third-party client wants to call your API, you must not use an ID token to call the API."
- "if your API doesn't care if a token is meant for it, an ID token stolen from any client application can be used to access your API"
- Use GCP-minted tokens with Cloud Run's built-in authentication
Despite not wanting to pass along the identity token to the API, it does carry proof of the user's identity. If we mint a new access or identity token suitable for invoker auth, that would be the Website identity, not the authenticated user.
- Use server-side secret or GCP-minted token to mint a per-call HMAC
In OAuth terms, I interpret the HMAC suggestion as "custom Access Token", this seems reasonable.
It's still unclear to me how we intend to handle both authentication and authorization aspects of this operation in a clean way.
Extended Notes
There are circumstances where we might want to take this further, such as if we're trying to have a pure frontend site. In that case, we might want Access Tokens with a sender constraint. Auth0 has more good stuff on that, but it's unclear what is viable & recommended:
Options down the sender constraint path look like:
- mTLS (https://datatracker.ietf.org/doc/html/rfc8705)
- https://datatracker.ietf.org/doc/draft-ietf-oauth-dpop/
- https://datatracker.ietf.org/doc/draft-ietf-httpbis-message-signatures/
I don't know anything about message signatures, and Cloud Run doesn't have good support for mTLS (here's a demo involving putting envoy inside the container). dPoP sounds interesting, being a protocol to manage all this within the application.