Identity-Aware Embedded Chat with Amazon Quick: Per-Student Data Isolation over Relational Data
A guide to embedding an Quick chat agent into an web portal — where the logged-in user's identity automatically governs every row the agent can return from structured dataset connected to RDMS systems. This is a lessons-learned and best-practices write-up grounded in a real production build: the patterns that worked.
A practical guide to Identity-Aware Embedded Chat with Amazon Quick: Per user Data Isolation over Relational Data
Co-Authors: Stephen Elsaker
Background
A university maintains all student and alumni-mentor records in a relational database (RDBMS) — structured tables containing profiles, majors, career interests, advising notes, and contact details. They want to embed Amazon Quick Suite's chat agent directly into their student portal so tens of thousands of daily users can ask natural-language questions ("find me mentors in finance who were first-gen students") and receive AI-powered mentor matches.
The constraint is strict: student data is sensitive and lives alongside mentor data in the same relational schema. The embedded agent must:
- Query structured RDBMS tables at runtime — not pre-indexed flat files
- Accept the logged-in student's identity from the portal session
- Match students to alumni mentors using their own profile attributes (major, interests, career goals, preferences)
- Enforce row-level security so each student sees only their own record plus eligible mentor records — never another student's name, GPA, contact details, or advising notes
- Operate serverlessly inside the existing portal — no separate login, no data replication outside the RDBMS
Core challenge: Propagate the authenticated student identity into the embedded Quick agent, enforce student-level data isolation against structured relational data, and return real-time query results from an operational database — all governed by the university's existing identity and access systems.
Architecture: Three Planes
Think of the system as three cooperating planes — an identity plane that keeps user/group
records in sync, a request plane that mints a per-user embed URL, and a data plane that
answers questions while filtering rows by identity.
records in sync, a request plane that mints a per-user embed URL, and a data plane that
answers questions while filtering rows by identity.

Key insight: the portal user is already a real, registered Quick user (provisioned via
SCIM), so the Lambda embeds that identity directly with
Every authorization decision — which agents are visible, which datasets are reachable, which
rows are returned — is evaluated by Quick against the real user and their real group membership.
The embedding layer passes nothing that influences row filtering; authorization lives entirely
in the RLS rules datasets and IdP-managed group membership.
SCIM), so the Lambda embeds that identity directly with
GenerateEmbedUrlForRegisteredUser.Every authorization decision — which agents are visible, which datasets are reachable, which
rows are returned — is evaluated by Quick against the real user and their real group membership.
The embedding layer passes nothing that influences row filtering; authorization lives entirely
in the RLS rules datasets and IdP-managed group membership.
Identity Propagation: IdP → SCIM → IDC → Quick
The foundation is that users are not managed in the enterprise IdP and synchronized automatically (
IdP → SCIM → IAM Identity Center → Amazon Quick, where users surface with IdentityType = IAM_IDENTITY_CENTER).- The IdP is the single source of truth. SCIM auto-provisions/deprovisions users and
groups into IDC; joiners/movers/leavers are handled upstream with no manual lifecycle in Quick. - Group membership drives everything. Groups (e.g.
*-Admins,*-Authors,*-Readers) map
to subscription roles and to agent-level permissions. - When the portal needs an embed URL, it passes the already-authenticated user's identity to the
Lambda as query params (roleName,sessionName), and the Lambda reconstructs the real
Quick user ARN:arn:aws:quicksight:us-east-1:<ACCOUNT_ID>:user/default/<roleName>/<sessionName>.
Because the URL is issued for the registered user, identity-aware access is enforced by the
platform — no translation layer to build or keep in sync. The same real
resolves from IDC is exactly what row-level security keys off, which is the heart of the pattern.
platform — no translation layer to build or keep in sync. The same real
UserName that Quickresolves from IDC is exactly what row-level security keys off, which is the heart of the pattern.
Solving the Student-Privacy Problem with User-Based RLS
The Two Guarantees
The agent must satisfy two requirements simultaneously, both enforced by Amazon Quick RLS keyed
to the real authenticated user — not by prompt rules or anything injected at embed time:
to the real authenticated user — not by prompt rules or anything injected at embed time:
- Student isolation — a student may read their own record and nothing about any other student.
- Mentor discovery — every enrolled student may browse the shared alumni mentor pool.
The
mint the embed URL, resolved from IDC). At query time Quick resolves the caller's
group membership and evaluates the RLS rules against them.
UserName that RLS matches on is the real registered Quick user (the same identity used tomint the embed URL, resolved from IDC). At query time Quick resolves the caller's
UserName andgroup membership and evaluates the RLS rules against them.
Pattern: The Permissions / Rules Dataset
Student isolation comes from a tiny rules dataset with exactly two columns — the
authenticated user and the primary key of the students table. Each row grants one user access to
exactly one
authenticated user and the primary key of the students table. Each row grants one user access to
exactly one
student_id — their own.STUDENT_RLS_RULES| UserName | student_id |
|---|---|
| maria.garcia@university.edu | STU-100245 |
| david.chen@university.edu | STU-100312 |
| aisha.khan@university.edu | STU-100489 |
| ... (one row per enrolled student) | ... |
UserNameholds the exact registered Quick user name Quick resolves from IDC.student_idis the primary key of the students table — the same column that identifies a
row in the students dataset.- Each student maps to a single primary-key value, so the rule grants exactly one row. (The
format supports comma-separated values for multi-row cases, but here it's strictly 1:1.)
Build this rules dataset from a small table or flat view that joins the enrollment/identity table
to the students primary key, so it stays in sync as students are added or removed.
to the students primary key, so it stays in sync as students are added or removed.
Pattern: Attaching RLS to the Students Dataset (RowLevelPermissionDataSet)
The students dataset uses
rules dataset against the students dataset via the
column is matched to the caller's identity, and its
same-named column in the students dataset.
student_id as its primary key. RLS is enforced by registering therules dataset against the students dataset via the
RowLevelPermissionDataSet property onCreateDataSet / UpdateDataSet. Matching is by column name: the rules dataset's UserNamecolumn is matched to the caller's identity, and its
student_id column is matched to thesame-named column in the students dataset.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// UpdateDataSet (or CreateDataSet) on the STUDENTS dataset
{
"AwsAccountId": "<ACCOUNT_ID>",
"DataSetId": "students-dataset",
"Name": "Students",
"ImportMode": "SPICE",
"PhysicalTableMap": { /* students table; primary key = student_id */ },
"LogicalTableMap": { /* ... */ },
// ---- This block enforces per-student isolation ----
"RowLevelPermissionDataSet": {
"Arn": "arn:aws:quicksight:us-east-1:<ACCOUNT_ID>:dataset/student-rls-rules",
"PermissionPolicy": "GRANT_ACCESS",
"FormatVersion": "VERSION_1",
"Namespace": "default"
}
}Arnpoints at theSTUDENT_RLS_RULESrules dataset.PermissionPolicy = GRANT_ACCESSmeans "show only rows explicitly permitted" (deny-by-default;
a user with no matchingUserNamerow sees zero student rows).FormatVersion = VERSION_1enables theUserName/GroupNamecolumn convention.
Net effect: when Maria is logged in, Quick filters the students dataset to
students in my major," those rows are filtered out by RLS — the agent has nothing to return. The
boundary holds regardless of phrasing.
student_id = STU-100245 before the agent sees a row. If she asks for David Chen's GPA or "otherstudents in my major," those rows are filtered out by RLS — the agent has nothing to return. The
boundary holds regardless of phrasing.
Pattern: Group-Based Discoverability for the Mentor Pool
The mentor pool must be broadly readable while student-to-student data stays isolated, so it uses
a group-based rule scoped to a group every enrolled student belongs to:
a group-based rule scoped to a group every enrolled student belongs to:
MENTOR_RLS_RULES| GroupName | (grants the whole pool to the group) |
|---|---|
| Enrolled-Students | * |
Because
gains mentor discoverability with no per-student maintenance. The student's stated preferences
(major, interests, career goals) then drive which permitted mentor rows the agent surfaces and
ranks — preferences shape the match; RLS enforces the privacy boundary underneath.
Enrolled-Students membership flows from the IdP via SCIM → IDC, every enrolled studentgains mentor discoverability with no per-student maintenance. The student's stated preferences
(major, interests, career goals) then drive which permitted mentor rows the agent surfaces and
ranks — preferences shape the match; RLS enforces the privacy boundary underneath.
The two datasets carry deliberately different RLS scoping:
| Dataset | RLS rules dataset | Match column | Result |
|---|---|---|---|
Students (PK student_id) | STUDENT_RLS_RULES (UserName, student_id) | UserName → own student_id | Sees only their own record |
Mentor pool (MENTOR_PROFILE_FLAT) | MENTOR_RLS_RULES (GroupName) | GroupName = Enrolled-Students | Any enrolled student can discover mentors |
The Lambda: Embedding for the Registered User
The embed-URL Lambda does three things: build the real user ARN, ensure the user can use a chat
agent, and mint the URL.
agent, and mint the URL.
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
import os, json, boto3
quicksight = boto3.client('quicksight')
ACCOUNT_ID = os.environ['AWS_ACCOUNT_ID']
ALLOWED_DOMAINS = os.environ.get('ALLOWED_DOMAINS', '').split(',')
AGENT_ID = os.environ['AGENT_ID']
AGENT_ARN = f'arn:aws:quicksight:us-east-1:{ACCOUNT_ID}:agent/{AGENT_ID}'
def get_user_arn(event):
"""Build the REAL registered-user ARN from portal-supplied identity."""
p = event.get('queryStringParameters') or {}
role_name, session_name = p.get('roleName'), p.get('sessionName')
if role_name and session_name:
return f'arn:aws:quicksight:us-east-1:{ACCOUNT_ID}:user/default/{role_name}/{session_name}'
return os.environ.get('QUICKSIGHT_USER_ARN', '')
def ensure_reader_pro(user_arn):
"""READER cannot access Chat Agents — auto-upgrade to READER_PRO."""
user_name = user_arn.split(':user/default/')[-1]
user = quicksight.describe_user(AwsAccountId=ACCOUNT_ID, Namespace='default',
UserName=user_name)['User']
if user['Role'] == 'READER':
quicksight.update_user(AwsAccountId=ACCOUNT_ID, Namespace='default',
UserName=user_name, Email=user['Email'], Role='READER_PRO')
return 'READER_PRO (upgraded)'
return user['Role']
def lambda_handler(event, context):
headers = {'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Allow-Methods': 'GET,OPTIONS',
'Content-Type': 'application/json'}
if event.get('httpMethod') == 'OPTIONS':
return {'statusCode': 200, 'headers': headers, 'body': ''}
try:
user_arn = get_user_arn(event)
role = ensure_reader_pro(user_arn) # auto READER -> READER_PRO
response = quicksight.generate_embed_url_for_registered_user(
AwsAccountId=ACCOUNT_ID,
UserArn=user_arn, # embed FOR the registered user
ExperienceConfiguration={'QuickChat': {}},
AllowedDomains=ALLOWED_DOMAINS)
return {'statusCode': 200, 'headers': headers,
'body': json.dumps({'embedUrl': response['EmbedUrl'],
'agentArn': AGENT_ARN, 'userRole': role})}
except Exception as e:
return {'statusCode': 500, 'headers': headers, 'body': json.dumps({'error': str(e)})}Worth calling out:
- IAM permissions:
quicksight:GenerateEmbedUrlForRegisteredUser,quicksight:DescribeUser,quicksight:UpdateUser. - The auto role upgrade is not optional. A plain
READERcannot access Chat Agents, so the
Lambda promotes toREADER_PRO(Professional) — factor it into cost planning. - The sample uses
Access-Control-Allow-Origin: *. Fine for a demo; in production scope it to
your CloudFront domain and consider a Lambda authorizer in front of the API.
Access Control: Four Layers, All Keyed to Real Identity
Authorization is enforced at four layers, each keyed to the user's real, IdP-governed identity:
| Layer | Mechanism | Principal | Example |
|---|---|---|---|
| Subscription role | Group → Quick role mapping | IDC group (SCIM-synced) | *-Admins → ADMIN_PRO, etc. |
| Agent access | UpdateAgentPermissions granting DescribeAgent | group/default/<GroupName> | Only granted groups can use the agent |
| Row-level security | RowLevelPermissionDataSet rules datasets | UserName (students) / GroupName (mentors) | Own record + shared mentor pool |
| Defense in depth | Lake Formation column-level controls | Quick service role | Sensitive columns excluded before RLS runs |
The four layers compose: a student's group sets their role and agent access, the
restricts them to their own student row, the
Formation keeps the most sensitive columns out of reach entirely — none of it influenced by
anything the embedding layer passes in.
UserName rulerestricts them to their own student row, the
GroupName rule opens the mentor pool, and LakeFormation keeps the most sensitive columns out of reach entirely — none of it influenced by
anything the embedding layer passes in.
Leveraging Structured Data via RDS Connectors
The student and alumni data lives in a relational database (RDBMS) behind the portal. Amazon
Quick connects via the RDS connector pattern (see the
AWS QuickSight tutorial ):
Quick connects via the RDS connector pattern (see the
AWS QuickSight tutorial ):
- RDBMS exposed via Amazon RDS (or reachable over VPC) — works for any RDS-supported engine (PostgreSQL, MySQL, SQL Server, Oracle, etc.)
- RDS connector configured with a VPC connection, over SSL
- Two datasets: a students dataset (private, locked by the
UserName→student_id
rules dataset) and a mentor pool dataset (broadly discoverable via theEnrolled-Students
group rule)
Mentor pool fields:
students dataset is keyed on
person_id, full_name, interest_area (matching), major, program,employer, role_title, class_year, profile_url, and GroupName (RLS match column). Thestudents dataset is keyed on
student_id and locked by STUDENT_RLS_RULES.SPICE vs. Direct Query: we use SPICE for the mentor pool with a scheduled daily refresh
— alumni profiles change infrequently and SPICE gives the sub-second scan speed chat needs.
Reserve Direct Query for genuinely real-time fields (enrollment status, live availability).
— alumni profiles change infrequently and SPICE gives the sub-second scan speed chat needs.
Reserve Direct Query for genuinely real-time fields (enrollment status, live availability).
Query Optimization: Topics vs. Dataset Q&A
During testing we found a significant latency gap:
| Approach | Response Time | Behavior |
|---|---|---|
| Topics (pre-indexed) | ~30 seconds | Pre-indexes fields; single SQL execution |
| Dataset Q&A | ~40+ seconds per SQL | Generates multiple queries and combines results |
Dataset Q&A sometimes issues several SQL queries per question; Topics pre-index fields so the
system generates one optimized query. What we did:
system generates one optimized query. What we did:
- Topics for the primary agent experience — faster mentor matching.
- Smart mode as fallback — routes between Topics and Dataset Q&A by query type.
- Simplified schema — fewer columns = faster SPICE scanning and better query generation.
- Pre-joined
MENTOR_PROFILE_FLATview — avoids query-time joins. - Rich semantic metadata — field descriptions and synonyms improve query-generation accuracy.
Topics with controlled field access also avoid Dataset Q&A occasionally surfacing columns you
didn't want exposed; Lake Formation back-stops that at the data layer, and RLS still ensures the
students dataset only ever exposes the logged-in student's own row.
didn't want exposed; Lake Formation back-stops that at the data layer, and RLS still ensures the
students dataset only ever exposes the logged-in student's own row.
Embedded SDK Configuration
The frontend (CloudFront + S3) uses a shell/iframe persistence pattern: a
(deployed as
inner
single per-session embed URL. The agent is embedded with the Quick Embedding SDK (v2.11.3) and
pinned via
shell.html(deployed as
index.html) never reloads, the chat lives in a persistent container, and only aninner
<iframe id="app"> swaps as the user navigates — keeping the conversation alive on asingle per-session embed URL. The agent is embedded with the Quick Embedding SDK (v2.11.3) and
pinned via
fixedAgentArn:1
2
3
4
5
const embeddingContext = await QuickSightEmbedding.createEmbeddingContext();
const frameOptions = { url: embedUrl, container: '#mentor-match-container',
height: '700px', width: '100%' };
const contentOptions = { fixedAgentArn: agentArn }; // pin to the Mentor Match agent
const embedded = await embeddingContext.embedQuickChat(frameOptions, contentOptions);Reasoning Mode and Web Search via URL Query Params
Some behaviors are controlled by query parameters on the embed iframe URL, not SDK options —
the cleanest way to standardize the experience (e.g. lock everyone to Smart and hide the picker):
the cleanest way to standardize the experience (e.g. lock everyone to Smart and hide the picker):
1
{HOST}/sn/account/{ACCOUNT_NAME}/embed/share/accounts/{ACCOUNT_NUMBER}/chatagents/SYSTEM?showReasoningModeSelection=true&defaultReasoningMode=SMART| Parameter | Values | Effect |
|---|---|---|
defaultReasoningMode | SMART, ADVANCED, FAST | Sets the initial reasoning mode |
showReasoningModeSelection | true / false | Show or hide the mode dropdown |
- Lock users on Smart:
defaultReasoningMode=SMARTandshowReasoningModeSelection=false. - Web search / attribution:
?showWebSearch=true&showBrandAttribution=true&showUsagePolicy=true
Networking and Security: Private by Design
The API that mints embed URLs is not exposed to the public internet:
- PRIVATE API Gateway reachable only through a VPC interface endpoint, with a resource
policy restricting access to that endpoint (aws:sourceVpce). Browsers reach it over VPN /
Direct Connect — never the open internet. - No public endpoint to defend — no WAF needed; the network boundary is the first control.
- CloudFront + S3 serve the static frontend over HTTPS.
- The embed URL is single-use and per-session (5-minute redemption, up to 10-hour session).
Production hardening beyond the sample: scope CORS to the CloudFront domain, and add a Lambda authorizer to validate the caller before an embed URL is generated.
End-to-End Flow
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Student logs into the portal (already a registered Amazon Quick user via IdP→SCIM→IDC)
→ Portal calls the PRIVATE API (via VPC endpoint) with roleName + sessionName
→ Lambda builds the REAL student user ARN
→ Lambda runs DescribeUser; if READER, UpdateUser → READER_PRO
→ Lambda calls generate_embed_url_for_registered_user (UserArn = real student, QuickChat)
→ Returns a single-use embed URL
→ CloudFront/S3 shell renders the SDK (embedQuickChat, fixedAgentArn) in a persistent iframe
→ Student asks: "Find me mentors in finance who were first-gen students"
→ Agent queries the students + mentor datasets (SPICE) via a Topic
→ USER-BASED RLS filters rows by the student's REAL identity:
• STUDENTS: STUDENT_RLS_RULES matches UserName → their OWN student_id ✔
• MENTORS: MENTOR_RLS_RULES matches GroupName = Enrolled-Students ✔
• every OTHER student's record → filtered out by RLS ✘
→ Agent returns: "Here are your top 5 mentors in finance, matched to your preferences..."Lessons Learned
The decisions and tradeoffs that actually shaped the build — grouped by area, each tied to a
concrete consequence. Nothing generic: if a lesson didn't change a decision or save debugging
time, it isn't here.
concrete consequence. Nothing generic: if a lesson didn't change a decision or save debugging
time, it isn't here.
Identity & governance
- Embed the real registered user — never a stand-in. RLS, agent permissions, and audit
trails all resolve to a real principal automatically. Embed a generic identity and RLS has no
realUserNameto filter on — every user collapses into one access scope and the privacy
boundary is gone before you write any policy. - Carry identity in the ARN; keep the embed request minimal. Identity is fully expressed by
the user ARN; the request carries onlyExperienceConfiguration={'QuickChat': {}}andAllowedDomains. Side-channel identity attributes create a second, drift-prone source of truth
that RLS doesn't read — so isolation silently depends on the wrong field. - Make the enterprise IdP the single source of identity truth. Users/groups flow IdP → SCIM
→ IDC → Quick. Creating users directly in the analytics layer forks identity from IdP reality
and produces orphaned accounts that bypass governance. - Map groups to roles; never grant roles per user. Capability follows group membership and
changes with it. Per-user grants defeat SCIM lifecycle management and leave stale elevated
access when someone changes teams. - Set agent access with
UpdateAgentPermissions— and verify withDescribeAgent. Assuming
a group mapping implies agent access (without reading it back) is how "I can log in but the chat
agent says no access" tickets are born. Verification is part of provisioning. - Auto-upgrade
READER→READER_PRO. Chat Agents requireREADER_PRO. Auto-upgrading your entire reader population the moment they touch the app can produce a surprise bill — gate the upgrade on actual chat entitlement, not login, and reconcile counts against entitlement.
Embedding & the embed URL
- Treat the embed URL as single-use and short-lived. ~5-minute redemption, ~10-hour session.
Caching, logging, or sharing it breaks redemption — mint a fresh URL per frame load and lean on
the long session for the working window. - Register the embedding domain in BOTH allowlists, over HTTPS. Set only one of the Lambda
AllowedDomainsand the account-level allowlist and you get an embed that authenticates but
renders a confusing blank frame. Verify the frame renders, not just that a URL is issued. - Pin the agent with
fixedAgentArn; set per-feature controls where they live. Bind a single
non-switchable agent viaembedQuickChat. File upload, web search, and code interpreter are
agent-level configuration, not SDK flags — hunting for SDK options that don't exist is wasted time. - Lock reasoning mode at the embed URL.
SMARTis the default for fresh users, so "stuck on
Advanced" is a saved prior-session preference, not a platform default. SetdefaultReasoningMode=SMART+showReasoningModeSelection=false(and the web-search params) to
standardize. This was our single biggest operational time-saver.
Row-level security (the centerpiece)
- Enforce isolation with a user-based RLS rules dataset keyed on the primary key.
UserName- the students primary key
student_id, attached viaRowLevelPermissionDataSet
(GRANT_ACCESS,VERSION_1), so eachUserNameresolves to exactly one row. Key the rule on a
non-unique column and one user matches multiple rows — isolation silently fails. This is the
highest-severity failure mode in the design, and why the primary key is non-negotiable.
- the students primary key
- Use a
GroupNamerule for the shared mentor pool — keep it separate from the per-row student
rule. Conflating "shared/discoverable" with "private/per-user" under one rule type either
over-restricts mentors or under-restricts student rows. Model two intents explicitly — one RLS
mechanism, two deliberately different scopes. - Validate RLS by impersonating real users, not by reading the rule. A rules dataset that
looks correct but has a stale/mismatchedUserNamemapping passes inspection and still leaks.
Test as at least two distinct students (and one group-only user) and confirm each sees exactly
their own row plus the expected mentor pool.
Data & performance
- Pre-join in the database, never at query time. Query-time joins fan each question into
multiple joined SQL statements, pushing Dataset Q&A past ~40s and making chat feel broken even
when correct. Materialize one flat view (MENTOR_PROFILE_FLAT) so Quick scans a denormalized table. - Use SPICE with a scheduled refresh for slow-changing data, and keep the schema lean. Direct
Query would pay the round-trip and join cost on every question. Load the mentor pool into SPICE
(daily refresh); reserve Direct Query for real-time fields. Prune columns: both SPICE scan time
and query-generation accuracy degrade as column count grows. - Prefer Topics over Dataset Q&A, and invest in semantic metadata. Topics pre-index fields and
emit one optimized SQL (~30s); Dataset Q&A generates multiple queries (~40s+). The gap is
structural. Make a Topic the primary experience with smart-mode fallback, and author field
descriptions/synonyms so phrasing maps to the right columns on the first query.
Networking & frontend
- Keep the embed-minting plane private by design. Expose the embed API only through a PRIVATE API Gateway via a VPC interface endpoint (
aws:sourceVpce); browsers reach it over VPN / Direct Connect. With no public endpoint, there's no public attack surface to defend with WAF/rate limits. - Serve a persistent shell/iframe frontend from CloudFront + S3. Re-embedding on every
navigation tears down the chat and re-redeems URLs. Ashell.htmlthat never reloads, with only
an inner<iframe>swapping, keeps the conversation alive on one per-session embed URL.
Best Practices Checklist
Identity & embedding
- [ ] Embed the real registered user with
GenerateEmbedUrlForRegisteredUser(realUserArnfromroleName/sessionName). - [ ] Keep the request minimal:
ExperienceConfiguration={'QuickChat': {}}+AllowedDomainsonly — identity comes solely from the ARN. - [ ] Treat the embed URL as single-use (5-min redemption, 10h session); mint fresh per frame load, never cache/log it.
- [ ] Provision users/groups via IdP → SCIM → IDC; map groups to
ADMIN_PRO/AUTHOR_PRO/READER_PRO(no per-user grants). - [ ] Grant agent access to groups with
UpdateAgentPermissions; verify withDescribeAgent. - [ ] Auto-upgrade
READER→READER_PROonly for chat-entitled users; budget ~$40/user/mo and reconcile to entitlement.
Row-level security (the centerpiece)
- [ ] Build the two-column rules dataset (
UserName→ students primary keystudent_id); assert oneUserName→ exactly one row. - [ ] Attach via
RowLevelPermissionDataSet(PermissionPolicy = GRANT_ACCESS,FormatVersion = VERSION_1). - [ ] Validate by impersonation: test as ≥2 real students (and one group-only user); confirm zero cross-student leakage regardless of phrasing.
- [ ] Scope mentor discoverability via a
GroupName = Enrolled-Studentsrule; let preferences filter which rows surface. - [ ] Confirm RLS resolves against the real IdP identity from IDC — nothing from the embedding layer influences filtering.
Data & performance
- [ ] Connect to the RDBMS via the RDS connector over SSL inside a VPC.
- [ ] Import into SPICE with a scheduled daily refresh; keep the schema minimal.
- [ ] Pre-join into a flat view (e.g.
MENTOR_PROFILE_FLAT). - [ ] Prefer Topics over Dataset Q&A; enable smart-mode fallback; add rich semantic metadata.
- [ ] Add Lake Formation column-level controls as defense in depth.
Networking, frontend & SDK
- [ ] Expose the embed API only through a PRIVATE API Gateway behind a VPC endpoint (
aws:sourceVpce). - [ ] Serve the frontend from CloudFront + S3 over HTTPS; use the shell/iframe persistence pattern.
- [ ] Register the embedding domain in both the Lambda
AllowedDomainsand the account-level allowlist. - [ ] Embed with
createEmbeddingContext()+embedQuickChat(frameOptions, {fixedAgentArn}); pin the agent. - [ ] Configure per-feature controls at the agent level, not via SDK flags.
- [ ] Standardize reasoning mode via URL params:
defaultReasoningMode=SMART+showReasoningModeSelection=false; set web-search params for consistency. RememberSMARTis the fresh-user default — "stuck on Advanced" is a saved preference the URL params override.
Reference Architecture
| Layer | Component | Purpose |
|---|---|---|
| Identity governance | IdP → SCIM → IAM Identity Center → Quick | Auto-provisioned users/groups; single source of truth |
| Portal | Existing student portal (web app) | User authentication + UI |
| Frontend | CloudFront + S3 (shell/iframe) | Persistent chat across navigations |
| Embed minting | PRIVATE API Gateway (VPC endpoint) + Lambda | Real user ARN, READER→READER_PRO, GenerateEmbedUrlForRegisteredUser |
| Access control | Group→role mapping, agent permissions, user-based RLS rules datasets | Keyed to the real authenticated identity |
| Defense in depth | Lake Formation column-level controls | Limits which columns the service role can read |
| AI agent | Quick Chat Agent (QuickChat) | Mentor-matching intelligence |
| Data | RDBMS (via Amazon RDS) → SPICE datasets | Students dataset (PK student_id) + pre-joined MENTOR_PROFILE_FLAT |
| Optimization | Topics (pre-indexed) | Sub-30s Q&A response |
| UX | Quick Embedding SDK (embedQuickChat, fixedAgentArn) | Seamless in-portal chat |
This pattern applies to any organization building an identity-aware embedded UI on Amazon
Quick — where an agent needs per-user data isolation on RDBMS Datasets
Quick — where an agent needs per-user data isolation on RDBMS Datasets
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article