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).
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
Prerequisites
Before you can request a token, you need the following information from your AWS Cognito setup:
7km8k5m9d1ab2cdefghijk3l1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0thttps://your-app.auth.us-east-1.amazoncognito.comhttps://{domain}.auth.{region}.amazoncognito.comapi/read api/write or resource-server/scopeThe Token Endpoint
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" |
1. Combine your Client ID and Client Secret with a colon:
clientId:clientSecret2. 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
- 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
- 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
- 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
- Set the request type to POST
- Enter the URL:
https://your-app.auth.us-east-1.amazoncognito.com/oauth2/token - Go to the Authorization tab:
- Type: Select "Basic Auth"
- Username: Enter your Client ID
- Password: Enter your Client Secret
- Go to the Body tab:
- Select "x-www-form-urlencoded"
- Add key:
grant_type, value:client_credentials - Optionally add key:
scope, value:your/scope
- Click Send
Additional Resources
- AWS Cognito Token Endpoint Documentation
- OAuth 2.0 Client Credentials Flow Specification (RFC 6749)
- JWT.io - Decode and verify JWT tokens
Endpoint:
POST {cognito-domain}/oauth2/tokenContent-Type:
application/x-www-form-urlencodedAuthorization:
Basic {Base64(clientId:clientSecret)}Body:
grant_type=client_credentials&scope={optional-scope}