Option C — Combined (group + email) access
Two-layer security: Lake Formation tags control which tables a group can access (table-level), and data cell filters further restrict rows by user email (row-level).
- How it works: Tag-Based Access Control (TBAC) for table isolation, plus email-based cell filters for row isolation within the allowed tables.
- Best for: multi-department setups where different groups access different tables, and within those tables users see only their own data.
- Requires: everything Option B requires, namely the
sts:TagSessionpermission on every IAM role and theemailscope on the Cognito app client.
Complete Set up Amazon Cognito first, including the email scope and read attribute on the app client.
Step 1: Prepare Lake Formation
- In the Lake Formation Console go to Data lake locations → Register location. Register the S3 paths where your data resides (e.g.
s3://my-athena-data-bucket/) and select the IAM role Lake Formation should use to access the data (or use the service-linked role). - Under Administrative roles and tasks → Data lake administrators, add your admin IAM user or role.
Step 2: Tag tables by group (table-level layer)
- Go to LF-Tags → Add LF-Tag and create tags that model your access dimensions, for example key
Departmentwith valuesfinance,hr,engineering. - Assign tags to your databases and tables under Data lake permissions → LF-Tags → select a database or table → Assign LF-Tag. For example, assign
Department=financetofinance_tableandDepartment=hrtohr_table.
AWS Lake Formation allows only one LF-tag value per key per resource, so you may need separate tables per department. Assign the department tag to each table, then apply the email-based data cell filter within each table.
Step 3: Create email-based data cell filters (row-level layer)
For each tagged table, go to Data filters → Create new filter and configure:
Filter name: email_row_filter
Target database: my_analytics_db
Target table: finance_table
Row filter expression: owner_email = '${session:UserEmail}'
The ${session:UserEmail} variable is resolved to the authenticated user's email address at query time.
Step 4: Grant permissions
For each Cognito group's IAM role:
- Go to Data lake permissions → Grant and select IAM users and roles → the role.
- Under LF-Tags or catalog resources, select the database and the table that carries the group's tag.
- Under Data filters, select the email cell filter for that table.
- Grant SELECT.
The tag decides which tables the role can reach; the filter decides which rows inside them.
Step 5: Add sts:TagSession to every IAM role
Add this statement to each role's permissions policy so the UserEmail session tag reaches Lake Formation:
{
"Sid": "AllowTagSession",
"Effect": "Allow",
"Action": "sts:TagSession",
"Resource": "*"
}
Without sts:TagSession, the ${session:UserEmail} variable will not resolve and queries will silently return zero rows. Also confirm the Cognito App Client has email in both AllowedOAuthScopes and ReadAttributes.
What each user sees
Consider a setup with two departments and email-based row filtering:
finance_analystsgroup →Athena-FinanceRole→ LF-Tag grants access tofinance_tablehr_viewersgroup →Athena-HRRole→ LF-Tag grants access tohr_table- Data cell filter
owner_email = '${session:UserEmail}'on both tables - alice@example.com is in
finance_analysts, assigned to rows 1, 2, 3 infinance_table - bob@example.com is in
hr_viewers - dave@example.com is in
finance_analysts, assigned to rows 4, 5, 6 infinance_table
| User | Query | TBAC check | Email filter | Result |
|---|---|---|---|---|
| alice | SELECT * FROM finance_table | ✅ PASS (finance group has access) | owner_email = 'alice@example.com' | Sees rows 1, 2, 3 |
| alice | SELECT * FROM hr_table | ❌ DENIED (finance group has no access) | — | Table is invisible |
| bob | SELECT * FROM finance_table | ❌ DENIED (hr group has no access) | — | Table is invisible |
| bob | SELECT * FROM hr_table | ✅ PASS (hr group has access) | owner_email = 'bob@example.com' | Sees only bob's rows |
| dave | SELECT * FROM finance_table | ✅ PASS (finance group has access) | owner_email = 'dave@example.com' | Sees rows 4, 5, 6 (different from alice) |
TBAC controls which tables a user can see, and email-based cell filters control which rows within those tables.