Task 0 · 8 tasks
Identity with Cognito
Two kinds of identity: a machine-to-machine token for your services, and signed-in users for people.
The “Enterprise Readiness” challenge
The local prototype won Alice over. Now she wants it in production for all 1,200 employees, and IT has one rule: nothing gets called without a verifiable identity.
In the original workshop this meant an hour of console clicking. Here the platform does it, and you spend the time understanding what a token actually carries.
What the platform provisions for you
uv run bootcamp.py up 0- A Cognito user pool for you, with a resource server and the scope
datastream/mcp.access - An app client allowed to use the OAuth client credentials (M2M) flow
- A Cognito domain that issues tokens (named
ds-<10 hex chars>-<account>) - A second, user app client (username + password sign-in, no secret) and two DataStream users,
alice-chenandjordan-lee, whose generated passwords are stored as SSM SecureString parameters under/workshop/awsworkshop-<name>-users/ - Nothing for model access: your laptop already reaches the LiteLLM gateway keylessly with your participant role
Adding this stage usually takes ~6 min; the CLI prints progress (and full terraform output with --verbose).
Most of that time is a deliberate ~5 minute pause on the very first up 0: a new Cognito domain needs time for DNS to propagate. A heartbeat line every minute shows it is still working.
What you do as a developer
Get a token
terminaluv run bootcamp.py tokenThat long string is a JWT. Every later stage (MCP runtime, Gateway, Agent) checks it.
Read the token
Challenge
Decode your token and find the claim that lets you call MCP tools, and the claim that says which app client you are.
Hint 1
A JWT is three base64url parts separated by dots: header, payload, signature. The claims live in the payload; you don't need the signature to read them.
Hint 2
Split on
., take part 1, pad it to a multiple of 4 andbase64.urlsafe_b64decodeit. Look atscopeandclient_id.Solution
terminal · macOS / LinuxTOKEN=$(uv run bootcamp.py token) uv run python -c "import base64,json,sys; p=sys.argv[1].split('.')[1]; print(json.dumps(json.loads(base64.urlsafe_b64decode(p + '=' * (-len(p) % 4))), indent=2))" "$TOKEN"terminal · Windows PowerShell$TOKEN = uv run bootcamp.py token uv run python -c "import base64,json,sys; p=sys.argv[1].split('.')[1]; print(json.dumps(json.loads(base64.urlsafe_b64decode(p + '=' * (-len(p) % 4))), indent=2))" $TOKENscopeisdatastream/mcp.access: the resource-server scope your runtimes and Gateway require.client_ididentifies your M2M app client (the MCP runtime's and Gateway's JWT authorizers allow only that client).expis when it expires.Sign in as a user
Machines use the M2M token above. People sign in: the CLI reads Alice's password from SSM Parameter Store and calls Cognito
InitiateAuth, exactly what a login page would do.bootcamp_cli/users.pydef stored_password(session: boto3.Session, out: dict, username: str) -> str: """A story user's password from SSM Parameter Store (decrypted).""" name = f"{out['users_parameter_path']}/{username}" return session.client("ssm").get_parameter(Name=name, WithDecryption=True)["Parameter"]["Value"] def sign_in(session: boto3.Session, out: dict, username: str, password: str) -> str: """Sign in through the pool's user app client (no client secret) and return the user's access token.""" response = session.client("cognito-idp").initiate_auth( ClientId=out["user_client_id"], AuthFlow="USER_PASSWORD_AUTH", AuthParameters={"USERNAME": username, "PASSWORD": password}, ) return response["AuthenticationResult"]["AccessToken"]terminaluv run bootcamp.py token --actor alice-chenDecode it like the M2M token: this one has a
usernameclaim (alice-chen), the user client'sclient_id, and nodatastream/mcp.accessscope. In Task 4 your agent accepts only this kind of token, and reads who is talking fromusername.See your ids
terminaluv run bootcamp.py statusPrints
POOL_IDand friends. Later stages add more lines here.
Check your work
uv run bootcamp.py test --only 0Four checks pass: Cognito issues a token for your client, both story users sign in with their stored passwords, litellm keyless identity (the gateway recognises your role) and no direct bedrock (a direct Bedrock call is refused with AccessDenied, as intended).
Under the hood
Amazon Cognito is AWS's identity provider. For service-to-service calls it uses the OAuth 2.0 client-credentials grant: your app client trades its id and secret for a short-lived access token scoped to datastream/mcp.access. AgentCore resources are configured with a JWT authorizer pointing at the pool's OIDC discovery URL, so they validate signatures and scopes without ever calling Cognito per request.
For people, the pool holds users. Signing in (InitiateAuth with USER_PASSWORD_AUTH) returns an access token whose username claim names the user, signed by the pool. A service that trusts that claim, and nothing the caller can simply type, knows who it is serving.
You work as the role bootcamp-participant-<name>, which the CLI assumes for you (BOOTCAMP_ROLE_ARN only overrides it). That role can only touch resources tagged Participant=<name> or named awsworkshop-<name>-*, and every role you create carries a permissions boundary that blocks direct Bedrock calls: LiteLLM is the only path to a model.