I have a REST api that accepts what are essentially anonymous survey responses, i.e., from non-authenticated users. My organization wants to allow a few (maybe 12) partners to develop client apps to collect the responses and send them to the api. I want the api to authenticate these clients, i.e., validate that the client is one of the 12 that we've authorized. I was planning to use the OAuth 2 "client credentials" flow, providing each of these clients with a unique client_id and client_secret. The client would request a token using their client_id and client_secret, and then use that token in subsequent api calls. The clients would be required to be https server-based apps that can keep the client_secret hidden -- i.e., "confidential clients" for the purposes of the OAuth2 spec (https://www.rfc-editor.org/rfc/rfc6749#page-14).
But can the client_secret really be confidential in the client credentials flow? As I understand it, the recommended way to pass the client_id and client_secret is as a base64-encoded value in the HTTP Basic auth header. If an attacker browses to that client app to submit a suryvey response, can't he just use the browser dev tool to see the base64-encoded client_id:client_secret value, decode it, and build his own app using that same client_id and client_secret?
I know I can use CORS to restrict browser-based requests to those from the 12 authorized domains. So I guess I'm really asking about non-browser attacks that try to impersonate one of the authorized clients.
So my questions:
- Am I right that this visible base-64 encoded version of the client_id:client_secret presents a vulnerability in a vanilla OAUth2 2-legged client credentials flow (i.e., no end-user to authorize the client)?
- Instead of client credentials, I'm thinking we could use a signed-token approach (probably JWT), similar to what's described here, where:
- the client server and api server have a shared private key.
- the client generates a token, signing it with a hash of the private key plus a timestamp. The client sends that token in the api request, along with the "request timestamp" used for the hash.
- the api server validates the token, using the private key and the "request timestamp" to verify the hash. The server accepts only recent timestamps (e.g., within the last 2 seconds) to prevent replay attacks.
- Does that sound like a reasonable approach?
- Can anyone think of another way to restrict the api to only authorized clients?