AWS Bedrock with .NET: Advanced Integration & Production Setup

AWS Bedrock with .NET: Advanced Integration & Production Setup
Author(s): Ajay Kumar
Last updated: 10 Jan 2026

The Problem: Credentials Buried in the Cloud


Picture this: You've built a Bedrock chatbot locally using aws configure. It works perfectly on your machine. You deploy it to an EC2 instance in production, and boom—UnauthorizedException. The code is identical. The code didn't change. So what's broken?

Your local machine had credentials in ~/.aws/credentials. The EC2 instance doesn't. And hardcoding AWS keys into your source code? That's a security audit nightmare waiting to happen. You need a different approach—one that works for development, staging, and production without storing secrets in git.

This is where advanced authentication patterns come in. They let you securely manage credentials across different deployment environments without changing your code.

ℹ️ Coming from the basics?

If you haven't read Getting Started with AWS Bedrock in .NET yet, start there. This article builds on that foundation and assumes you're familiar with the Invocation APIs.

What: The SDK's Credential Chain


The AWS SDK doesn't require you to explicitly pass credentials. Instead, it follows a credential resolution chain—a priority-ordered search for credentials from different sources.

Think of it like this: Imagine you're looking for your API key. You check your desk (environment variables), then your backpack (.aws/credentials file), then your jacket pockets (EC2 metadata). Once you find it, you stop searching. The SDK does the same thing, in a predictable order.

Here's the chain (in priority order):

  1. Explicit credentials: If you pass credentials directly in code (not recommended for production)
  2. Environment variables: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN
  3. Credentials file: ~/.aws/credentials (local dev)
  4. IMDS (Instance Metadata Service): EC2 instance role (automatic)
  5. ECS task role environment variables: Automatic in ECS/Fargate
  6. IAM role for Lambda: Automatic in Lambda execution

The beauty of this chain is: your code never changes. You write one invocation, and it works locally (using credentials file), in CI/CD (using environment variables), and in production (using EC2 / Lambda roles). The SDK figures out which source to use automatically.

When: Choosing Your Authentication Pattern


Different deployment environments call for different patterns. Here's a decision matrix to help you pick the right approach:

Scenario Pattern Pros Cons
Local development (your laptop) ~/.aws/credentials file Zero code changes, works offline Only works on your machine
Quick local testing (STS tokens) SessionAWSCredentials in code (temp tokens) Temporary access, portable Credentials expire; need to refresh
Production on EC2 EC2 Instance Profile Role (IMDS) Recommended: Auto-refresh, no code changes, no secrets in git EC2-specific; requires IAM role setup
Production on Lambda or ECS Execution Role (auto via env vars) Built-in; no setup needed; highly secure Service-specific setup required
CI/CD pipelines (GitHub Actions, etc.) Environment variables or OIDC federation No long-lived secrets; portable Need to configure OIDC trust policy
💡 The Best Practice

Use role-based authentication (EC2 instance role, Lambda execution role, or ECS task role) for any production workload. It's automatic, doesn't require secrets in code, and permissions rotate transparently. Config it once, forget about credential management forever.

How: Implementing Each Pattern


Pattern 1: Session Credentials (Quick Local Testing)

Use this for testing with temporary credentials from AWS STS. Useful when you can't install aws-cli or want isolated, time-limited access.


SessionAWSCredentials GetCredentials()
{
    // ⚠️ NEVER hardcode credentials in production!
    // Use environment variables or CloudFormation outputs instead
    string accessKey = Environment.GetEnvironmentVariable("AWS_ACCESS_KEY_ID") 
        ?? throw new InvalidOperationException("AWS_ACCESS_KEY_ID not set");
    string secretKey = Environment.GetEnvironmentVariable("AWS_SECRET_ACCESS_KEY")
        ?? throw new InvalidOperationException("AWS_SECRET_ACCESS_KEY not set");
    string sessionToken = Environment.GetEnvironmentVariable("AWS_SESSION_TOKEN")
        ?? throw new InvalidOperationException("AWS_SESSION_TOKEN not set");
    
    return new SessionAWSCredentials(accessKey, secretKey, sessionToken);
}

var credentials = GetCredentials();
var client = new AmazonBedrockRuntimeClient(credentials, RegionEndpoint.USEast1);
            
Code Sample #1 : Creating temporary session credentials programmatically

ℹ️ Session credentials expire

Session tokens are time-limited (typically 15 minutes to 1 hour). Once expired, requests fail with UnauthorizedException. Use this pattern only for testing or short-lived scripts, not long-running services.

Pattern 2: Environment Variables (Containers & CI/CD)

Store credentials in environment variables. This is ideal for containerized workloads and CI/CD pipelines where your infrastructure orchestrator (Kubernetes, Docker, GitHub Actions) injects secrets at runtime.


// The SDK automatically looks for AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
// No manual credential handling needed
var client = new AmazonBedrockRuntimeClient(RegionEndpoint.USEast1);

// This works because the SDK's credential chain finds the env vars
var response = await client.InvokeModelAsync(request);
            
Code Sample #2 : Using environment variables (SDK resolves automatically)

In Docker: Pass secrets as environment variables at container startup:


docker run \
  -e AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID \
  -e AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY \
  -e AWS_REGION=us-east-1 \
  my-bedrock-app:latest
            
Code Sample #3 : Docker example passing AWS credentials as env vars

💡 Secret management in Kubernetes

Use Kubernetes Secrets or external secret managers (HashiCorp Vault, AWS Parameter Store) to inject credentials safely. Docker Compose uses environment variables similarly.

Pattern 3: EC2 Instance Profile Role (Production on EC2)

This is the recommended approach for production on EC2. The SDK automatically retrieves temporary credentials from the EC2 Instance Metadata Service (IMDS) — no hardcoded keys, no manual credential rotation.

How it works: Every EC2 instance has access to its metadata service at http://169.254.169.254/. When you create an instance with an IAM role attached, the role's credentials are available at that endpoint. The SDK automatically fetches them, uses them for requests, and refreshes them before expiry.


// No credentials code needed! SDK discovers the EC2 instance role automatically.
var client = new AmazonBedrockRuntimeClient(RegionEndpoint.USEast1);

// This just works because the SDK's credential chain finds IMDS
var response = await client.ConverseAsync(request);
            
Code Sample #4 : Using EC2 instance role (no credentials needed in code)

Creating an EC2 Instance Profile Role (Step-by-Step)

Step 1: Create an IAM Role

  1. AWS Console → IAM → Roles → Create Role
  2. Trusted entity type: AWS service
  3. Service: EC2
  4. Click "Next" → "Create role"

Step 2: Add Bedrock Permissions (Inline Policy)

Attach this least-privilege policy to the role:


{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "bedrock:InvokeModel",
        "bedrock:InvokeModelWithResponseStream"
      ],
      "Resource": "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-*"
    }
  ]
}
                
Code Sample #5 : IAM policy for Bedrock invocation on EC2

💡 Least-privilege approach

Scope the Resource ARN to specific models you use (e.g., anthropic.claude-3-haiku-*) rather than wildcard. This limits damage if credentials are compromised.

Step 3: Attach Role to EC2 Instance

  1. AWS Console → EC2 → Instances → Select your instance
  2. Actions → Security → Modify IAM role
  3. Select the role you created
  4. Click "Update IAM role"

Step 4: Verify (Test the Connection)


# SSH into your EC2 instance
ssh -i your-key.pem ec2-user@your-instance-ip

# Test if IMDS is reachable and the role is attached
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/

# Should output your role name. Then test Bedrock access:
dotnet run  # Run your app; it should authenticate automatically
                
Code Sample #6 : SSH into EC2 and test Bedrock access

Pattern 4: Lambda & ECS (Serverless Production)

Both Lambda and ECS automatically provide credentials via execution roles. No manual setup beyond attaching the role to your function/task.


[LambdaFunction]
public async Task<APIGatewayProxyResponse> Handler(APIGatewayProxyRequest request)
{
    // SDK automatically discovers Lambda execution role
    var client = new AmazonBedrockRuntimeClient(RegionEndpoint.USEast1);
    
    var response = await client.ConverseAsync(new ConverseRequest
    {
        ModelId = "anthropic.claude-3-haiku-20240307-v1:0",
        Messages = new() { new Message { Role = ConversationRole.User, Content = new() 
        { 
            new ContentBlock { Text = request.QueryStringParameters?["prompt"] ?? "Hello" } 
        } } }
    });
    
    return new APIGatewayProxyResponse
    {
        StatusCode = 200,
        Body = response.Output.Message.Content[0].Text
    };
}
            
Code Sample #7 : Lambda handler using automatic execution role credentials

ℹ️ ECS & Fargate

Same pattern for ECS. Attach the task role in your task definition, and the SDK discovers it via the AWS_CONTAINER_CREDENTIALS_FULL_URI environment variable (automatically set by ECS).

Troubleshooting: When Things Break


Common Authentication Errors & Fixes

If auth fails, the cause is usually one of these:

Error Likely Cause Fix
UnauthorizedException Credentials missing or not found Run aws sts get-caller-identity to test. Check ~/.aws/credentials or env vars.
AccessDeniedException Credentials valid but lack Bedrock permissions Check IAM policy attached to role/user. Must include bedrock:InvokeModel.
ResourceNotFoundException Model not available in region Check model availability. Claude 3 Haiku is us-east-1 and us-west-2 only.
EC2 instance timeout on first invoke IMDS not reachable (wrong VPC config) Instance must have internet access (or NAT gateway for private subnets). Test IMDS: curl http://169.254.169.254/
💡 Always test credentials locally first

Before deploying, verify credentials work with the AWS CLI: aws bedrock list-foundation-models --region us-east-1. If the CLI works, your app will too (same SDK under the hood).

Key Takeaways & Best Practices


  • Local dev: Use ~/.aws/credentials — run aws configure once and forget about it.
  • Production on EC2: Attach an instance profile role with Bedrock permissions. Let IMDS handle credentials automatically.
  • Production on Lambda/ECS: Attach the execution role with Bedrock permissions. No code changes needed.
  • Never hardcode credentials in source code or config files. Always use the credential chain (env vars, roles, IMDS).
  • Test with AWS CLI first. If credentials work with the CLI, they'll work with your .NET app.
ℹ️ Next in the Series

Now that you've mastered production authentication, you're ready for multi-turn conversations. Explore the Converse API to build stateful chatbots and assistants that remember context across turns.

For reference, the code repository being discussed is available at github: https://github.com/ajaysskumar/ai-playground

Thanks for reading through. Please share feedback, if any, in comments or on my email ajay.a338@gmail.com

Copyright © 2026 Dev Codex

An unhandled error has occurred. Reload 🗙