Back to Resume
PrivateDecember 2024· 1 week

Cognito-Authenticated Bedrock API Access Tooling

Cognito-Authenticated Access Scripts for a Bedrock-Backed API

RoleAI/ML Engineer

A software consultancy needed the client-side half of a Cognito-protected, Bedrock-backed API: a way to authenticate a service account against a Cognito user pool and use the resulting token to call the endpoint. I wrote three short scripts covering password setup, token fetch, and the authenticated call, documented a direct-boto3 alternative to the API Gateway proxy, and produced a pricing estimate for the intended production scale. It is a thin utility folder, not a platform, and the case study says so plainly.

The problem

The client had (or was building) an API Gateway endpoint in front of a Bedrock-backed service, guarded by a Cognito authorizer. Before that endpoint could be used or tested, something needed to turn a Cognito username and password into a valid JWT, handle the case where a service account still had a temporary password, and make an authenticated call with that JWT. There was no existing client-side tooling for any of this, and no cost picture for what the Bedrock/API Gateway/Lambda stack would cost once it carried real traffic.

Constraints

  • ·Scope was the client side only: the API Gateway endpoint and whatever sits behind it were being built or operated separately, out of reach for this work.
  • ·The Cognito app client uses a client secret, so every auth call needs a SECRET_HASH computed correctly, not just a username and password.
  • ·The service account was created with a temporary password, so the NEW_PASSWORD_REQUIRED challenge had to be handled, not just a normal login.
  • ·The client-side script had to work as a bearer-token consumer only, with no AWS credentials of its own, since it was never meant to call AWS directly.
  • ·A cost estimate was needed before committing further, at a scale of tens of billions of Bedrock input tokens per month.

Architecture

Three standalone Python scripts and one written reference cover the client side of a Cognito-protected Bedrock API. Two scripts authenticate against Cognito to set up and use a service account's credentials, and the third uses the resulting JWT to call an API Gateway endpoint that fronts a server-side Bedrock integration outside this project. A written reference documents the alternative of calling Bedrock directly with boto3, and a saved AWS Pricing Calculator estimate sizes the same stack for its intended production scale.

  • fetch_token.py — Cognito USER_PASSWORD_AUTH with a hand-computed SECRET_HASH, returns the IdToken
  • pass-update.py — drives the NEW_PASSWORD_REQUIRED challenge to finalize a temporary-password account
  • request_bedrock.py — POSTs a Bedrock-shaped prompt payload to an API Gateway URL with the JWT as a Bearer header
  • API Gateway + Cognito authorizer + Lambda + Bedrock — server side, outside this folder; inferred from the pricing estimate and the architecture sketch, not built or verified here
  • readme_bedrock_direct.md — a written reference for calling bedrock-runtime.invoke_model directly via boto3, with no corresponding script

Read left to right: fix the password if needed, fetch a token, then call the API. Everything past the API Gateway URL is server-side and out of scope for this folder.

How one request flows

  1. 01If the Cognito service account still has a temporary password, run pass-update.py: it authenticates once, catches the NEW_PASSWORD_REQUIRED challenge, and responds with the new password using the Session token Cognito returns.
  2. 02Run fetch_token.py: it computes the Cognito SECRET_HASH by hand (HMAC-SHA256 of username plus client id, keyed by the app client secret) and calls InitiateAuth with USER_PASSWORD_AUTH.
  3. 03Pull AuthenticationResult.IdToken out of the response. That JWT is the only credential the next step needs.
  4. 04Run request_bedrock.py: it builds a Bedrock-shaped JSON payload (prompt, max_tokens, temperature, top_p, top_k) and POSTs it to the API Gateway URL with Authorization: Bearer <JWT>.
  5. 05API Gateway validates the JWT against its Cognito authorizer and forwards the request server-side. What happens next, a Lambda invoking Bedrock per the pricing estimate and architecture sketch, happens outside this folder.
  6. 06The script prints the JSON response on success, or the status code and error text on failure.

Engineering decisions

Hand-computed SECRET_HASH instead of an SDK call

What. Both auth scripts compute the Cognito SECRET_HASH themselves: base64(HMAC-SHA256(client_secret, username + client_id)), using Python's hmac and hashlib rather than a helper from an AWS SDK.

Why. The scripts only make two Cognito HTTP calls total. Pulling in boto3 for one hash calculation would have added a dependency the scripts otherwise don't need.

Tradeoff. boto3's Cognito Identity Provider client would compute the hash internally and handle more of the auth flow. That convenience wasn't worth the extra dependency for scripts this small.

Bearer JWT client, no AWS credentials

What. request_bedrock.py holds only a JWT and attaches it as an Authorization: Bearer header. It never imports boto3 and never touches an AWS access key or secret.

Why. This keeps the client decoupled from whatever runs behind the API Gateway endpoint. If the server side changes model, region, or provider, the client script does not need to change.

Tradeoff. The alternative, giving this script AWS credentials and calling Bedrock directly, is exactly what readme_bedrock_direct.md documents as a separate path, deliberately not the one built here.

A written direct-boto3 path, not a second script

What. readme_bedrock_direct.md walks through calling bedrock-runtime.invoke_model directly against us.anthropic.claude-3-5-haiku-20241022-v1:0, with boto3-managed SigV4 signing.

Why. It gives the client a documented alternative to the API Gateway proxy, for a case where a server-side proxy isn't wanted, without building and maintaining a second script nobody asked for.

Tradeoff. Building it out would have meant maintaining two separate credential stories side by side (AWS keys versus a bearer JWT). Writing it up was the right size for what was needed.

A pricing estimate before committing to scale

What. A saved AWS Pricing Calculator export sizes Bedrock, API Gateway, and Lambda for the traffic the API is meant to carry: 20,000M Bedrock input tokens/month and roughly 1,000,000,000 Lambda invocations/month.

Why. Putting a number on it, roughly $5,235/month, meant the client could weigh the API Gateway/Lambda proxy path against the direct-boto3 path with real cost figures instead of a guess.

No retry or token-refresh logic in the client scripts

What. None of the three scripts retry a failed call, cache a token, or refresh one that expires. Each is a single, synchronous request that prints its result or raises an exception.

Why. These are one-shot utility scripts, run by hand while setting up or testing an account, not a long-running client. Retry and refresh logic belongs in whatever calls this API for real, not in a throwaway script.

Tradeoff. A production client would need to cache the JWT and refresh it before it expires. That was explicitly out of scope for a script meant to be run once per test.

What changed along the way

  • There isn't much of an iteration story here. Per file mtimes, the three scripts and their Turkish usage readme landed together on the same day, and readme_bedrock_direct.md was added about two weeks later as a follow-up reference once the direct-boto3 alternative came up.
  • This case study is written smaller than most of the others in this portfolio on purpose: the honest description of this folder is a thin utility, not a platform, and padding it would misrepresent the work.

Security and reliability

  • ·Every credential (Cognito app client id and secret, username, password, JWT, API Gateway URL) is now read from environment variables, not hardcoded, after this cleanup pass.
  • ·The request script never holds AWS credentials, only a bearer JWT, so a leaked copy of it carries a smaller blast radius than one holding AWS access keys.
  • ·No retry, backoff, or token-refresh logic. Each script is a single manual run, by design.
  • ·The Cognito app client secret and the bearer JWT found hardcoded in the original source were live credentials. Sanitizing the files does not invalidate them, so they still need to be rotated or revoked at the AWS Cognito console.

Metrics

3
standalone scripts covering the full password-to-token-to-call path
measured
~$5,235/mo
AWS Pricing Calculator estimate for Bedrock + API Gateway + Lambda at target scale
target
20,000M
Bedrock input tokens/month sized in the estimate
target

What shipped

  • Working end-to-end path from a Cognito username/password to a JWT to an authenticated call against a Bedrock-backed API Gateway endpoint.
  • A written, buildable reference for the alternative direct-boto3 Bedrock invocation path, for when a server-side proxy isn't wanted.
  • A saved AWS Pricing Calculator estimate sizing the same Bedrock/API Gateway/Lambda stack for its intended production traffic (20B input tokens and ~1B Lambda invocations per month).

Lessons learned

  • ·For a thin client that only needs to call an authenticated endpoint, a bearer JWT with no AWS credentials keeps the blast radius small if the script ever leaks.
  • ·Hand-computing a hash you only need once, Cognito's SECRET_HASH here, is a reasonable call. Reaching for a full SDK just for that would have been the wrong size fix.
  • ·Writing up an alternative path, the direct-boto3 Bedrock call, is worth doing even without building it, since it changes what the client can choose later.
  • ·A pricing estimate is worth producing before scaling a proxy pattern like this one. The API Gateway and Lambda costs at real volume are not obvious from the code alone.
  • ·Hardcoded credentials in a script folder like this are a real risk the moment it's shared or archived. Environment variables should be the default from the first version, not a later cleanup.

Tech stack

AI & retrieval
Amazon Bedrock (target service behind the API, called server-side, not from this folder)Claude 3.5 Haiku via bedrock-runtime.invoke_model (documented direct-call path only)
Backend
AWS Cognito, USER_PASSWORD_AUTH and the NEW_PASSWORD_REQUIRED challengeAWS API Gateway, Bearer-JWT-authenticated endpoint
Data & state
None: stateless scripts, no data store
Ops & quality
AWS Pricing Calculator estimate for production-scale sizing