AWS Cognito simplified

Often when we hear that app would need login, it often sounds as simple feature: a user opens application, enters credentials and gets access.

But in real AWS applications, especially enterprise applications, login is usually only the first part of a larger identity design. Each customer has its own users, some are admins, some users can only read data while other should only see data that belongs to their company. One customer might wants users to log in with Microsoft Entra ID and another customer uses Okta. Suddenly simple login becomes larger part of architecture, dictates solution and answer questions like:

  • Where do users come from?
  • Can they use company SSO?
  • Which APIs can they call?
  • What data are they allowed to access?
  • Does the frontend needs direct AWS access?

Each of these actually maps to a different part of Cognito architecture.

Start with user

The first thing you need is a place that represents your users and that would be Cognito User Pool.

A Cognito User Pool is the place where users are represented. It handles authentication, user sign-in, sign-up flows, password policies, MFA, tokens, and user-related information. In a simple application, users might log in directly with an email and password managed by Cognito. The flow would look like this:

User → Cognito User Pool → Web App

After successful authentication, Cognito issues tokens that the frontend can use. At this point, the application knows that the user is authenticated and that’s it.

But for enterprise applications, this is often not enough. Many enterprise users should not create a new username and password just for your application. They already have a corporate identity. That leads to the next part of the design.

Enterprise Login with SSO

Lets say one of your customers uses Microsoft Entra ID. Their employees already use that account to access internal tools, email, dashboards or other company things. They expect the same login experience within application (solution) you are building for them. In that case, your application should not manage their passwords directly. Instead you should use federation. This is where SAML or OIDC comes in.

Cognito User Pool can integrate with external identity providers using SAML or OIDC. That external identity provider can be Entra ID, Okta, Ping Identity or any other corporate SSO system. The flow becomes:

User → Company SSO → Cognito User Pool → Web App

The user is redirected to the company identity provider. After successful login, the identity provider sends the result back to Cognito. Cognito then issues token for your application. This keeps your application integrated with the enterprise identity system.

It also means that important things such as onboarding, offborading, passworng policies, MFA and account lifecycle can be controlled by the customers’ identity provider.

API Access

Now the user is logged in with or without external identity provider depending on the situation and the next question is what this user can do inside the application. For example the frontend may need to call APIs such as:

  • Get dashboard data
  • Create a report
  • Upload a document
  • Manage users
  • Change account configurations

This is where API authorization becomes important. When FE calls your API, it sends the token received from Cognito. API Gateway can validate token before the request reaches your backend.

The flow looks like this:

Frontend → API Gateway → Cognito Authorizer → Backend

If the token is missing, invalid, or expired, API Gateway rejects the request before it reaches your application code. That is the base security layer already. However validating token only answer question which user is authenticated and it doesn’t automatically answer every business question. For example, just because a user is logged in does not mean they should be allowed to call every API. This is where scope can help.

API scopes

Scopes can be used to describe what a token is allowed to access.

For example:

  • read
  • write
  • admin

You can configure API Gateway so that some routes require specific scopes.

For example:

  • GET /reports may require read
  • POST /reports may require write
  • POST /users may require admin

This gives you another layer of protection at the API Gateway level. API Gateway can reject a request if the token does not contain the required scope. That is useful because some authorization decisions can be handled before the request even reaches your backend.

But scopes are usually not enough for full business authorization.

They are good for API-level permissions, but most applications also need business context.

Cognito attributes, groups and claims

Now let’s say that two users both have permission to call:

GET /reports

But user A belongs to Customer A and user B to Customer B. Both users are allowed to call same API, but they must not see the same data. This is where claims, groups and custom attributes become important. A token can contain additional information about the user such as:

customerId
roles
groups
pricingPlan
isAdmin

Some of this information can come from the external identity provider. Some of it can be managed inside Cognito. Some of it may even be managed in your own application database. The backend can then use this context to apply business rules.

For example:

  • A user with customerId = customer-a should only see data for customer-a.
  • A user with role = admin can manage users.
  • A user with pricingPlan = basic can access only basic features.

This is the essence difference between authentication and real authorization.

What if Frontend needs direct AWS access

Most of the time, the frontend should call your APIs, and your backend should talk to AWS services, as that keeps permissions easier to control, but sometimes it needs direct AWS access. Common example could be direct file upload from FE to S3.

Instead of sending a large file through your backend, you may want the browser to upload directly to S3. A Cognito User Pool token is not enough for that because you need temporary AWS credentials. It proves who the user is, but it does not give the browser AWS permissions. For that, you need Cognito Identity Pool. An Identity Pool can exchange the authenticated user identity for temporary AWS credentials.

The flow becomes:

Logged-in user -> Identity Pool -> temporary AWS credentials -> S3

Those credentials should be tightly scoped. For example, the user should not get general S3 access. They should only be allowed to upload to a specific bucket and usually even to specific S3 path such as:

s3://my-bucket/customer-a/user-123/uploads/

This is important distinction: Cognito User Pool is about user authentication and Identity Pool is about giving temporary AWS credentials to an authenticated or unauthenticated identity.

Putting It Together

So if we go back to the original request: “the app needs login” — the real design may look like this:

The user signs in with company SSO.

Company SSO sends the authentication result to Cognito User Pool.

Cognito issues tokens for the web application.

The frontend sends those tokens when calling backend APIs.

API Gateway validates the token using a Cognito Authorizer.

API Gateway can also check required scopes for specific routes.

The backend reads claims, groups, and attributes from the token.

The backend applies business rules, such as customer-level data isolation.

If the frontend needs direct AWS access, Cognito Identity Pool provides temporary AWS credentials with limited permissions.

Conclusion
  • Cognito User Pool — Who is the user?
  • SAML / OIDC federation — Can the user log in with company SSO?
  • Hosted UI — Where does the user complete the login flow?
  • API Gateway Authorizer — Is this token valid for calling the API?
  • Scopes — Is this token allowed to access this API route?
  • Claims, groups, and attributes — What business context does this user have?
  • Identity Pool — Does the frontend need temporary AWS credentials?