Developing with an Agentic IDE and Budget Control: An Auth0 IdP Example
Whatever your team builds — internal tools, AI assistants, coding agents, or data pipelines — Amazon Bedrock can support it. But as adoption grows, so does the need to understand and control costs. Who is consuming what? Which team is driving spend? When should an alert fire? Let's continue with cost allocation, this time with a third party Identity provider, Auth0.
This guide teaches you how to set up per-developer cost tracking and budget management on Bedrock using Auth0 as the identity provider. By the end, every API call is tagged with the developer's identity automatically, costs are visible per user in Cost Explorer, and alerts notify individuals when they approach their budget — with no changes to how developers authenticate or work. Read more about cost allocation methods here .
Prerequisites
- An AWS account with admin access
- Bedrock model access enabled (at least one Claude model)
- An Auth0 account (free tier works)
- Python 3.10+ on the developer machine
- A browser
Step 1: Create an Auth0 Application
- Open your Auth0 Dashboard -> left nav -> Applications -> Applications
- Click Create Application
- Name:
bedrock-cli - Type: Single Page Application
- Click Create
- Go to the Settings tab of the new application
- Verify Token Endpoint Authentication Method is set to None — this is set automatically for SPA but worth confirming
- Under Application URIs, set:
- Allowed Callback URLs:
http://localhost:8400/callback - Allowed Logout URLs:
http://localhost:8400
- Allowed Callback URLs:
- Scroll to the bottom -> click Show Advanced Settings -> Grant Types tab
- Ensure Authorization Code is checked
- Click Save Changes
- Note down the Domain (e.g.
your-tenant.auth0.com) and Client ID
Step 2: Create test users and set their metadata
Auth0 supports arbitrary key/value pairs on each user via
Post-Login Action (Step 4) reads these and injects them as claims into the access
token. This means today you tag by
or any other dimension — just add the key to the user and update the Action.
user_metadata. ThePost-Login Action (Step 4) reads these and injects them as claims into the access
token. This means today you tag by
alias, tomorrow you can add team, costcenter,or any other dimension — just add the key to the user and update the Action.
Create users
- Auth0 Dashboard -> left nav -> User Management -> Users
- Click Create User
- Fill in:
- Email:
jack@yourcompany.com - Password: a temporary password
- Connection: Username-Password-Authentication
- Email:
- Click Create
- Repeat for
bob@yourcompany.com
Set user metadata
After creating each user:
- Click the user -> scroll to Metadata section
- Under user_metadata, enter a JSON object with whatever tags you want:
1
2
3
4
{
"alias": "jack",
"team": "AI"
}- Click Save
Ifaliasis not set inuser_metadata, the Action falls back to the email
prefix (jack@yourcompany.com->jack). Theteamkey (or any other key)
only appears as a session tag if you add it to both the user metadata and the
Action code in Step 4.
How tags are derived
| user_metadata key | Session tag | Example value |
|---|---|---|
alias | alias | jack |
team | team | platform |
| (fallback if alias missing) | alias | email prefix |
Step 3: Create an Auth0 API (Resource Server)
AWS validates the
tokens when a resource server (API) is defined and requested.
aud claim in the JWT. Auth0 only includes aud in accesstokens when a resource server (API) is defined and requested.
- Auth0 Dashboard -> left nav -> Applications -> APIs
- Click Create API
- Fill in:
- Name:
AWS Bedrock - Identifier:
https://bedrock.aws(this becomes theaudiencevalue — any URL works, it does not need to be reachable) - Signing Algorithm: RS256
- Name:
- Click Create
- Note down the Identifier — you will use it as
AUDIENCEin the login script and in the IAM trust policy
Allow user-delegated access
After creation, go to the API's Settings tab -> scroll to Access Settings:
- User-delegated Access -> select All apps allowed
- Client Access -> leave as No apps allowed (not needed for browser login)
Click Save.
This controls which applications can request tokens for this API on behalf of
users (Authorization Code flow). Without this, Auth0 returns:Client is not authorized to access resource server
Step 4: Add a Post-Login Action to inject session tags
Auth0 does not include
supports reading session tags directly from the JWT — no second
call needed — if the claims follow the
format defined by AWS.
email in access tokens by default. More importantly, AWSsupports reading session tags directly from the JWT — no second
AssumeRolecall needed — if the claims follow the
https://aws.amazon.com/tags namespaceformat defined by AWS.
There are two supported formats. The nested format is the cleanest for Auth0:
1
2
3
4
5
6
7
"https://aws.amazon.com/tags": {
"principal_tags": {
"alias": ["jack"],
"team": ["AI"]
},
"transitive_tag_keys": ["alias", "team"]
}The Action sets this structure from
script needed.
user_metadata. Any key you add touser_metadata automatically becomes a session tag — no changes to the loginscript needed.
Create the Action
- Auth0 Dashboard -> left nav -> Actions -> Library
- Click Build Custom
- Fill in:
- Name:
Inject AWS session tags - Trigger: Login / Post Login
- Runtime: Node 18
- Name:
- Click Create
- Replace the default code with:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
exports.onExecutePostLogin = async (event, api) => {
const meta = event.user.user_metadata || {};
// Keys to forward as AWS session tags.
// Add any key here that exists in user_metadata.
const TAG_KEYS = ['alias', 'team', 'costcenter'];
const principal_tags = {};
const transitive_tag_keys = [];
// Derive alias: prefer explicit metadata key, fall back to email prefix
const alias = meta.alias
|| (event.user.email ? event.user.email.split('@')[0].toLowerCase() : null)
|| event.user.user_id;
if (alias) {
principal_tags['alias'] = [alias];
transitive_tag_keys.push('alias');
}
for (const key of TAG_KEYS) {
if (key !== 'alias' && meta[key]) {
principal_tags[key] = [meta[key]];
transitive_tag_keys.push(key);
}
}
api.accessToken.setCustomClaim('https://aws.amazon.com/tags', {
principal_tags,
transitive_tag_keys,
});
};- Click Deploy
- Go to Actions -> Flows -> Login
- Drag
Inject AWS session tagsinto the flow between Start and Complete - Click Apply
To add a new tag tomorrow (e.g.department): add'department'toTAG_KEYS,
set"department": "engineering"in the user'suser_metadata, and redeploy
the Action. No changes to the login script or IAM role needed.
Auth0 requires custom claims to be namespaced with a URI. Thehttps://aws.amazon.com/tagsnamespace is the exact URI AWS expects for
session tags passed viaAssumeRoleWithWebIdentity. Using any other namespace
(or no namespace) will cause AWS to ignore the tags.
Step 5: Register Auth0 as an IAM OIDC Provider
Unlike Cognito + Identity Pool (which registers the provider automatically), with
Auth0 you register it manually. This is a one-time account-level step.
Auth0 you register it manually. This is a one-time account-level step.
- Open the AWS Console -> IAM -> left nav -> Identity providers
- Click Add provider
- Configure:
- Provider type: OpenID Connect
- Provider URL:
https://your-tenant.auth0.com/(include the trailing slash — Auth0 JWTs use it as the issuer) - Audience: your Auth0 Client ID (e.g.
JvpNuUkyHb1SlPjUfINSqiun0f5PZPo6) — NOT the API identifier
- Click Get thumbprint — AWS fetches the TLS certificate fingerprint automatically
- Click Add provider
- Note down the Provider ARN (format:
arn:aws:iam::<ACCOUNT_ID>:oidc-provider/your-tenant.auth0.com/)
Why the Client ID and not the API identifier? The script uses theid_token
forAssumeRoleWithWebIdentity. Theid_tokenhasaud = CLIENT_ID(a single
value). Theaccess_tokenhas multipleaudvalues including/userinfo,
which AWS rejects. AWS validates that the token'saudmatches a registered
client ID in the OIDC provider.
Step 6: Create the IAM Role
6a. Create the role
- IAM -> Roles -> Create role
- Trusted entity type: Web identity
- Identity provider: select the Auth0 provider you just created
- Audience: your Client ID (same value used when registering the OIDC provider)
- Click Next
- Attach AmazonBedrockFullAccess (scope down in production)
- Click Next
- Role name:
BedrockAuth0Role - Click Create role
6b. Edit the trust policy
The condition key must use the provider URL (with trailing slash) and the audience
must be the Client ID — not the API identifier. The
(single value), which is what AWS validates:
must be the Client ID — not the API identifier. The
id_token has aud = CLIENT_ID(single value), which is what AWS validates:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<ACCOUNT_ID>:oidc-provider/your-tenant.auth0.com/"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"your-tenant.auth0.com/:aud": "<CLIENT_ID>"
}
}
}
]
}Replace
<ACCOUNT_ID>, your-tenant.auth0.com, and <CLIENT_ID> with your values.The condition key isyour-tenant.auth0.com/:aud— the trailing slash before:audmust match the OIDC provider URL exactly.Do not use the API identifier (https://bedrock.aws) here. Theaccess_token
has multipleaudvalues including/userinfowhich AWS rejects. Theid_tokenhas onlyaud = CLIENT_ID— a single value AWS accepts.
6c. No inline policy needed
Unlike the Cognito guide, there is no
The role is ready after Steps 6a and 6b.
AllowSelfAssume inline policy required.The role is ready after Steps 6a and 6b.
Step 7: Activate the cost allocation tag
- Open the AWS Console -> Billing and Cost Management -> left nav -> Cost allocation tags
- Click the AWS-generated cost allocation tags tab
- Search for
aws:PrincipalTag/alias- If it does not appear yet, come back after Step 10 (first tagged call populates it)
- Select it -> click Activate
Note: It takes up to 24 hours for a newly activated tag to appear in Cost Explorer. CloudTrail shows the tags immediately.
Step 8: Developer login script
Save the following as
~/.aws/bedrock-login-auth0.py on the developer's machine and fill in the CONFIG section — that is the only change needed.To delete cached credentials and re-login:rm ~/.aws/.oidc_token_cache_auth0.json ~/.aws/.bedrock_creds_cache_auth0.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
"""
bedrock-login-auth0.py - Auth0 OIDC -> AWS credentials with session tags from JWT.
Called automatically by AWS credential_process.
Save as: ~/.aws/bedrock-login-auth0.py
Add to ~/.aws/config:
[profile bedrock-auth0]
credential_process = /bin/sh -c 'python3 $HOME/.aws/bedrock-login-auth0.py'
region = us-east-1
Session tags (alias, team, etc.) are injected directly into the JWT by the
Auth0 Post-Login Action using the https://aws.amazon.com/tags namespace.
AssumeRoleWithWebIdentity reads them from the id_token automatically.
Why id_token and not access_token?
Auth0 access tokens have multiple aud values (API identifier + /userinfo).
AWS rejects tokens with multiple audiences. The id_token has aud = CLIENT_ID only.
"""
import json, base64, re, sys, os, subprocess, hashlib
from urllib.parse import urlencode, urlparse, parse_qs
from http.server import HTTPServer, BaseHTTPRequestHandler
# -- Auto-install dependencies -------------------------------------------------
DEPS_DIR = os.path.expanduser("~/.aws/.bedrock_deps")
def ensure_deps():
sys.path.insert(0, DEPS_DIR)
try:
import boto3 # noqa: F401
import requests # noqa: F401
except ImportError:
print("First run: installing dependencies...", file=sys.stderr)
subprocess.check_call(
[sys.executable, "-m", "pip", "install", "--target", DEPS_DIR, "boto3", "requests"],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
)
ensure_deps()
import boto3 # noqa: E402
import requests as req # noqa: E402
# ============ CONFIGURATION — fill in your values ============
AUTH0_DOMAIN = "your-tenant.auth0.com" # e.g. dev-abc123.us.auth0.com
CLIENT_ID = "<CLIENT_ID>" # Auth0 Application Client ID
AUDIENCE = "https://bedrock.aws" # Auth0 API Identifier (Step 3)
ROLE_ARN = "arn:aws:iam::<ACCOUNT_ID>:role/BedrockAuth0Role"
REGION = "us-east-1"
# =============================================================
REDIRECT_URI = "http://localhost:8400/callback"
SCOPES = ["openid", "email", "profile"]
TOKEN_CACHE = os.path.expanduser("~/.aws/.oidc_token_cache_auth0.json")
AWS_CREDS_CACHE = os.path.expanduser("~/.aws/.bedrock_creds_cache_auth0.json")
# -- Helpers -------------------------------------------------------------------
def decode_jwt(token):
payload = token.split(".")[1]
payload += "=" * (4 - len(payload) % 4)
return json.loads(base64.b64decode(payload))
def extract_session_name(claims):
"""Derive RoleSessionName from alias tag in JWT, fall back to email prefix."""
tags = claims.get("https://aws.amazon.com/tags", {})
principal_tags = tags.get("principal_tags", {})
alias = principal_tags.get("alias", [None])[0]
if not alias:
email = claims.get("email", "")
alias = email.split("@")[0].lower() if "@" in email else claims.get("sub", "unknown").split("|")[-1][:20]
return f"alias-{re.sub(r'[^a-zA-Z0-9=,.@_-]', '-', alias)[:58]}"
# -- Check cached AWS credentials first ----------------------------------------
def try_cached_creds():
if not os.path.exists(AWS_CREDS_CACHE):
return
try:
with open(AWS_CREDS_CACHE) as f:
cached = json.load(f)
from datetime import datetime, timezone
exp = datetime.strptime(cached["Expiration"], "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=timezone.utc)
if (exp - datetime.now(timezone.utc)).total_seconds() > 300:
print(json.dumps(cached))
sys.exit(0)
except (json.JSONDecodeError, KeyError, ValueError):
pass
try_cached_creds()
# -- Token cache ---------------------------------------------------------------
def load_token_cache():
if not os.path.exists(TOKEN_CACHE):
return None
try:
with open(TOKEN_CACHE) as f:
return json.load(f)
except (json.JSONDecodeError, ValueError):
return None
def save_token_cache(data):
os.makedirs(os.path.dirname(TOKEN_CACHE), exist_ok=True)
with open(TOKEN_CACHE, "w") as f:
json.dump(data, f)
def try_refresh_token(cached):
refresh_token = cached.get("refresh_token")
if not refresh_token:
return None
resp = req.post(f"https://{AUTH0_DOMAIN}/oauth/token", json={
"grant_type": "refresh_token",
"client_id": CLIENT_ID,
"refresh_token": refresh_token,
}, timeout=10)
if resp.status_code != 200:
return None
token_data = resp.json()
if "access_token" not in token_data:
return None
token_data.setdefault("refresh_token", refresh_token)
save_token_cache(token_data)
return token_data
def do_authorization_code_flow():
code_verifier = base64.urlsafe_b64encode(os.urandom(32)).rstrip(b"=").decode()
code_challenge = base64.urlsafe_b64encode(
hashlib.sha256(code_verifier.encode()).digest()
).rstrip(b"=").decode()
state = base64.urlsafe_b64encode(os.urandom(16)).rstrip(b"=").decode()
auth_url = f"https://{AUTH0_DOMAIN}/authorize?" + urlencode({
"client_id": CLIENT_ID,
"response_type": "code",
"redirect_uri": REDIRECT_URI,
"scope": " ".join(SCOPES),
"audience": AUDIENCE,
"state": state,
"code_challenge": code_challenge,
"code_challenge_method": "S256",
})
auth_code = [None]
auth_error = [None]
class CallbackHandler(BaseHTTPRequestHandler):
def do_GET(self):
qs = parse_qs(urlparse(self.path).query)
if qs.get("state", [None])[0] != state:
auth_error[0] = "State mismatch"
elif "error" in qs:
auth_error[0] = qs.get("error_description", qs["error"])[0]
elif "code" in qs:
auth_code[0] = qs["code"][0]
self.send_response(200)
self.send_header("Content-Type", "text/html")
self.end_headers()
msg = b"<h2>Login successful. You can close this window.</h2>" \
if auth_code[0] else f"<h2>Login failed: {auth_error[0]}</h2>".encode()
self.wfile.write(msg)
def log_message(self, *args):
pass
server = HTTPServer(("localhost", 8400), CallbackHandler)
import webbrowser
webbrowser.open(auth_url)
server.timeout = 120
while auth_code[0] is None and auth_error[0] is None:
server.handle_request()
server.server_close()
if auth_error[0]:
print(f"Login error: {auth_error[0]}", file=sys.stderr)
sys.exit(1)
if not auth_code[0]:
print("Login timed out", file=sys.stderr)
sys.exit(1)
resp = req.post(f"https://{AUTH0_DOMAIN}/oauth/token", json={
"grant_type": "authorization_code",
"client_id": CLIENT_ID,
"code": auth_code[0],
"redirect_uri": REDIRECT_URI,
"code_verifier": code_verifier,
}, timeout=10)
if resp.status_code != 200:
print(f"Token exchange failed: {resp.text}", file=sys.stderr)
sys.exit(1)
token_data = resp.json()
if "access_token" not in token_data:
print(f"No access_token in response: {resp.text}", file=sys.stderr)
sys.exit(1)
save_token_cache(token_data)
return token_data
# -- Main flow -----------------------------------------------------------------
token_data = None
cached = load_token_cache()
if cached:
token_data = try_refresh_token(cached)
if not token_data:
token_data = do_authorization_code_flow()
# Use id_token for AssumeRoleWithWebIdentity.
# The access_token has multiple aud values (API + /userinfo) which AWS rejects.
# The id_token has aud = CLIENT_ID only — a single value AWS accepts.
# Session tags in the access_token's https://aws.amazon.com/tags claim are still
# read by AWS from the id_token because the Action sets them on the access_token
# and AWS reads principal_tags from whichever token is presented.
web_identity_token = token_data.get("id_token") or token_data["access_token"]
claims = decode_jwt(web_identity_token)
session_name = extract_session_name(claims)
# -- AssumeRoleWithWebIdentity — tags come from the JWT automatically ----------
sts = boto3.client("sts", region_name=REGION)
response = sts.assume_role_with_web_identity(
RoleArn=ROLE_ARN,
RoleSessionName=session_name,
WebIdentityToken=web_identity_token,
DurationSeconds=3600,
)
creds = response["Credentials"]
# -- Output and cache ----------------------------------------------------------
output = {
"Version": 1,
"AccessKeyId": creds["AccessKeyId"],
"SecretAccessKey": creds["SecretAccessKey"],
"SessionToken": creds["SessionToken"],
"Expiration": creds["Expiration"].strftime("%Y-%m-%dT%H:%M:%SZ"),
}
with open(AWS_CREDS_CACHE, "w") as f:
json.dump(output, f)
print(json.dumps(output))Step 9: Configure the AWS profile and test
Add to
~/.aws/config:1
2
3
[profile bedrock-auth0]
credential_process = /bin/sh -c 'python3 $HOME/.aws/bedrock-login-auth0.py'
region = us-east-1Test authentication:
1
aws sts get-caller-identity --profile bedrock-auth0Expected output:
1
2
3
4
5
{
"UserId": "AROAXXXXXXXXXXXX:alias-jack",
"Account": "123456789012",
"Arn": "arn:aws:sts::123456789012:assumed-role/BedrockAuth0Role/alias-jack"
}Step 10: Make a Bedrock call
1
2
3
4
5
6
aws bedrock-runtime invoke-model \
--model-id us.anthropic.claude-sonnet-4-6 \
--body '{"anthropic_version":"bedrock-2023-05-31","max_tokens":50,"messages":[{"role":"user","content":"Say hello"}]}' \
--cli-binary-format raw-in-base64-out \
--profile bedrock-auth0 \
/tmp/out.json && cat /tmp/out.jsonStep 11: Verify in CloudTrail (immediate)
1
2
3
4
5
6
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=Username,AttributeValue=alias-jack \
--region us-east-1 \
--max-results 10 \
--query 'Events[*].{Event:EventName,Time:EventTime,User:Username}' \
--output tableExpected output:
1
2
3
4
5
6
+--------------------+-----------------------------+--------------+
| Event | Time | User |
+--------------------+-----------------------------+--------------+
| InvokeModel | 2026-05-31T10:22:14+00:00 | alias-jack |
| GetCallerIdentity | 2026-05-31T10:20:03+00:00 | alias-jack |
+--------------------+-----------------------------+--------------+To verify the tag is attached, look at the
AssumeRole event:1
2
3
4
5
6
7
8
9
10
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=AssumeRole \
--max-results 1 --region us-east-1 \
--query 'Events[0].CloudTrailEvent' --output text \
| python3 -c "
import sys, json
e = json.load(sys.stdin)
print('Session:', e['requestParameters'].get('roleSessionName'))
print('Tags:', json.dumps(e['requestParameters'].get('tags', []), indent=2))
"Step 12: Verify in Cost Explorer (after 24h)
- Billing and Cost Management -> Cost Explorer -> Launch Cost Explorer
- Filter by Service: Amazon Bedrock
- Group by: Tag -> select
alias
Step 13: Set per-developer budget alerts (optional)
- Billing and Cost Management -> Budgets -> Create budget
- Select Customize (advanced) -> Cost budget -> Next
- Fill in:
- Budget name:
jack-daily-bedrock - Period: Daily
- Budgeted amount: e.g.
$5
- Budget name:
- Under Filters -> Add filter:
- Filter dimension: Tag, key:
alias, value:jack - Add second filter: Service =
Amazon Bedrock
- Filter dimension: Tag, key:
- Add alert threshold: Actual, 80%, email:
jack@yourcompany.com - Create budget
Summary
| What | Where | Latency |
|---|---|---|
| API call with user identity | CloudTrail Event History | 5-15 minutes |
| Session tags on AssumeRole | CloudTrail Event History | 5-15 minutes |
| Per-user cost breakdown | Cost Explorer | 24 hours after tag activation |
How this differs from the Cognito guide
| Cognito (Part 1) | Auth0 (Part 3) | |
|---|---|---|
| Identity Pool | Required | Not needed |
| IAM OIDC provider | Auto-created by Identity Pool wizard | Manual registration (Step 5) |
| Session tags in JWT | Not supported natively | https://aws.amazon.com/tags nested claim |
| Second AssumeRole call | Required (to attach tags) | Not needed (tags come from JWT) |
| Trust policy self-assume | Required | Not required |
| Token used | id_token | id_token (access_token has multiple aud values AWS rejects) |
| Role path | /service-role/BedrockCognitoRole | /BedrockAuth0Role |
| Trust policy principal | cognito-identity.amazonaws.com | Auth0 OIDC provider ARN |
| Adding a new tag dimension | Change login script | Add key to Action's TAG_KEYS + user metadata |
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
InvalidIdentityToken: No OpenIDConnect provider found | OIDC provider not registered or URL missing trailing slash | Step 5: provider URL must be https://your-tenant.auth0.com/ — Auth0 JWTs include a trailing slash in the iss claim |
InvalidIdentityToken: Incorrect token audience | Using access_token (multiple aud values) or wrong condition key | Script uses id_token which has single aud = CLIENT_ID; trust policy condition must be your-tenant.auth0.com/:aud = <CLIENT_ID> |
AccessDenied on AssumeRoleWithWebIdentity | Trust policy condition mismatch | Check your-tenant.auth0.com:aud in the trust policy matches your API Identifier |
| Session tags missing in CloudTrail | https://aws.amazon.com/tags claim not in JWT | Step 4: verify the Action is deployed AND added to the Login flow; decode the access token at jwt.io to confirm the claim is present |
alias is a random string in CloudTrail | alias key missing from user_metadata | Step 2: set "alias": "jack" in the user's metadata; the Action falls back to email prefix if missing |
| Tags not appearing in Cost Explorer | Cost allocation tag not activated | Step 7: activate aws:PrincipalTag/alias in Billing -> Cost allocation tags |
401 access_denied on token exchange | App type is not SPA — token endpoint auth method is not None | Auth0 app Settings -> Application Type must be Single Page Application; Token Endpoint Authentication Method must be None |
| Token exchange returns opaque token instead of JWT | audience param missing from auth URL | The script must pass "audience": AUDIENCE in the authorization URL — without it Auth0 returns an opaque token |
References
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article