RDS IAM database authentication replaces the database password with a short-lived signed token: your app’s IAM role generates a token valid for 15 minutes and presents it as the password over TLS. It removes stored database credentials entirely, which is a real security win — but it needs three pieces wired correctly (instance flag, database user, IAM policy), it only works on MySQL, MariaDB, and PostgreSQL, and for high-churn connection patterns or third-party tools, a rotated password in Secrets Manager is still the simpler, better answer.
What’s happening
With IAM auth enabled, RDS accepts two kinds of logins side by side: normal password users, and users flagged for IAM. For a flagged user, the “password” is a token produced by signing a request with AWS credentials — the same SigV4 machinery as any API call. RDS validates the signature, checks that the caller’s IAM identity is allowed to connect as that database user, and requires the connection to use SSL/TLS.
The three pieces:
- The instance or cluster flag — IAM authentication is off by default and enabled per instance/cluster.
- The database user — on PostgreSQL, a role granted
rds_iam; on MySQL/MariaDB, a user created with theAWSAuthenticationPlugin. The user has no usable password; the plugin/role is the gate. - The IAM policy —
rds-db:connecton a resource ARN that names the instance’sDbiResourceIdand the database username. Note the ARN uses the resource ID, not the instance name — the single most common setup error.
Two operational truths worth knowing up front. The 15-minute token lifetime applies to establishing the connection, not to the connection itself — an open session stays open. And AWS guidance caps the pattern at roughly 200 new IAM-authenticated connections per second; token generation and validation add overhead per connect, so apps that churn connections need pooling in front — this is exactly where RDS Proxy pairs well, since the proxy can authenticate clients via IAM while holding warm connections to the database. Details per engine live in the RDS documentation at https://docs.aws.amazon.com/rds/.
What to check first
- Engine and version — MySQL, MariaDB, or PostgreSQL only; Oracle and SQL Server are out.
- Whether the flag is already on —
IAMDatabaseAuthenticationEnabledon the instance/cluster. - Connection churn rate — hundreds of new connections per second without a pool means stop and add pooling first.
- TLS posture — IAM auth requires SSL; clients must carry the RDS CA bundle.
- Who connects — application roles are a clean fit; humans and third-party BI tools are where friction starts.
How to verify
aws rds describe-db-instances --db-instance-identifier prod-db \
--query 'DBInstances[0].[IAMDatabaseAuthenticationEnabled,DbiResourceId,Engine]'
Generate a token and test a connection (PostgreSQL shown):
TOKEN=$(aws rds generate-db-auth-token \
--hostname prod-db.abc123xyz.ca-central-1.rds.amazonaws.com \
--port 5432 --username app_user --region ca-central-1)
PGPASSWORD="$TOKEN" psql \
"host=prod-db.abc123xyz.ca-central-1.rds.amazonaws.com port=5432 dbname=app user=app_user sslmode=verify-full sslrootcert=global-bundle.pem"
A rejected login with PAM authentication failed or password authentication failed almost always means the IAM policy ARN or the database-side role is wrong, not the token. In the console, the flag lives under RDS > Databases > (instance) > Configuration, and failed connection attempts show in the engine’s error log under Logs & events.
The runbook
-
Enable the flag.
aws rds modify-db-instance --db-instance-identifier prod-db --enable-iam-database-authentication --apply-immediately(for Aurora,modify-db-cluster). This does not restart the instance on current engines, but confirm in the pending modifications before assuming. -
Create the database user. PostgreSQL:
CREATE USER app_user LOGIN; GRANT rds_iam TO app_user;. MySQL:CREATE USER 'app_user'@'%' IDENTIFIED WITH AWSAuthenticationPlugin AS 'RDS';. Grant only the schema privileges the app needs. -
Write the IAM policy with the resource ID.
{ "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": "rds-db:connect", "Resource": "arn:aws:rds-db:ca-central-1:123456789012:dbuser:db-ABCDEFGHIJKL123/app_user" }] }Attach it to the application’s role — the ECS task role, Lambda execution role, or EC2 instance profile.
-
Wire token generation into the app. Most AWS SDKs expose a token generator; treat the token as the password and regenerate on every new connection, since pools that cache a password will fail reconnects after 15 minutes.
-
Enforce TLS with certificate verification. Ship the RDS CA bundle with the app and use
sslmode=verify-full(PostgreSQL) or the equivalent MySQL flags — IAM auth without certificate checks keeps the token but gives up the endpoint guarantee. -
Retire the old password path. Once IAM login is proven, remove the password from Secrets Manager or rotate it into a break-glass-only credential, and alarm on its use.
When this is the wrong move
Third-party tools that only know how to store a static password — BI dashboards, legacy ETL, some migration tools — fight you every step. Very high connection-churn workloads without a pooling layer will hit token-generation overhead and the ~200 connections-per-second guidance. Cross-account and human access can work but multiplies IAM complexity. In all of those cases, a strong password in Secrets Manager with automatic rotation delivers most of the hygiene with none of the integration friction — the Secrets Manager vs Parameter Store decision covers where that credential should live. Mixing models is fine: IAM for your services, rotated passwords for the tools.
Common mistakes
- Using the instance name instead of
DbiResourceIdin the policy ARN — the policy validates, attaches, and silently never matches. - Caching the token in a connection pool as if it were a password, then failing every reconnect after minute fifteen.
- Skipping the CA bundle and either failing TLS verification or disabling it “temporarily” forever.
- Granting
rds-db:connectondbuser:*/*, which quietly authorizes every IAM principal with the policy to be any database user.
Prevention
- Keep the IAM policy, database user creation script, and app config in the same repo so the three pieces cannot drift apart.
- Alarm on authentication failures in the database error log — they are your earliest signal of expired-token bugs.
- Standardize one pattern per environment: IAM for first-party services, Secrets Manager rotation for everything else.
- Re-test the token path after engine major upgrades and CA rotations; both touch the TLS layer it depends on.
Done properly, IAM database authentication removes an entire class of leaked-credential incidents for your own services — just do not force it onto tools that were never built for it. If nobody on your team truly owns the AWS environment end to end, that is the root of most of these problems — Stack Harbor runs AWS accounts daily under AWS Operations, and you can book an AWS review.