emblem icon indicating copy to clipboard operation
emblem copied to clipboard

Design: determine website -> content API auth system

Open ace-n opened this issue 4 years ago • 1 comments

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.

ace-n avatar Aug 21 '21 01:08 ace-n

I did some research to refresh my memory, and I found some interesting materials.

  1. 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"
  1. 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.

  1. 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.

grayside avatar Mar 29 '22 03:03 grayside