AWS IAM Database Authentication with EF Core

Raul Naupari - Sep 7 - - Dev Community

AWS Aurora (both MySQL and Postgres) and MariaDB allow IAM authentication. With this authentication method, you don't need to use a password when you connect to a database. Instead, you use an authentication token. In this post, we are going to explore how to use IAM database authentication with EF Core and Aurora Postgres.

Before starting, you need to fulfill a few pre-requisites:

The first step is to create an IAM policy using this template:

{
   "Version": "2012-10-17",
   "Statement": [
      {
         "Effect": "Allow",
         "Action": [
             "rds-db:connect"
         ],
         "Resource": [
             "arn:aws:rds-db:[region]:[account-id]:dbuser:[db-cluster-resourceId]/[db-user-name]"
         ]
      }
   ]
}
Enter fullscreen mode Exit fullscreen mode
  • [region] is the AWS Region for the database cluster. An example of a region is us-east-2.

  • [account-id] is the AWS account number for the database cluster. An example of an account number is 1234567890.

  • [db-cluster-resourceId] is the identifier for the database cluster. An example of this identifier is cluster-ABCDEFGHIJKL01234.

  • [db-user-name] is the name of the database account associated with IAM authentication.

Once we have that, we attach it to our IAM user. The next step is to create a database (we are using the postgres user to run the following scripts):

CREATE DATABASE tasksmanager
WITH 
ENCODING = 'UTF8';
Enter fullscreen mode Exit fullscreen mode

And then a table inside this new database:

CREATE TABLE tasks(
  id UUID PRIMARY KEY,
  name VARCHAR(255)
);
Enter fullscreen mode Exit fullscreen mode

And finally, the database user:

CREATE USER db_iam_user; 
GRANT rds_iam TO db_iam_user;
GRANT postgres TO db_iam_user;
Enter fullscreen mode Exit fullscreen mode

Now, we will create a .NET ASP Core Web API application. Add the following NuGet packages to that application:

In our appsetting.json, we are going to add a ConnectionString property:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionString": "host=[aurora-database-endpoint];Database=tasksmanager;Username=db_iam_user;SSL Mode=Require;TrustServerCertificate=true"
}
Enter fullscreen mode Exit fullscreen mode

Add a file with the following content:

    public class Task
    {
        public Guid Id { get; set; }
        public string Name { get; set; }

        private Task()
        {

        }

        public Task(string name)
        {
            Id = Guid.NewGuid();
            Name = name;
        }
    }
Enter fullscreen mode Exit fullscreen mode

With that, we can create our DbContext class:

    public class ApplicationDbContext : DbContext
    {
        public DbSet<Task> Tasks { get; set; }

        public ApplicationDbContext(DbContextOptions options) : base(options)
        {

        }
    }
Enter fullscreen mode Exit fullscreen mode

Now, add a ServiceCollectionExtensions.cs file. Here is where all the magic happens:

    public static class ServiceCollectionExtensions
    {
        public static IServiceCollection AddPostgreSQL(this IServiceCollection services, IConfiguration configuration)
        {
            services.AddDbContext<ApplicationDbContext>(options =>
            {
                var connectionString = configuration["ConnectionString"];
                options.UseNpgsql(connectionString, npgsqlOption => { npgsqlOption.UseAwsIamAuthentication(); });
                options.UseLowerCaseNamingConvention();
            });
            return services;
        }

        public static NpgsqlDbContextOptionsBuilder UseAwsIamAuthentication(this NpgsqlDbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.ProvidePasswordCallback(RequestAwsIamAuthToken);
            return optionsBuilder;
        }

        static string RequestAwsIamAuthToken(string host, int port, string database, string username)
        {
            return RDSAuthTokenGenerator.GenerateAuthToken(host, port, username);
        }
    }
Enter fullscreen mode Exit fullscreen mode

Then, add the following line to your Program.cs file:

builder.Services.AddPostgreSQL(builder.Configuration);
Enter fullscreen mode Exit fullscreen mode

Add a couple of models for our request and response:

    public class RegisterTaskCommand
    {
        public string Name { get; set; }
    }

    public class ListTasksResult
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
    }
Enter fullscreen mode Exit fullscreen mode

And finally, our TaskController.cs file:

    [ApiController]
    [Route("[controller]")]
    public class TasksController : ControllerBase
    {
        private readonly ApplicationDbContext _context;
        public TasksController(ApplicationDbContext context)
        {
            _context = context;
        }

        [HttpGet()]
        public Task<ListTasksResult[]> ListTasks()
        {
            return _context.Tasks.Select(t => new ListTasksResult() { Id = t.Id, Name = t.Name }).ToArrayAsync();
        }

        [HttpPost()]
        public async Task<Guid> RegisterTask(RegisterTaskCommand command)
        {
            var task = new Task(command.Name);
            _context.Tasks.Add(task);
            await _context.SaveChangesAsync();
            return task.Id;
        }
    }
Enter fullscreen mode Exit fullscreen mode

Run your application and test your passwordless connection against your database. Here, you can find all the code and database scripts.

. . . . . . . . .