Skip to content

0039: Centralized Authentication Service using Amazon Cognito

STATUS

Accepted

CONTEXT

Authentication across Adgem services currently functions as separate entities, with each project managing its own users, access tokens, and client credentials. While this works for smaller architectures, maintainability decreases significantly as Adgem grows and new interconnected projects emerge.

This decentralized approach leads to growing complexity in token management. For example, the Targeted API (TA) currently retrieves offers via the Offer API (OA). This means the TA must store and manage its own access token as well as the OA token. If a new service is introduced, the number of tokens to manage increases, escalating complexity.

A diagram showing the current interaction between Adgem APIs is relevant here:

--- config: layout: elk --- flowchart TD A1["Offerwall <br> Publisher"] -- Embeds Iframe --> OW["Offerwall <br> Vue JS, Amplify"] OW -- Get Offers (Sanctum token) --> TA["Targeted API <br> Laravel, Elastic Beanstalk"] OW -- Create Support Requests --> SH["Service Hub <br> Laravel, Elastic Beanstalk"] SH -- Approve / Reject Support Requests --> PA["Player API <br> Laravel, Elastic Beanstalk"] TA -- Get Player History (Internal API key) --> PA TA -- Get Offers (Sanctum Token) --> OA["Offer API <br> Laravel, Elastic Beanstalk"] AD["Admin Dashboard <br> Laravel, Elastic Beanstalk"] -- Sync Offers (Internal API Key) --> OA AD -- Manage API Tokens --> OA A2["Targeted API <br> Publisher"] -- Get Offers (Sanctum Token) --> TA A3["Offer API <br> Publisher"] -- Get Offers (Sanctum Token) --> OA A2 -- Manage Integrations --> PD["Publisher Dashboard <br> Laravel, Elastic Beanstalk"] A3 -- Manage Integrations --> PD A1 -- Manage Integrations --> PD PD --> TA & OA & CA["Campaigns API <br> Laravel, Elastic Beanstalk <br> Future Product"] AD --> PD style A1 fill:#E1BEE7,color:#000000 style TA fill:transparent style OA fill:transparent style A2 fill:#E1BEE7,color:#000000 style A3 fill:#E1BEE7,color:#000000

Since the introduction of further products will heavily increase authentication complexity, moving toward centralizing authentication is a necessary and reasonable step. The goal is to implement a centralized authentication service for AG products to reduce maintenance issues and complexity.

Considered Options

1. Continue Current Decentralized Authentication

Maintain the existing setup where each service handles its own authentication and stores credentials for every service it calls.

Pros Cons
Minimal immediate cost or refactoring required. Complexity increases linearly with growth.
Teams retain full control over their service's authentication logic. High maintenance cost due to token sprawl (Targeted API needing Offer API token, etc.).
Significantly increases future technical debt.

2. Build an Internal Centralized Authentication Service

Develop and maintain a custom authentication service from scratch (e.g., using a framework like Laravel or dedicated auth library).

Pros Cons
Absolute control over all features, logic, and integration points. High upfront development cost.
Significant, long-term maintenance and operations burden.
Critical responsibility for user security, compliance, and handling security best practices (JWT issuance, key rotation, MFA).

3. Implement Amazon Cognito as the Central Authentication Service (Preferred)

Utilize Amazon Cognito, an AWS-managed service, to handle token issuance (JWT), authorization, and potentially user management in the future.

What is Amazon Cognito?

Amazon Cognito is an AWS service that handles user authentication, authorization, and user management for web and mobile applications.

In simpler terms, it’s like a ready-made login system that doesn’t require building from scratch.

It provides:

  • User Sign-Up and Sign-In: Allows to create user pools with features like username/password login, password resets, and multi-factor authentication (MFA).

  • Social & Enterprise Identity Integration: Users can log in via Google, Facebook, Apple, Amazon, or enterprise identity providers using SAML and OIDC.

  • Secure Access Control: Issues JSON Web Tokens (JWT) to control access to APIs or AWS resources.

  • User Directory & Profile Management: Stores user attributes and preferences.

It’s commonly used together with API Gateway or custom backends to secure APIs and protect AWS resources.

Cognito Authentication Flow Options

Client Credentials Flow (Preferred option)

The Client Credentials flow is the simplest of the Amazon Cognito flows. It’s used when systems or services communicate without user interaction. The requesting system uses the client ID and client secret to obtain an access token. Since both systems operate without user involvement, no consent step is necessary.

--- config: layout: elk --- flowchart LR subgraph subGraph0["AWS Cloud"] A1["Application 1"] Cg("Amazon Cognito") A2["Application 2"] end Cg -- Access token (response) --> A1 A1 -- Access token --> A2 A2 -- Validate Token --> Cg A1 -- /oauth2/token --> Cg style A1 fill:#e0f7fa,color:#000,stroke:#333 style Cg fill:#d43c3c,color:#ffffff,stroke:#888,stroke-width:2px style A2 fill:#e0f7fa,color:#000,stroke:#333

Diagram showing Client Credentials flow

This flow authenticates through the following steps:

  • Call the OAuth 2.0 token endpoint at /oauth2/token. It will issue a JSON web token (JWT).
POST https://mydomain.auth.us-east-1.amazoncognito.com/oauth2/token
Content-Type='application/x-www-form-urlencoded'&
Authorization=Basic ZGpjOTh1M2ppZWRtaTI4M2V1OTI4OmFiY2RlZjAxMjM0NTY3ODkw
grant_type=client_credentials&
client_id=1example23456789&
scope=resourceServerIdentifier1%2Fscope1%20resourceServerIdentifier2%2Fscope2&
&aws_client_metadata=%7B%22onBehalfOfToken%22%3A%22eyJra789ghiEXAMPLE%22,%20%22ClientIpAddress%22%3A%22192.0.2.252%22%7D

Authorization Code flow

The Authorization Code flow is designed for web-based authentication. In this flow, the backend manages token exchange and storage.

This flow relies on redirection. The client interacts with a web browser or a similar client, gets redirected to an authentication server, and authenticates there. Upon successful authentication, the client is redirected back to the server.

--- config: layout: elk --- flowchart LR subgraph subGraph0["AWS Cloud"] C1["Amazon Cognito"] C2["Application"] end Server["Server or rendering component"] <-- /oauth2/token --> C1 C2 --> C1 Server -- Access token --> C2 Server <-- /oauth2/authorize --> C1 App["Browser-based or <br> desktop application"] --> Server C1 <-- SAML token --> n1["IdP login"] style C1 fill:#d43c3c,color:#ffffff,stroke:#888,stroke-width:2px style C2 fill:#e0f7fa,color:#000,stroke:#333

Diagram showing Authorization Code flow

  • Call the OAuth 2.0 authorize endpoint at /oauth2/authorize. It will return a redirection response with an authorization code that can be used for requesting a token.
GET https://mydomain.auth.us-east-1.amazoncognito.com/oauth2/authorize?
response_type=code&
client_id=1example23456789&
redirect_uri=https://www.example.com&
state=abcdefg&
scope=openid+profile+aws.cognito.signin.user.admin
  • This will return a code, code=a1b2c3d4-5678-90ab-cdef-EXAMPLE11111, which can be used to request an access_token.

  • Call the OAuth 2.0 token endpoint at /oauth2/token. It will issue a JSON web token (JWT)

POST https://mydomain.auth.us-east-1.amazoncognito.com/oauth2/token
Content-Type='application/x-www-form-urlencoded'&
Authorization=Basic ZGpjOTh1M2ppZWRtaTI4M2V1OTI4OmFiY2RlZjAxMjM0NTY3ODkw

grant_type=authorization_code&
client_id=1example23456789&
code=AUTHORIZATION_CODE&
redirect_uri=com.myclientapp://myclient/redirect
  • This will return a few different tokens
HTTP/1.1 200 OK
Content-Type: application/json

{
    "access_token": "eyJra1example",
    "id_token": "eyJra2example", establish who you are
    "refresh_token": "eyJj3example", grants permission to recreate access token
    "token_type": "Bearer",
    "expires_in": 3600
}
  • Finally, the access_token should be used in order to authenticate requests.

Choosing the Right Flow

We must first choose the appropriate OAuth 2.0 flow for our primary goal: securing server-to-server (machine-to-machine) communication. The table below helps determine the best flow based on the application's nature:

Do you need machine-to-machine authentication? Is your app a web-based application where the frontend is rendered on the server? Is your app a single-page application (SPA) or mobile-based frontend application? Recommended Amazon Cognito flow
Yes No No Client Credentials flow
No Yes No Authorization Code flow
No No Yes Authorization Code flow with PKCE
Pros Cons
Managed Security: Eliminates the need to build and maintain a custom login/token system. Requires initial configuration effort (User Pools, App Clients, etc.).
Scalability: Handles features like JWTs, MFA, and social/enterprise federation out-of-the-box. Introduces a tight dependency on AWS and its limitations (e.g., non-customizable claims/scopes).
Native Integration: Integrates seamlessly with other AWS services (API Gateway Authorizers). Adherence to AWS-defined limits and workflows.

Extra features to consider

Resource Servers and Custom Scopes

A resource server in AWS Cognito hosts protected resources, such as APIs. When you register a resource server in Cognito, you define it within a user pool, grouping APIs or microservices under a single logical entity. Basically, a resource server is a remote server that authorizes access using OAuth 2.0 scopes in an access token.

Cognito can create OAuth 2.0 Resource servers and associate Custom scopes. These custom scopes in an access token authorize specific actions in an API. That means any app client in the user pool can issue a token using custom scopes in the resource servers.

sequenceDiagram participant User as User/App participant Cognito as Amazon Cognito User Pool participant API as API User->>Cognito: Request grant with custom scope <br> scope=https://example.com/MyCustomScope Cognito-->>User: Access token <br> "scope": "https://example.com/MyCustomScope" User->>API: Access token <br> Authorization: eyJra... API-->>User: Data

Using the diagram as an example, if the client has been authenticated in order to retrieve offers from the Offer API, we could define a resource server called Offer API containing a custom scope called “read.” That means when verifying the access_token the API will check if it has the proper permissions to execute the requested task. This concept is similar to abilities in Laravel.

Pre-token generation Lambda trigger & Custom Claims

Access tokens could be customized through pre-token generation using a Lambda trigger for it, In simpler words, the Lambda will act as middleware between the token generation and the initial request, ensuring the token contains customized claims or suppressing undesired claims from the token. This topic is long enough to have its own document, because there are several options to customize token payload. For further information, take a look at Pre token generation Lambda trigger - Amazon Cognito.

DECISION

  • Implement Amazon Cognito as the Central Authentication Service for Adgem products, starting with the Client Credentials flow for server-to-server communication.
  • Transition to using Amazon Cognito for user authentication and authorization across all Adgem products.
  • The initial implementation will focus on securing API-to-API communication between Targeted API and Offer API.
  • Resource Servers and Custom Scopes will be defined for each API to manage access control effectively.
    • Offer API Resource Server
    • Scopes: read, write, update, delete
    • Targeted API Resource Server
    • Scopes: read
  • We will deploy Cognito in the us-east-2 (Ohio) region to align with our existing AWS infrastructure.
  • Token expiration policy can be set between 5 minutes to 1 day, we'll need to define the appropriate expiration times for each use case, and check for any potential impacts on user experience, further exploration may be required to optimize this aspect.

Future Considerations

  • Define a policy for token expiration and refresh strategies.
  • Determine how many tokens would be issued per service based on usage patterns and requirements.
  • Analyze cost implications as usage scales based on the chosen token strategy.

CONSEQUENCES

  • Reduced Maintenance Burden: By leveraging a managed service, we reduce the overhead of maintaining custom authentication logic.
  • Improved Security Posture: Amazon Cognito provides built-in security features, reducing the risk of vulnerabilities in custom implementations.
  • Scalability: As Adgem grows, Cognito can handle increased authentication demands without significant changes to our architecture.
  • AWS Dependency: Relying on Amazon Cognito introduces a dependency on AWS services, which may have implications for cost and vendor lock-in.
  • Initial Learning Curve: Teams will need to familiarize themselves with Amazon Cognito and its features, which may require training and documentation updates.
  • Integration Effort: Existing services will need to be refactored to integrate with Cognito, which may require significant development effort.

Pricing

Amazon Cognito offers three pricing tiers for user pools: Lite, Essentials, and Plus.

Lite and Essentials Tiers

The Lite tier provides basic authentication capabilities including social identity integration and password-based authentication, designed for cost-conscious use cases. The Essentials tier builds upon Lite by offering more comprehensive authentication features, enabling secure and customizable sign-up and sign-in experiences for applications, with both tiers being priced based on the actual usage.

Amazon Cognito offers a perpetual free tier for both Essentials and Lite pricing tiers that doesn't expire after the standard 12-month AWS Free Tier period, making it available to all AWS customers indefinitely. This free tier includes 10,000 monthly active users (MAUs) per month for users who sign in directly through Amazon Cognito or via social identity providers when using either the Lite or Essentials tier configurations.

Lite Pricing Table

Amazon Cognito Lite pricing is based on monthly active users (MAUs) in the user pool with the Lite pricing tier, where a user counts as a Lite MAU if they're active at least once in a month with the Lite tier configuration and were never active under Essentials or Plus tiers.

Pricing Tier (MAUs) Price per MAU
First 10,000 (Free-tier) $0.00
10,001-100,000 $0.0055
100,001 - 1,000,000 $0.0046
1,000,001 - 10,000,000 $0.00325
Greater than 10,000,000 $0.0025

Amazon Cognito's advanced security features (ASF) incur additional costs per monthly active user beyond the base pricing, even when used in audit mode. These features include compromised credentials detection, adaptive authentication, advanced security metrics, and access token customization, with pricing remaining consistent with rates before November 22, 2024. It's important to note that these advanced security features are not available in the AWS GovCloud (US-West) region.

Pricing Tier (MAUs) Price per MAU
First 50,000 $0.050
Next 50,000 $0.035
Next 900,000 $0.020
Next 9,000,000 $0.015
Greater than 10,000,000 $0.010
Essentials Pricing Table

Amazon Cognito Essentials pricing is based on Monthly Active Users (MAUs) in the user pool, with a user counted as an Essentials MAU if they're active at least once during a month with the Essentials tier configured and were never active when the pool was configured as Plus. Essentials is the default tier for new user pools, and customers can freely switch between Lite, Essentials, or Plus tiers at any time to match their application requirements. Additionally, there's a special eligibility for customers to upgrade existing user pools without Advanced Security Features to the Essentials tier while maintaining their current pricing until November 30, 2025.

MAUs Price per MAU
First 10,000 (Free-tier) $0.00
Greater than 10,000 $0.015

Plus Tier

The Plus tier is an enhanced security offering that builds upon the Essentials tier by providing advanced threat protection capabilities. It specifically includes risk-based adaptive authentication, detection of compromised credentials, and the ability to export authentication event logs for security analysis. This tier is designed for customers who have higher security requirements and need more sophisticated tools to protect against suspicious login attempts and potential threats.

Plus Pricing

Amazon Cognito Plus pricing is based on Monthly Active Users (MAUs) in the user pool with the Plus pricing tier, where a user is counted as a Plus MAU if they're active at least once during the month. The pricing structure differentiates between users who sign in directly with their credentials (including via social identity providers) and those who sign in through enterprise directories using SAML federation. Notably, customers using Advanced Security Features (ASF) can achieve significant cost savings—up to 60% on their monthly bill—by configuring their user pool with the Plus pricing tier.

Amazon Cognito Plus costs $0.02 per MAU

Cognito Add-ons

Additionally, Cognito supports machine-to-machine (M2M) authorization and higher requests per second (RPS) as add-ons, each priced based on usage.

Tier comparison

Features Lite Essentials Plus
Basic capabilities for password-based authentication targeted for value-oriented use-cases. Additional capabilities requires customization Core set of capabilities that enable seamless authentication for end-users such as passwordless login Enhanced set of capabilities for applications with elevated security needs
40 million users or more Yes Yes Yes
Sign-in with social, SAML, or OIDC providers Yes Yes Yes
Sign-in with username and password Yes Yes Yes
MFA with authenticator apps and SMS one-time codes Yes Yes Yes
Custom runtime action with Lambda triggers Yes Yes Yes
Customize managed login page with CSS Yes Yes Yes
99.9% service level agreement Yes Yes Yes
Customize managed login page with visual editor Yes Yes
MFA with email one-time codes Yes Yes
Passwordless sign-in with one-time codes Yes Yes
Passkeys sign-in with biometrics and hardware keys Yes Yes
Prevent reuse of previous passwords Yes Yes
Customize access token scopes and claims at runtime Yes Yes
Support for refresh token rotation Yes Yes
Protect against malicious sign-in attempts Yes
Log and analyze threat profiles and user activity Yes
Risk-based adaptive authentication Yes
Compromised credentials detection to protect against unsafe passwords Yes
Export threat profiles and user activity Yes
Machine-to-machine authorization Add-on Add-on Add-on
Higher API RPS quota Add-on Add-on Add-on

Machine-to-Machine Authorization Add-on

Amazon Cognito provides machine-to-machine (M2M) authorization through OAuth 2.0 client credentials flow, allowing to create app clients that represent the services or APIs. With this feature, access token can be issued in exchange for client credentials, configure token validity periods, and monitor token usage per app client. The pricing model is straightforward - it's charged monthly based on successful token responses, with no extra costs for the number of registered app clients in the account (though further contact with the account team is required if more than 2,500 app clients are needed).

Tier Number of token requests per month Price
Tier 1 1 - 250,000 $2.250 per 1000 token requests
Tier 2 250,001 - 5,000,000 $1.500 per 1000 token requests
Tier 3 5,000,001 - And above $1.125 per 1000 token requests

Risks

  • Service Limits: Amazon Cognito has service limits that may impact scalability. We will need to monitor usage and request limit increases as necessary.
  • Feature Limitations: Cognito may not support all desired features or customizations, potentially requiring workarounds or additional services.
  • Cost Management: While Cognito is cost-effective for many use cases, we will need to monitor costs as usage scales to ensure it remains within budget.

NOTES

References

Original Author

Daniel Ballesteros

Approval date

Approved by

Appendix