AWS Amplify makes it quick to ship a frontend with a backend attached, and the same speed lets teams publish open data APIs, over-privileged roles and secrets in build settings without noticing. An Amplify app is several things at once: a hosting service with branch previews, a Git-driven build pipeline, and (in Gen 2) a TypeScript-defined backend that deploys Amazon Cognito, AWS AppSync, AWS Lambda and Amazon S3 into your account. Each of those layers has its own identities and its own way to fail open. This guide covers hosting access control, Gen 2 data authorisation rules, what the generated client configuration does and does not expose, build-time secrets, the service and SSR compute roles, logging and a hardening baseline. It is written for frontend and full-stack developers and the cloud security engineers who review their work. User pool settings are covered in depth in the Amazon Cognito Security Guide; this page stays on Amplify.
This is an independent guide from NHI Mgmt Group, based on AWS's public documentation as of November 2026. It is not sponsored or reviewed by AWS. Product features change, so confirm settings against AWS's current documentation.
Key takeaways
- The authorisation rules in your schema are the security boundary. Amplify Gen 2 data access is deny by default, but one
allow.publicApiKey()or a global rule copied from a tutorial opens a model to anyone holding the key that ships in the client. - Owners can hand records away unless you stop them. Amplify's own documentation warns that, by default, owners can reassign the owner of an existing record; protect the owner field with a field-level rule.
- Branch passwords are a preview gate, not authentication. Hosting access control gives one shared username and password per branch or per app; use Cognito in the application for anything that holds real data.
- Two roles carry most of the risk. The backend deployment service role has account administrative permissions, and the SSR compute role's credentials are live inside your server-side code; scope both and condition the trust policies.
- Environment variables are not secrets. Use Amplify secrets (Parameter Store backed) for credentials, and assume anything written to a client-side
.envoramplify_outputs.jsonis public.
How identity and access work in Amplify
Three separate audiences touch an Amplify app, and each is controlled in a different place. Mixing them up is the root of most findings.
| Audience | Controlled by | Where it is configured | Main risk |
|---|---|---|---|
| End users of the app | Cognito user pool and identity pool (defineAuth), AppSync authorisation modes and rules (defineData) | amplify/auth/resource.ts, amplify/data/resource.ts | Public or over-broad model and field rules |
| Visitors to a hosted branch | Hosting access control (username and password), AWS WAF web ACL | Amplify console: Hosting > Access control; Firewall | Preview branches with production data reachable by anyone with the URL |
| Amplify itself and your server code | Backend deployment service role, SSR compute role, IAM authorisation on the data API | App settings > IAM roles | Administrative role misuse, credential exposure from SSR code |
| Developers and pipelines | IAM policies for the Amplify, AppSync, Cognito and CloudFormation APIs | IAM, IAM Identity Center | Anyone who can push to a connected branch can deploy backend changes |
Gen 2 data authorisation rules
Amplify documents the following strategies, each tied to an AppSync authorisation mode. Authorisation is deny by default, the most specific rule applies (field, then model, then global), and multiple rules on the same model are combined with a logical OR, so adding a rule can only widen access.
| Rule | Auth mode | Who gets access | Use it for |
|---|---|---|---|
allow.publicApiKey() | apiKey | Anyone with the API key, which is in the client configuration | Prototypes; genuinely public read-only content with a short key expiry |
allow.guest() | identityPool | Unauthenticated users via the identity pool's unauthenticated role | Public access in production, scoped by IAM on that role |
allow.authenticated() | userPool, oidc, identityPool | Any signed-in user | Shared reference data, never per-user records |
allow.owner(), ownerDefinedIn, ownersDefinedIn | userPool, oidc | The user (or users) stored in the owner field | Per-user and shared-with records |
allow.groups(), groupDefinedIn, groupsDefinedIn | userPool, oidc | Members of a Cognito group, static or stored on the record | Admin and tenant roles |
allow.custom() | lambda | Whatever your Lambda authoriser decides | Rules that need external context; test them as code |
- Owner reassignment: by default an owner can change the owner field to another user. Add a field-level rule on the owner field that allows the owner only
readanddelete, as Amplify's per-user documentation shows. - Global rules: Amplify's examples use a global
publicApiKeyrule to get started and recommend model or field level rules for production. Search the schema for a global rule before every release. - Dynamic rules on custom operations: owner and group-defined-in rules are not supported on non-model types; custom queries and mutations with these rules fail deployment, so custom operations need their own checks, usually in the handler.
- IAM access is always on: every Gen 2 project enables IAM authorisation on the data API (the console data manager uses it). Any principal with AppSync permissions in the account can therefore reach the data, which makes account IAM hygiene part of application security. See the GraphQL Security Guide for schema-level threats such as introspection and query depth.
API key lifetime
When apiKey is used, apiKeyAuthorizationMode: { expiresInDays: n } sets the expiry relative to deployment, and redeploying before expiry extends it. Rotating a key that has leaked means overriding the logical ID of the cfnApiKey resource in amplify/backend.ts, deploying, removing the override and deploying again. Treat the key as an identifier that throttles abuse, not as a credential: it is delivered to every browser.
Admin roles and privileged access
Backend deployment service role
Amplify uses a service role (the console use case is Amplify - Backend Deployment) to deploy backend resources from the build pipeline. AWS describes it as having account administrative permissions. Practical consequences:
- Anyone who can merge to a connected branch can change cloud infrastructure: the backend definition is code in the repository, and the pipeline deploys it with the service role. Branch protection and required reviews on connected branches are therefore privileged access controls.
- Condition the trust policy: the current default trust policy enforces
aws:SourceArn(for examplearn:aws:amplify:<region>:<account>:apps/*) andaws:SourceAccount. Roles created earlier may not; add both conditions to prevent the confused deputy problem. - One role per app where practical: a shared role across apps means a compromise of one repository affects every app's resources.
- Separate accounts for production: because the role is administrative within its account, the strongest boundary is putting production Amplify apps in their own AWS account. See the AWS Organizations and SCPs Guide.
SSR compute role
For server-side rendered apps (for example Next.js), the SSR compute role gives server code temporary credentials to call AWS services such as S3 or Amazon Bedrock, instead of keys in environment variables. Its trust policy names amplify.amazonaws.com as the principal. AWS's own warnings are direct:
- Credentials are live in the runtime: if SSR code exposes them by design, by a bug or through remote code execution, an unauthorised user gains the role's permissions. Server-side request forgery and verbose error pages are the usual routes.
- Least privilege: the role is only called from SSR compute functions and should grant only what that code needs, for example
s3:GetObjecton one prefix rather thans3:*. - Public repositories: where auto branch creation or pull request previews are on, AWS does not recommend an app-level role; attach it to specific branches only, because a pull request from a stranger could otherwise run with it.
- Response: revoke the role's active sessions in IAM, then remove the role in the Amplify console (App settings > IAM roles > Compute role), which takes immediate effect. The role can be changed without redeploying.
See the AWS IAM Policies and Roles Guide and the guide to reviewing AWS trust policies for confused deputy.
Integrations, API tokens and service accounts
Secrets versus environment variables
| Mechanism | Storage | Visibility | Use for |
|---|---|---|---|
Gen 2 secrets (Hosting > Secrets; npx ampx sandbox secret set) | AWS Systems Manager Parameter Store, all-branch or branch-specific | Resolved in the backend with secret('name') | Social sign-in client secrets, third-party API keys used by the backend |
| Gen 1 environment secrets | Parameter Store SecureString at /amplify/{app_id}/{env}/{name} with the default KMS key | Build only, as a JSON string in process.env.secrets | Legacy apps; Amplify can read only its own app and environment path |
| Environment variables (console) | Amplify app configuration | Visible to anyone who can read the app settings; can be written into the build | Non-confidential behaviour flags, as Amplify's documentation describes them |
Client .env written at build (for example REACT_APP_*) | Compiled into the JavaScript bundle | Public to every visitor | Public configuration only |
The common mistake is following the documented pattern of echoing a console variable into .env during the build and then using it for a private key. Anything that reaches the client bundle is public. See the Environment Variables and .env Files guide.
What amplify_outputs.json exposes
amplify_outputs.json is the client configuration file, generated by ampx sandbox, by the CI/CD pipeline per branch, or with ampx generate outputs. It contains the Region, user pool ID, user pool client ID, identity pool ID, password and MFA settings, the AppSync endpoint URL, the default and enabled authorisation types and, when API key mode is on, the api_key. None of these is a secret by design: the app needs them in the browser. The security consequence is that your authorisation rules, Cognito app client settings (for example whether self sign-up is enabled) and identity pool roles must hold up against someone who has read this file and calls AppSync, Cognito and the identity pool directly, bypassing your UI. Test that way.
Repository connection and build identity
- Git provider access: Amplify connects to the repository to build on push. Restrict the connection to the repositories the app needs and review it when people leave.
- Third-party tokens in builds: package registry tokens and monitoring keys used during the build belong in secrets, not environment variables, and should be read-only.
- Pipeline credentials outside Amplify: if a separate CI system calls Amplify APIs, give it a federated role rather than an access key. See the CI/CD Pipeline Identity Security Guide.
Logging and detection
Amplify API calls are recorded by AWS CloudTrail with event source amplify.amazonaws.com (Amplify console API) and amplifybackend.amazonaws.com (Admin UI API). The data plane lives in AppSync, Cognito, Lambda and S3, each with its own logs.
| Signal | Source | Why it matters |
|---|---|---|
DeleteApp, DeleteBackendEnvironment, CreateApp | CloudTrail, Amplify | Destructive or unexpected new apps |
| Changes to app or branch settings, including access control and environment variables | CloudTrail, Amplify (update app and branch calls) | Removal of a branch password, new variables, changed roles |
Changes to the compute or service role, and iam:PassRole to Amplify | CloudTrail, IAM and Amplify | Privilege escalation through a more powerful role |
| Parameter Store reads and writes under Amplify paths | CloudTrail, SSM | Secret access outside the build |
| AppSync request and resolver logs | CloudWatch Logs (AppSync logging) | Unauthorised errors, bulk list queries with the API key |
| WAF sampled requests and rate rule matches | AWS WAF | Scraping and brute force against hosted pages |
Hosting access control throttles failed password attempts, which limits dictionary attacks but does not give you a per-user audit trail. Route CloudTrail to a central account; see the AWS CloudTrail Guide.
Metrics that show the controls work:
- Number of models or fields with
publicApiKeyor global rules in production schemas (target zero, or a documented exception list). - Days to API key expiry for every app using API key mode (no key longer than your agreed maximum).
- Share of preview branches with access control or no production data.
- Service roles without
aws:SourceArnandaws:SourceAccountconditions (target zero). - SSR compute roles with wildcard actions or resources (target zero).
Hardening baseline
Hosting
- Access control on non-production branches: Hosting > Access control > Manage access, either for all branches or per branch with Restricted-password required. A branch password overrides the app-level one. For SSR apps, run a new build for the setting to take effect.
- Firewall: associate an AWS WAF web ACL for IP allow and block lists, geographic rules, rate-based rules over a trailing five-minute window, and managed rules for SQL injection and cross-site scripting. Body inspection covers the first 8 KB of the request, and a web ACL can serve several apps in the same Region. See the Web Application Firewall Guide.
- Cookies on the default domain:
amplifyapp.comis on the Public Suffix List, and AWS recommends a__Host-prefix for any sensitive cookie set there, to help defend against cross-site request forgery. See the Cookie Security Guide. - Pull request previews: turn them off for public repositories, or make sure previews never get production roles, secrets or data.
Rollout order
- Inventory: list every Amplify app, branch, connected repository, service role and compute role in each account and Region.
- Schema review: grep each
amplify/data/resource.tsforpublicApiKey, global rules and unprotected owner fields; test with a second user and with the API key alone against the AppSync endpoint. - Roles: add trust conditions to service roles, replace wildcard SSR permissions with resource-scoped ones, move app-level compute roles to branch level where repositories are public.
- Secrets: move credentials out of environment variables into secrets, rotate anything that was ever a variable or in a bundle.
- Hosting gates: enable access control on previews and associate a WAF web ACL in count mode, review matches for a week, then switch rules to block.
- Rollback: keep the previous schema and role policy in Git; a redeploy of the prior commit restores the backend, and WAF rules can return to count mode without downtime.
Small versus large teams: a single developer can rely on one account per environment, branch-level roles and a schema review checklist. Larger organisations should add an SCP that restricts who can modify Amplify roles, central WAF policies and automated schema linting in pull requests.
Common misconfigurations
- Tutorial schema in production: a global
allow.publicApiKey()left in place exposes every model to anyone who loads the site. The same pattern of client-shipped keys with permissive backend rules drives many mobile and web data leaks. - Owner field left writable: users reassign records to others, or claim them, because the owner field has no field-level rule.
- Private keys in build variables: a value echoed into
.envduring the build ends up in the bundle. - App-level SSR role on a public repository with previews: preview builds run with the same AWS permissions as production.
- Preview branch with production backend and no password: the branch URL follows a predictable pattern (branch name plus app ID on
amplifyapp.com) and is often shared in tickets and chat. - Old service roles without source conditions: created before the default trust policy enforced
aws:SourceArnandaws:SourceAccount.
Framework mapping
| Amplify control | NIST SP 800-53 Rev 5 | NIST CSF 2.0 | ATT&CK technique addressed |
|---|---|---|---|
| Authorisation rules, owner field protection | AC-3, AC-6 | PR.AA-05 | T1530 Data from Cloud Storage |
| Secrets in Parameter Store, not variables or bundles | IA-5, SC-28 | PR.AA-05 | T1552.001 Credentials In Files |
| Service and SSR role least privilege | AC-6 | PR.AA-05 | T1078.004 Cloud Accounts |
| WAF and branch access control | SC-7 | DE.CM-01 | T1190 Exploit Public-Facing Application, T1110 Brute Force |
| CloudTrail and AppSync logging | AU-2, AU-6 | DE.CM-01 | T1098 Account Manipulation (role changes) |
Practitioner checklist
- No
allow.publicApiKey()or global rule in production schemas without a written exception and short key expiry. - Owner fields protected with a field-level rule allowing only
readanddelete. - Authorisation tested directly against the AppSync endpoint with a second user and with the API key alone.
- Backend deployment service role per app, trust policy conditioned on
aws:SourceArnandaws:SourceAccount. - SSR compute role scoped to named resources and attached per branch where the repository is public.
- All credentials in Amplify secrets; nothing confidential in environment variables or client
.envfiles. - Access control on every preview branch; WAF web ACL associated with production apps.
- Branch protection and reviews on every connected branch, because merges deploy infrastructure.
- CloudTrail alerts on role, access control and environment variable changes for Amplify apps.
Standards and references
- AWS: Security in Amplify
- AWS Amplify: Customize your auth rules
- AWS Amplify: Per-user/per-owner data access
- AWS: Adding an SSR Compute role to allow access to AWS resources
- AWS: Adding a service role to deploy backend resources
- AWS Amplify: Secrets and environment variables
- AWS: Restricting access to an Amplify app's branches
- AWS: Firewall support for hosted sites
Related NHI Mgmt Group resources: Amazon Cognito Security Guide, AWS Security Guide, Secrets Management Guide, CSRF Prevention Guide.