Modern AWS Authentication: Humans vs Automation
How to simplify your AWS security model by clearly separating human access from automated deployment.
AWS authentication can be a source of friction for engineers. Between IAM users, roles, SSO, OIDC, and identity providers, the terminology can quickly become overwhelming. However, for a personal project or a small production system, the complexity often comes less from the technology itself and more from the way AWS bundles very different access problems under the same set of terms.
I found that my own confusion cleared up once I stopped treating "access" as a single problem. Instead, I realised that authentication becomes much clearer when you explicitly separate human access from automation access.
The initial confusion
When I first started managing my own AWS accounts, I followed the path many of us take: I created a single AWS account, set up an IAM user for myself, and generated a pair of long-lived Access Keys. I used those same keys for everything: my local CLI, my Terraform runs, and my GitHub Actions deployments.
This approach works, but it feels increasingly "wrong" as the system matures. I was mentally conflating two very different things: my own need to log in and look at the console, and my CI/CD pipeline's need to deploy new code.
The confusion was compounded by the terminology. I wasn't sure whether I needed Okta or Azure AD for SSO, whether OIDC replaced SSO, or how I would log in if I deleted my IAM user. Human access and automation access are parallel systems, not layered ones. They solve different problems and should be handled with different tools.
The missing mental model
The first step to clarity is understanding what AWS means by an "identity provider". In an enterprise environment, this is usually an external service like Okta. However, for a small system, you do not need a corporate IdP to use modern authentication.
AWS IAM Identity Centre (the successor to AWS SSO) provides a built-in identity store. You can create a user directly within AWS, assign them to a group, and give that group permission to access your account. You get a dedicated login portal, support for MFA, and short-lived credentials for your local CLI, all without ever creating a traditional IAM user.
Crucially, this is entirely separate from how your automation works. AWS expects you to solve human authentication and workload authentication independently; confusion creeps in when you try to force them into the same shape.
Humans vs automation
By separating these concerns, we can choose the best tool for each job:
Humans: IAM Identity Centre (SSO)
For my own access, I use SSO. This provides a significantly better user experience and a stronger security posture:
- Short-lived sessions: When I use the CLI, I get credentials that expire in hours, not years.
- MFA by default: Web-based login makes it easy to enforce multi-factor authentication.
- No stored secrets: I never have a
credentialsfile on my laptop containing permanent keys.
Automation: OIDC and Role Assumption
For GitHub Actions, I use OpenID Connect (OIDC). This allows GitHub to "prove" its identity to AWS using a cryptographic token.
- No stored credentials: There are no AWS secrets stored in GitHub.
- Specific trust: I can define exactly which repository and which branch is allowed to assume a deployment role.
- Role-based: The automation assumes a role with a narrowly scoped policy for the duration of the job, then relinquishes it.
These two systems exist side-by-side. One handles my human intent; the other handles the system's automated requirements.
What "no access keys" actually means
A common piece of security advice is to "delete your access keys". However, this is only half the story if your IAM users still exist. In a modern setup, the end state is even simpler: you should have no IAM users for day-to-day access.
When you move to SSO for humans and OIDC for CI, the concept of a "user" in the traditional IAM sense almost disappears. You have roles for human sessions and separate roles for automation.
In this model, identity is always temporary and contextual: who is acting, why they are acting, and for how long. By removing the concept of a long-lived user, you eliminate the primary vector for credential leakage. If there are no permanent keys to leak, the blast radius of a compromised developer machine or a CI pipeline is significantly reduced.
Why this stayed single-account
AWS best practice often suggests a multi-account strategy (using AWS Organizations) to isolate environments. While this is excellent advice for teams, I made a deliberate choice to keep this project in a single account.
For a personal blog, the overhead of managing multiple accounts, cross-account roles, and consolidated billing is rarely justified. Instead, I focus on blast-radius control through IAM boundaries and resource-level policies. By using Terraform to manage these roles, I can ensure that my GitHub Actions role can only touch the specific S3 bucket and CloudFront distribution it needs, providing strong isolation within the same account.
The resulting setup
The high-level architecture for this blog's authentication is now quite simple:
- One AWS Account: All resources live in a single, well-defined environment.
- IAM Identity Centre enabled: Provides my login portal and CLI access.
- One SSO User: My personal identity, mapped to an
AccountAdminpermission set. - OIDC role for GitHub Actions: A dedicated role that trusts GitHub and has permissions to deploy the site.
- Terraform managed: All roles, policies, and trust relationships are defined in code.
The old IAM user I started with has been removed. There are no long-lived access keys in my possession or in my CI secrets.
An unexpected side effect: cleaning house
Going through this exercise had an unexpected benefit: it forced me to take a look at what was actually in my AWS account.
As I removed my old IAM user and followed the trust paths more carefully, I found resources and roles created by experiments I'd long forgotten about: early Amplify projects, abandoned test deployments, some Dynamo single-table-design experiments, and permissions that no longer had a clear owner.
None of this was malicious or particularly dangerous, but it was a useful reminder that security drift isn't always about breaches. Sometimes it's just about accumulation. Having a clearer authentication model made it much easier to see what no longer belonged and clean it up with confidence.
Key takeaway
Security in a small system does not come from following every enterprise-scale recommendation. It comes from clear responsibilities and the elimination of permanent secrets.
By separating human access from automation, the "correct" tool for each task becomes obvious. You get a better experience as a developer and a stronger security model for your production environment, without the weight of unnecessary complexity.