Procare Pay Integration Service

Complete API Documentation for Payment Processing

AWS Cognito OAuth2 Client Credentials Flow

Overview

This document explains how to obtain an access token from AWS Cognito using the OAuth2 Client Credentials flow. This authentication method is used for machine-to-machine communication where an application needs to access APIs on its own behalf (not on behalf of a user).

What is OAuth2 Client Credentials Flow?
The Client Credentials flow is the simplest OAuth2 flow. Your application exchanges its credentials (Client ID and Client Secret) for an access token. This token can then be used to authenticate API requests. Think of it like showing an ID card to get a temporary badge that grants you access to a building.

When to Use This Flow

  • Server-to-Server Communication: When your backend service needs to call another API
  • Automated Processes: Scheduled jobs, batch processing, or background tasks
  • Service Accounts: Applications acting on their own behalf, not representing a user
  • Microservices: One service authenticating to call another service
Important: Never use Client Credentials flow for mobile apps or single-page applications where the client secret could be exposed. This flow is only for secure server-side applications.

Prerequisites

Before you can request a token, you need the following information from your AWS Cognito setup:

Client ID (required)
Example: 7km8k5m9d1ab2cdefghijk3l
Description: A unique identifier for your application, provided by AWS Cognito when you create an app client
Client Secret (required)
Example: 1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t
Description: A secret key associated with your Client ID. Keep this secure and never commit it to source control!
Cognito Domain (required)
Example: https://your-app.auth.us-east-1.amazoncognito.com
Description: Your Cognito User Pool's domain URL. Format is typically: https://{domain}.auth.{region}.amazoncognito.com
Scope (optional)
Example: api/read api/write or resource-server/scope
Description: Space-separated list of permissions your token should have. If not specified, Cognito returns all scopes configured for your app client

The Token Endpoint

POST https://{your-cognito-domain}.auth.{region}.amazoncognito.com/oauth2/token

Request Headers

Header Value Description
Content-Type application/x-www-form-urlencoded Required. Indicates the body is form-encoded data
Authorization Basic {base64-encoded-credentials} Required. Base64-encoded string of "ClientID:ClientSecret"
How to Create the Authorization Header:
1. Combine your Client ID and Client Secret with a colon: clientId:clientSecret
2. Base64-encode this string
3. Prepend "Basic " to the encoded string
Example: Basic N2ttOGs1bTlkMWFiMmNkZWY6MWEyYjNjNGQ1ZTZmN2c4aDlpMGo=

Request Body Parameters

Parameter Value Description
grant_type client_credentials Required. Always set to "client_credentials" for this flow
scope your/scope Optional. Space-separated list of requested scopes

Code Examples

using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Generic;
using Newtonsoft.Json;

public class CognitoTokenResponse
{
    [JsonProperty("access_token")]
    public string AccessToken { get; set; }

    [JsonProperty("token_type")]
    public string TokenType { get; set; }

    [JsonProperty("expires_in")]
    public int ExpiresIn { get; set; }
}

public class CognitoAuthClient
{
    private readonly string _cognitoDomain;
    private readonly string _clientId;
    private readonly string _clientSecret;
    private readonly HttpClient _httpClient;

    public CognitoAuthClient(string cognitoDomain, string clientId, string clientSecret)
    {
        _cognitoDomain = cognitoDomain;
        _clientId = clientId;
        _clientSecret = clientSecret;
        _httpClient = new HttpClient();
    }

    public async Task<CognitoTokenResponse> GetAccessTokenAsync(string scope = null)
    {
        try
        {
            // Construct the token endpoint URL
            string tokenEndpoint = $"{_cognitoDomain}/oauth2/token";

            // Create Basic Authentication header
            string credentials = $"{_clientId}:{_clientSecret}";
            string base64Credentials = Convert.ToBase64String(
                Encoding.UTF8.GetBytes(credentials));

            // Prepare the request
            var request = new HttpRequestMessage(HttpMethod.Post, tokenEndpoint);
            request.Headers.Authorization =
                new AuthenticationHeaderValue("Basic", base64Credentials);

            // Prepare form-encoded body
            var formData = new Dictionary<string, string>
            {
                { "grant_type", "client_credentials" }
            };

            if (!string.IsNullOrEmpty(scope))
            {
                formData.Add("scope", scope);
            }

            request.Content = new FormUrlEncodedContent(formData);

            // Send the request
            HttpResponseMessage response = await _httpClient.SendAsync(request);
            response.EnsureSuccessStatusCode();

            // Parse the response
            string responseBody = await response.Content.ReadAsStringAsync();
            CognitoTokenResponse tokenResponse =
                JsonConvert.DeserializeObject<CognitoTokenResponse>(responseBody);

            return tokenResponse;
        }
        catch (HttpRequestException ex)
        {
            Console.WriteLine($"Error obtaining token: {ex.Message}");
            throw;
        }
    }
}

// Example usage:
var authClient = new CognitoAuthClient(
    "https://your-app.auth.us-east-1.amazoncognito.com",
    "your-client-id",
    "your-client-secret"
);

CognitoTokenResponse token = await authClient.GetAccessTokenAsync("api/read api/write");
Console.WriteLine($"Access Token: {token.AccessToken}");
Console.WriteLine($"Token Type: {token.TokenType}");
Console.WriteLine($"Expires In: {token.ExpiresIn} seconds");
Imports System
Imports System.Net.Http
Imports System.Net.Http.Headers
Imports System.Text
Imports System.Threading.Tasks
Imports System.Collections.Generic
Imports Newtonsoft.Json

Public Class CognitoTokenResponse
    <JsonProperty("access_token")>
    Public Property AccessToken As String

    <JsonProperty("token_type")>
    Public Property TokenType As String

    <JsonProperty("expires_in")>
    Public Property ExpiresIn As Integer
End Class

Public Class CognitoAuthClient
    Private ReadOnly _cognitoDomain As String
    Private ReadOnly _clientId As String
    Private ReadOnly _clientSecret As String
    Private ReadOnly _httpClient As HttpClient

    Public Sub New(cognitoDomain As String, clientId As String, clientSecret As String)
        _cognitoDomain = cognitoDomain
        _clientId = clientId
        _clientSecret = clientSecret
        _httpClient = New HttpClient()
    End Sub

    Public Async Function GetAccessTokenAsync(Optional scope As String = Nothing) _
        As Task(Of CognitoTokenResponse)
        Try
            ' Construct the token endpoint URL
            Dim tokenEndpoint As String = $"{_cognitoDomain}/oauth2/token"

            ' Create Basic Authentication header
            Dim credentials As String = $"{_clientId}:{_clientSecret}"
            Dim base64Credentials As String = Convert.ToBase64String(
                Encoding.UTF8.GetBytes(credentials))

            ' Prepare the request
            Dim request As New HttpRequestMessage(HttpMethod.Post, tokenEndpoint)
            request.Headers.Authorization =
                New AuthenticationHeaderValue("Basic", base64Credentials)

            ' Prepare form-encoded body
            Dim formData As New Dictionary(Of String, String) From {
                {"grant_type", "client_credentials"}
            }

            If Not String.IsNullOrEmpty(scope) Then
                formData.Add("scope", scope)
            End If

            request.Content = New FormUrlEncodedContent(formData)

            ' Send the request
            Dim response As HttpResponseMessage = Await _httpClient.SendAsync(request)
            response.EnsureSuccessStatusCode()

            ' Parse the response
            Dim responseBody As String = Await response.Content.ReadAsStringAsync()
            Dim tokenResponse As CognitoTokenResponse =
                JsonConvert.DeserializeObject(Of CognitoTokenResponse)(responseBody)

            Return tokenResponse

        Catch ex As HttpRequestException
            Console.WriteLine($"Error obtaining token: {ex.Message}")
            Throw
        End Try
    End Function
End Class

' Example usage:
Dim authClient As New CognitoAuthClient(
    "https://your-app.auth.us-east-1.amazoncognito.com",
    "your-client-id",
    "your-client-secret"
)

Dim token As CognitoTokenResponse =
    Await authClient.GetAccessTokenAsync("api/read api/write")

Console.WriteLine($"Access Token: {token.AccessToken}")
Console.WriteLine($"Token Type: {token.TokenType}")
Console.WriteLine($"Expires In: {token.ExpiresIn} seconds")
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName;

public class CognitoTokenResponse {
    @SerializedName("access_token")
    private String accessToken;

    @SerializedName("token_type")
    private String tokenType;

    @SerializedName("expires_in")
    private int expiresIn;

    // Getters
    public String getAccessToken() { return accessToken; }
    public String getTokenType() { return tokenType; }
    public int getExpiresIn() { return expiresIn; }
}

public class CognitoAuthClient {
    private final String cognitoDomain;
    private final String clientId;
    private final String clientSecret;
    private final HttpClient httpClient;
    private final Gson gson;

    public CognitoAuthClient(String cognitoDomain, String clientId, String clientSecret) {
        this.cognitoDomain = cognitoDomain;
        this.clientId = clientId;
        this.clientSecret = clientSecret;
        this.httpClient = HttpClient.newHttpClient();
        this.gson = new Gson();
    }

    public CognitoTokenResponse getAccessToken(String scope)
            throws IOException, InterruptedException {

        // Construct the token endpoint URL
        String tokenEndpoint = cognitoDomain + "/oauth2/token";

        // Create Basic Authentication header
        String credentials = clientId + ":" + clientSecret;
        String base64Credentials = Base64.getEncoder()
            .encodeToString(credentials.getBytes(StandardCharsets.UTF_8));

        // Prepare form body
        StringBuilder formBody = new StringBuilder("grant_type=client_credentials");
        if (scope != null && !scope.isEmpty()) {
            formBody.append("&scope=").append(scope);
        }

        // Build the request
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(tokenEndpoint))
            .header("Content-Type", "application/x-www-form-urlencoded")
            .header("Authorization", "Basic " + base64Credentials)
            .POST(HttpRequest.BodyPublishers.ofString(formBody.toString()))
            .build();

        // Send the request
        HttpResponse<String> response = httpClient.send(
            request,
            HttpResponse.BodyHandlers.ofString()
        );

        // Check for successful response
        if (response.statusCode() != 200) {
            throw new IOException("Failed to get token: HTTP " + response.statusCode()
                + " - " + response.body());
        }

        // Parse and return the response
        return gson.fromJson(response.body(), CognitoTokenResponse.class);
    }

    public CognitoTokenResponse getAccessToken()
            throws IOException, InterruptedException {
        return getAccessToken(null);
    }
}

// Example usage:
public class Main {
    public static void main(String[] args) {
        try {
            CognitoAuthClient authClient = new CognitoAuthClient(
                "https://your-app.auth.us-east-1.amazoncognito.com",
                "your-client-id",
                "your-client-secret"
            );

            CognitoTokenResponse token = authClient.getAccessToken("api/read api/write");

            System.out.println("Access Token: " + token.getAccessToken());
            System.out.println("Token Type: " + token.getTokenType());
            System.out.println("Expires In: " + token.getExpiresIn() + " seconds");

        } catch (Exception e) {
            System.err.println("Error: " + e.getMessage());
            e.printStackTrace();
        }
    }
}
require 'net/http'
require 'uri'
require 'base64'
require 'json'

class CognitoAuthClient
  def initialize(cognito_domain, client_id, client_secret)
    @cognito_domain = cognito_domain
    @client_id = client_id
    @client_secret = client_secret
  end

  def get_access_token(scope = nil)
    # Construct the token endpoint URL
    token_endpoint = "#{@cognito_domain}/oauth2/token"
    uri = URI.parse(token_endpoint)

    # Create Basic Authentication header
    credentials = "#{@client_id}:#{@client_secret}"
    base64_credentials = Base64.strict_encode64(credentials)

    # Prepare form body
    form_data = { 'grant_type' => 'client_credentials' }
    form_data['scope'] = scope unless scope.nil? || scope.empty?

    # Create the HTTP request
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true

    request = Net::HTTP::Post.new(uri.path)
    request['Content-Type'] = 'application/x-www-form-urlencoded'
    request['Authorization'] = "Basic #{base64_credentials}"
    request.set_form_data(form_data)

    # Send the request
    response = http.request(request)

    # Handle the response
    if response.code.to_i == 200
      token_response = JSON.parse(response.body)
      {
        access_token: token_response['access_token'],
        token_type: token_response['token_type'],
        expires_in: token_response['expires_in']
      }
    else
      raise "Failed to get token: HTTP #{response.code} - #{response.body}"
    end
  rescue StandardError => e
    puts "Error obtaining token: #{e.message}"
    raise
  end
end

# Example usage:
begin
  auth_client = CognitoAuthClient.new(
    'https://your-app.auth.us-east-1.amazoncognito.com',
    'your-client-id',
    'your-client-secret'
  )

  token = auth_client.get_access_token('api/read api/write')

  puts "Access Token: #{token[:access_token]}"
  puts "Token Type: #{token[:token_type]}"
  puts "Expires In: #{token[:expires_in]} seconds"

rescue StandardError => e
  puts "Error: #{e.message}"
end

Response Format

Success Response (HTTP 200)

{
  "access_token": "eyJraWQiOiJ1dU...(long JWT token)...xQiLCJhbGc",
  "token_type": "Bearer",
  "expires_in": 3600
}

Response Fields

Field Type Description
access_token string The JWT access token you'll use to authenticate API requests. This is typically a long Base64-encoded string.
token_type string Always "Bearer" - indicates how to use the token (in the Authorization header as "Bearer {token}")
expires_in integer Number of seconds until the token expires (typically 3600 = 1 hour). After this time, you'll need to request a new token.

Using the Access Token

Once you have the access token, include it in the Authorization header of your API requests:

Authorization: Bearer eyJraWQiOiJ1dU...(your-access-token)...xQiLCJhbGc

Example API Request with Token (C#)

var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", token.AccessToken);

var response = await httpClient.GetAsync("https://your-api.com/api/endpoint");
var content = await response.Content.ReadAsStringAsync();

Error Responses

Invalid Client Credentials (HTTP 401)

{
  "error": "invalid_client"
}

Cause: Client ID or Client Secret is incorrect, or the Authorization header is malformed.

Solution: Verify your Client ID and Client Secret are correct and properly Base64-encoded.

Invalid Grant Type (HTTP 400)

{
  "error": "unsupported_grant_type",
  "error_description": "Invalid grant type"
}

Cause: The grant_type parameter is missing or not set to "client_credentials".

Solution: Ensure your request body includes grant_type=client_credentials.

Invalid Scope (HTTP 400)

{
  "error": "invalid_scope",
  "error_description": "Invalid scope requested"
}

Cause: The requested scope is not configured for your app client in Cognito.

Solution: Check your Cognito app client configuration and ensure the scope exists, or omit the scope parameter to get all available scopes.

Best Practices

1. Token Caching

Tokens are valid for a specific duration (typically 1 hour). Instead of requesting a new token for every API call, cache the token and reuse it until it expires. This reduces unnecessary requests to Cognito.

// Example: Simple token caching in C#
private static CognitoTokenResponse _cachedToken;
private static DateTime _tokenExpiration;

public async Task<string> GetValidTokenAsync()
{
    if (_cachedToken == null || DateTime.UtcNow >= _tokenExpiration)
    {
        _cachedToken = await GetAccessTokenAsync();
        _tokenExpiration = DateTime.UtcNow.AddSeconds(_cachedToken.ExpiresIn - 60); // Refresh 1 min early
    }
    return _cachedToken.AccessToken;
}

2. Secure Storage of Credentials

  • Never hardcode Client ID and Client Secret in your source code
  • Use environment variables or secure configuration management (e.g., AWS Secrets Manager, Azure Key Vault)
  • Never commit credentials to version control
  • Rotate credentials regularly

3. Error Handling

Always implement proper error handling for:

  • Network failures (timeouts, connection errors)
  • Authentication failures (invalid credentials)
  • Token expiration (implement automatic token refresh)
  • Rate limiting (implement exponential backoff)

4. HTTPS Only

Always use HTTPS for token requests. Never send credentials over unencrypted HTTP connections. AWS Cognito endpoints use HTTPS by default.

5. Token Renewal Strategy

Implement token renewal before expiration. A common practice is to refresh the token when it has 10% of its lifetime remaining (e.g., refresh at 54 minutes for a 1-hour token).

Troubleshooting

Common Issues

Issue: Getting "invalid_client" error
  • Verify Client ID and Client Secret are correct (no extra spaces or characters)
  • Check that the Authorization header is properly formatted: Basic {base64-encoded-credentials}
  • Ensure you're Base64-encoding the string "clientId:clientSecret" (with the colon)
  • Verify the app client has "Client credentials" OAuth flow enabled in Cognito
Issue: Getting "invalid_scope" error
  • Check that the scope exists in your Cognito Resource Server configuration
  • Verify the app client is allowed to use the requested scope
  • Try omitting the scope parameter to get all available scopes
  • Ensure scope format is correct: resource-server-identifier/scope-name
Issue: Token works initially but then fails
  • The token has likely expired (check the expires_in value)
  • Implement token caching with automatic renewal
  • Check that your system clock is synchronized (JWT tokens are time-sensitive)

Testing Your Implementation

Using cURL (Command Line)

# First, Base64 encode your credentials
echo -n "your-client-id:your-client-secret" | base64

# Then make the request
curl -X POST https://your-app.auth.us-east-1.amazoncognito.com/oauth2/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -H "Authorization: Basic {your-base64-encoded-credentials}" \
  -d "grant_type=client_credentials&scope=your/scope"

Using Postman

  1. Set the request type to POST
  2. Enter the URL: https://your-app.auth.us-east-1.amazoncognito.com/oauth2/token
  3. Go to the Authorization tab:
    • Type: Select "Basic Auth"
    • Username: Enter your Client ID
    • Password: Enter your Client Secret
  4. Go to the Body tab:
    • Select "x-www-form-urlencoded"
    • Add key: grant_type, value: client_credentials
    • Optionally add key: scope, value: your/scope
  5. Click Send

Additional Resources

Quick Reference:
Endpoint: POST {cognito-domain}/oauth2/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic {Base64(clientId:clientSecret)}
Body: grant_type=client_credentials&scope={optional-scope}