Search code examples
amazon-web-servicesauthenticationoauth-2.0amazon-cognitoaccess-token

Should you use Client Credentials Grant Type with authenticating server-to-server?


I'm creating an api service ("My Api") where the end users are other apis ("Client"). This is my first application where the Client is not an actual person, so I want to make sure I'm going through the authentication flow correctly.

I'm using AWS Cognito and have based the authentication flow off the "Client credentials grant" section of this post.

The flow I have right now is:

  1. Client registers with My Api
  2. My Api creates an app client on AWS. I have a simple dashboard that will display the client_id and client_secret to the Client (My Api exposes an endpoint to rotate client_secrets)
  3. Client sends the following POST to my AWS oauth2 domain
curl -X POST \
  https://[DOMAIN_NAME].auth.[REGION].amazoncognito.com/oauth2/token \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -H 'authorization: Basic BASE64(client_id:client_secret)' \
  -d 'grant_type=client_credentials&scope=[SCOPE]'
  1. Client receives access_token in the form of a jwt from AWS
  2. Client sends access_token in authorization header to My Api
  3. My Api verifies the access_token is valid
  4. My Api provides access to resources for the applicable scope and client_id

It seems strange that I have to create an app client on AWS Cognito for each Client. Is that normal when you're authenticating using client credentials instead of an authorization code?

If that's the case, can someone direct me to what the pricing is for each each app client? Is it in the "Users who sign in directly with their User Pool credentials or with social identity providers:" section on this page?


Solution

  • After some time to think about this, this is what I would do (preface: this is definitely not AWS/Banking level authentication). The code below is in postgres.

    I would design the database schema to accommodate multiple tenants see this paper by Google for ideas. Each User (eg, employee of an Organization) will have a Cognito User which will be linked to the User.

    CREATE TABLE organizations (
      org_id uuid
    );
    
    CREATE TABLE users (
      user_id      uuid,
      cognito_uid  uuid,
      org_id       uuid REFERENCES organizations(org_id)
    );
    
    CREATE TABLE secret_stuffs (
      secrets   varchar
    );
    

    I would then create an api_keys table.

    // We only want user to have two keys max
    CREATE TYPE api_key_type AS ENUM (
      'primary',
      'secondary'
    );
    
    CREATE TABLE api_keys (
      PRIMARY KEY (
        user_id,
        key_type
      )
    
      org_id       uuid REFERENCES organizations(org_id),
      user_id      uuid REFERENCES users(user_id),
      key_type     api_key_type,
      private_key  varchar
    );
    
    // You'd probably want to create a composite index with user_id and private_key fields since we'll create a function that access both
    

    I would lock down the api_keys and secret_stuffs table (ie, not grant access to any role) and create a SECURITY DEFINER function that takes the user_id and private_key as inputs, checks that that row exists in your api_keys table and returns whatever you need from the secret_stuffs table.