Skip to content
Back to blog
6 min read

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 credentials file 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 AccountAdmin permission 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.