← AWS Security Specialty: evidence-based security
21 / 25 · 110 MIN

Application identity and authorization

Follow identity from login to an authorized operation, including tokens, caching, and permissions evidence.

1. Choose the right identity

In a fictional operations portal, separate three needs: authenticating a person, obtaining credentials for AWS services, and authorizing an application action. A Cognito user pool supplies the directory and OIDC tokens; an identity pool can supply temporary AWS credentials associated with roles. Do not introduce AWS credentials into the browser if the design only requires calling your API with a token. For a batch without human interaction using the token endpoint, consider client credentials and custom scopes with a protected secret. That flow represents the machine, not an operator. Project inventory should identify who issues each credential, where it is validated, who manages its lifetime, and which operations it permits. Answering these questions avoids treating all tokens as equivalent.

2. Interpret claims in the correct context

An identity-pool role needs to restrict origin through aud. In that context, aud is the identity pool ID; amr distinguishes authenticated from unauthenticated and sub identifies that pool identity. The original user-pool sub is not interchangeable. A guest role with private writes remains dangerous even with short sessions. For user-pool JWTs, verify signatures before trusting groups and other claims. Check issuer, recipient, validity, and token_use according to the contract. In ID tokens, aud identifies the app client; in access tokens without resource binding, check client_id. Keep a fictional example of each context in the runbook and clearly identify which verifier processes it.

3. Handle rotation and revocation

Decoding a JWT reveals content but does not establish integrity. Use a suitable verification library and keys obtained from the trusted source. An unfamiliar kid can reflect legitimate rotation: refresh the expected pool JWKS cache and verify again without accepting the token merely because issuer text looks correct. A valid signature and future expiration also do not independently detect later revocation. Define the access-cutoff deadline and the mechanism supporting it; do not promise immediate cutoff with exclusively offline checks. For authorization code with PKCE, preserve the original verifier until code exchange. Generating another verifier then breaks its relationship with the submitted challenge. Never use real tokens in shared incident-meeting examples.

4. Express the API rule

In an HTTP API JWT authorizer, aud takes precedence over client_id when both exist. That authorizer does not universally distinguish ID from access tokens; appropriate scopes or exclusive issuer/audience values help express the contract. An authorizationScopes list accepts at least one listed scope rather than requiring all of them. If a release rule requires two simultaneous conditions, that list is insufficient. Do not infer permissions from GET and POST names without configuration. For IdP key rotation, account for API Gateway public-key caching and a validity overlap. Rehearse tokens with the wrong recipient, insufficient scope, and wrong purpose alongside the permitted case. Negative testing demonstrates the restriction the sponsor actually approved.

5. Scope cached decisions

An HTTP API Lambda authorizer can reuse results through identity sources. With simple responses, a cached boolean applies to requests matching the same key. If reading and release require different rules, add routeKey to the key and evaluate the route, or use a suitable granular policy. Reducing TTL limits failure duration but does not repair inappropriate sharing across routes. A missing required header produces 401 before invocation; a 500 with an invocation-permission error points to authorizer integration. The exercise below demonstrates collisions in a fictional cache; it neither validates JWTs nor implements a production-ready authorizer. For revocation, also consider how long an old decision can continue being reused.

6. Reduce permissions with sufficient evidence

Absence from a report does not always mean the same thing. Unused access follows a configured window and permission age; a recent permission may not yet be evaluated. Service last accessed includes denied attempts, and action last accessed does not cover data-plane events. To conclude that reading occurred, find relevant events and their outcomes. To remove access, combine observation with actual cycles, including quarterly closing and incident recovery. A policy generated from CloudTrail has limits too: iam:PassRole is not included. Treat it as a candidate for review and rehearsal. A reduction plan should identify each exception owner, the basis for its dependency, and when that justification will be reassessed.

7. Hand over a demonstrable control

ValidatePolicy helps find grammar and best-practice issues. CheckNoNewAccess compares a proposed policy with a reference to look for additional access. Neither replaces every functional test in the context of actual policies and conditions. A usage-plan API key also does not replace authentication and authorization; quotas are best effort rather than a hard cost guarantee. In the APS summary, connect each requirement to proof: login, permitted operation, denied operation, behavior after permissions change, and error diagnosis. Retain records without secrets and identify IdP, gateway, and application owners. This lesson produces authorization bounded by identity, operation, and time, with evidence that restrictions remain valid when caches or keys change.

# Original fictional cache-key model, not a JWT verifier or AWS authorizer.
# No credentials, network calls, or AWS changes.
def key(identity, route, per_route):
 return (identity, route) if per_route else (identity,)
def allowed(route):
 return route == "GET /status"
cache = {key("demo-user", "GET /status", False): True}
assert cache[key("demo-user", "POST /release", False)] is True
scoped = {key("demo-user", "GET /status", True): True}
assert key("demo-user", "POST /release", True) not in scoped
assert allowed("GET /status") is True
assert allowed("POST /release") is False
assert key("other-user", "GET /status", True) not in scoped
print("five cache-scope cases passed; no token was verified")
IN PRACTICE

A read fills the cache and incorrectly permits a release. Per-route rehearsal reveals the difference between valid login and authorized operation.

Common pitfalls

aud out of context; decoding as verification; scopes as AND; missing tracking as proven disuse.

Related topics: Identity and data · Acceptance and continuity

Take this idea with you

Authenticate identity and demonstrate operation authorization in the intended context and period.

Create account

Reference: Amazon Cognito user pools · SCS-C03

AWS is a trademark of Amazon.com, Inc. or its affiliates. bigsavant.com is an independent preparation platform and is not affiliated with, associated with, sponsored, authorised or endorsed by AWS. Content and questions are original, are not official exam questions, and completing our tests does not award or guarantee any certification. Names are used only to identify the subject. All other trademarks belong to their respective owners.