Skip to content
Infrastructure as Code iac providers 4 min read

Provider Authentication

Every provider that talks to a remote API needs credentials, and how you supply those credentials is one of the most consequential security decisions in your Terraform setup. The wrong choice — hardcoding an access key inside a .tf file — leaks secrets into version control, plan output, and state. The right choice keeps credentials out of your configuration entirely and lets the surrounding environment supply them. This page covers the standard authentication methods, the order in which providers resolve them, and how to wire short-lived credentials into CI. Everything here applies equally to OpenTofu, which uses the same provider plugins and credential chains.

Why you never hardcode secrets

Provider blocks are plain HCL. Anything you write there is committed to Git, echoed in terraform plan diffs, and frequently embedded verbatim into terraform.tfstate. A static access_key/secret_key pair in source control is effectively a public credential the moment it is pushed.

# DO NOT do this — credentials in source control and state
provider "aws" {
  region     = "us-east-1"
  access_key = "AKIAIOSFODNN7EXAMPLE"
  secret_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
}

Warning: Provider configuration is captured in state and plan files. Treat both as sensitive artifacts, but never rely on that to excuse inline secrets — keep credentials out of .tf files altogether.

The fix is to leave authentication arguments out of the provider block and let the provider read them from the environment, a credentials file, or an attached IAM role.

# Region is safe to commit; credentials come from the environment
provider "aws" {
  region = "us-east-1"
}

Environment variables

Environment variables are the most portable method and the one CI systems use. The AWS provider honors the standard AWS SDK variables, so the same export works for the CLI, SDKs, and Terraform.

export AWS_ACCESS_KEY_ID="AKIAIOSFODNN7EXAMPLE"
export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
export AWS_REGION="us-east-1"

terraform plan

For temporary credentials (from SSO, MFA, or an assumed role) also export the session token:

export AWS_SESSION_TOKEN="FwoGZXIvYXdzEJ...truncated...=="

Shared credentials files

The AWS CLI stores named profiles in ~/.aws/credentials and ~/.aws/config. Point the provider at a profile rather than inlining keys — useful on developer machines that switch between accounts.

provider "aws" {
  region                   = "us-east-1"
  shared_credentials_files = ["~/.aws/credentials"]
  profile                  = "staging"
}

You can also select the profile via the environment, keeping the provider block free of any account-specific detail:

export AWS_PROFILE="staging"
terraform apply

IAM roles and instance profiles

When Terraform runs on EC2, ECS, EKS, or a Lambda function, attach an IAM role to the compute and let the provider pick up credentials automatically from the instance metadata service. No keys exist anywhere — they are issued on demand and rotated by AWS.

To assume a role from a different starting identity (for example, a base profile that assumes a cross-account deployment role), use the assume_role block:

provider "aws" {
  region = "us-east-1"

  assume_role {
    role_arn     = "arn:aws:iam::123456789012:role/terraform-deploy"
    session_name = "terraform-ci"
    external_id  = "devcraftly-prod"
  }
}

Output:

Acquiring state lock. This may take a few moments...
aws_s3_bucket.assets: Refreshing state... [id=devcraftly-assets]

No changes. Your infrastructure matches the configuration.

OIDC in CI/CD pipelines

The modern standard for CI is OpenID Connect (OIDC): the pipeline presents a short-lived signed token to AWS STS and receives temporary credentials, with no long-lived secret stored in the CI system at all. GitHub Actions is the most common example.

permissions:
  id-token: write
  contents: read

jobs:
  apply:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-oidc-terraform
          aws-region: us-east-1
      - uses: hashicorp/setup-terraform@v3
      - run: terraform init && terraform apply -auto-approve

The action exports AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN into the job, and the provider picks them up through the normal environment-variable path — no provider changes needed.

Tip: OIDC eliminates the single biggest credential-leak vector in CI: stored static keys. Pair it with an IAM trust policy scoped to your specific repository and branch so only intended workflows can assume the role.

Resolution precedence

When more than one source is present, the AWS provider resolves credentials in a fixed order and uses the first it finds. Knowing this order prevents surprises like a stale environment variable overriding the profile you intended.

OrderSourceTypical use
”1”Static arguments in the provider blockAvoid — only for narrow legacy cases
”2”Environment variables (AWS_ACCESS_KEY_ID, etc.)CI pipelines, OIDC sessions
”3”Shared credentials / config files (profile)Local development
”4”assume_role blockCross-account deployment
”5”Container / instance metadata (IAM role)Terraform running on AWS compute

Other providers expose the same ideas under their own names — azurerm supports environment variables, Azure CLI auth, managed identities, and OIDC federation; google supports GOOGLE_CREDENTIALS, Application Default Credentials, and Workload Identity Federation.

Best practices

  • Never place access_key, secret_key, or any token literal in a committed .tf file — supply them through the environment instead.
  • Prefer short-lived credentials: IAM roles on AWS compute and OIDC federation in CI, over static long-lived keys.
  • Scope every role to least privilege and constrain CI trust policies to specific repositories and branches.
  • Use named profiles or distinct roles per environment so a single misconfiguration cannot reach production.
  • Mark sensitive provider inputs as sensitive if you must pass them as variables, and treat state files as secret-bearing artifacts.
  • Rotate any static key that has ever been committed, and audit terraform.tfstate and plan logs for leaked values.
Last updated June 14, 2026
Was this helpful?