Credentials for the AWS SDK for Delphi
The AWS credential provider chain, configuring credentials for different environments, and handling the exception you'll hit the first time something's wrong.
Contents
The wizard from the last post wrote a single set of credentials to your laptop's home directory. The SDK picked them up, called Translate, and returned a string. That works for development. It stops working the moment your code needs to run somewhere else — your colleague's laptop, a build server, an EC2 instance, an ECS task — and it stops working the moment any one of the pieces (access key, region, IAM permission) isn't quite right.
This post covers both: the credential provider chain the SDK uses to find credentials wherever it runs, and what happens when nothing in that chain has a usable answer.
The credential provider chain
When you write TTranslateClient.Create (or any other client), the SDK
doesn't ask you where credentials live. It walks a fixed lookup order and
picks the first source that has them:
- Environment variables.
- The shared credentials file, with the profile selected by
AWS_PROFILE(or thedefaultprofile if unset). - IAM role credentials supplied by the runtime environment: EC2 instance metadata, ECS task metadata, and similar.
The order matters because higher-priority sources override lower ones. Drop
an AWS_ACCESS_KEY_ID into a shell session and the SDK uses it even if
your shared credentials file has different keys for the same account.
That's a feature, not a bug. It lets a CI job inject credentials without
rewriting files on the build machine, and it lets you override your default
profile for a one-off script.
Everything that follows is a way of populating one of those slots.
Configuring credentials
The shared credentials file
~/.aws/credentials holds your access keys; ~/.aws/config holds your
default region and any profile-specific settings. The wizard from the last
post writes both. They look like this:
# ~/.aws/credentials
[default]
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
# ~/.aws/config
[default]
region = eu-west-1
Sections in square brackets are profiles. Add more by giving them names:
# ~/.aws/credentials
[default]
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
[client-acme]
aws_access_key_id = AKIA...
aws_secret_access_key = ...
This is the form most people will use day-to-day. It's the same file the AWS CLI and every other AWS SDK read, so configuring it once covers your other AWS tooling as well.
There are a few ways to tell the SDK which profile to use.
Inside the Delphi IDE, set the profile you want as the IDE's default at Tools > Options > Third Party > Appercept AWS SDK (star the profile). Programs launched from the IDE will pick it up automatically.
Outside the IDE, set the AWS_PROFILE environment variable before
running your app:
AWS_PROFILE=client-acme MyApp.exe
Common in CI pipelines and deployed apps.
Programmatically, each client's constructor optionally accepts an
options object — ITranslateOptions for Translate, with an analogous
interface for every other service — that sets the profile, region, and
other per-client settings explicitly. We'll meet the options route again
as the series goes.
Environment variables
Three matter most:
AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYAWS_SESSION_TOKEN(only if you're using temporary credentials, e.g. from STS or SSO)
Plus the region and profile selectors:
AWS_REGION(orAWS_DEFAULT_REGION)AWS_PROFILE
Environment variables sit at the top of the chain, which makes them the right fit for CI pipelines, container deployments, and one-shot overrides. They're also the form most cloud platforms feed credentials in: GitHub Actions, GitLab CI, and container orchestrators all let you inject the standard names without code changes.
The trade-off: environment variables show up in process listings, in some
crash dumps, and sometimes in logs. Treat them the same way you'd treat
any other secret. Don't print them, don't commit them to a .env file in
source control.
IAM roles on EC2 and ECS
Code running on AWS infrastructure can skip explicit credentials entirely. The runtime exposes a metadata endpoint that the SDK queries automatically:
- EC2: attach an instance profile (a wrapper around an IAM role) to the instance. Code on it can call AWS without an access key configured anywhere.
- ECS: assign a task role to the task definition. The credentials are scoped to the task itself.
In both cases there's nothing to do on the Delphi side. The SDK finds the metadata endpoint, fetches short-lived credentials, and refreshes them as they expire. From your code's point of view it's identical to having a working profile.
This is the model you want for production deployments. Roles can be rotated centrally, the credentials never sit on disk, and IAM policy binds permissions to the workload rather than to a long-lived user.
Other credential sources
There's more to the credential model than what's above.
SSO via IAM Identity Center: if a user has signed in with aws sso login, the SDK can use the token the CLI cached. Configure an
sso_session block in ~/.aws/config and the SDK will read the cached
token to call AWS as that user, refreshing it when needed. This is the
path most enterprise developers take today.
Process credentials, configured with credential_process in
~/.aws/config, point the SDK at a shell command that returns
credentials. This is the route for hardware-backed keys, vault
integrations, and other enterprise credential helpers.
Cognito federated identities are a different shape: an app signs a user in (via Cognito, Google, Apple, or another identity provider) and uses the temporary credentials Cognito issues to call AWS as that user. That isn't part of the default lookup chain; it's a credential source you wire in explicitly.
All three get their own treatment later. Cognito in particular has enough surface area for its own series.
Why not just hardcode the keys?
The first time you call AWS from new code, hardcoding an access key feels like the path of least resistance. Two lines, no environment juggling, done. The reason every guide tells you not to is that hardcoded keys end up in places you didn't put them:
- In commits, when the file gets staged before you remember to redact it.
- In logs, when someone adds a debug print of "the config object."
- In screenshots, when you share your screen on a call.
- In stack traces, when an exception serialises the surrounding state.
GitHub scans every public push for AWS access keys and notifies AWS directly when one shows up. AWS will email you and, depending on the key, attach an automated quarantine policy. That feedback loop is fast enough to be useful but slow enough that someone with a script can collect a working key before you've finished reading the email.
The provider chain exists so you never have to put a key in code. Use it.
When things go wrong
Run the app from the last post on a machine with no AWS credentials
configured. The SDK walks the chain, finds nothing usable, and raises an
exception before TranslateText reaches the network. Without a try
block, your Delphi app crashes with an unhandled exception and a stack
trace.
The fix is the standard Delphi exception handler:
procedure TForm1.TranslateButtonClick(Sender: TObject);
var
Client: ITranslateClient;
Response: ITranslateTranslateTextResponse;
TargetLanguageCode: string;
begin
TargetLanguageCode := LANGUAGES[TargetLanguageComboBox.ItemIndex].Code;
try
Client := TTranslateClient.Create;
Response := Client.TranslateText('auto', TargetLanguageCode, SourceMemo.Text);
ResultMemo.Text := Response.TranslatedText;
except
on E: Exception do
ResultMemo.Text := 'Error: ' + E.Message;
end;
end;
The SDK's exceptions are organised in a hierarchy. EAWSException is the
root for anything SDK-related; EAWSClientException descends from it as
the base for all service-client failures; each service has its own root
beneath that (ES3Exception, ETranslateException, and so on), with
specific error types below. Catching Exception and showing E.Message
is enough for a first pass. Once you want to handle some failure modes
differently from others, you can narrow the catch to the level you care
about. The message that arrives for a credentials problem is specific
enough to point you at the missing piece: "Unable to locate credentials",
region not configured, IAM user not authorised for the action.
The SDK retries automatically. An exception that reaches your code is one whose retry policy has already been exhausted; you're catching the final state, not a transient blip you should retry yourself.
Production code will want richer handling beyond that: logging that doesn't include credentials, and structured reporting for the error paths that matter. Those patterns turn up when a later post needs them.
What about IsSuccessful?
The response object returned by TranslateText carries an IsSuccessful
property. If you've used another AWS SDK before, you'll recognise the name
and wonder why we're not checking it.
Exceptions cover the hard errors. IsSuccessful covers a narrower class
of cases: actions that complete the network round-trip with a 2xx status
but where the response body indicates the operation didn't fully apply.
That mostly comes up in batch operations, where some items in a batch can
succeed while others fail. A single TranslateText call doesn't have a
partial-success mode. Translate either returns the translated text or
raises an exception telling you why it didn't.
IsSuccessful returns when we meet services with batch operations. For
now, the exception block above is the whole error story.
What's next
Next post: a working S3 application. Listing buckets, uploading and downloading objects, presigned URLs, and the file uploader helpers that handle multipart uploads. The credential chain you've set up here carries through unchanged. The only thing that changes is the client class.
More posts
Calling Amazon Translate from Delphi
Install the AWS SDK for Delphi, plug in credentials, and call Amazon Translate from an FMX app in under fifty lines of code.
Read more →
Asserting HTTP requests with WebMocks in Delphi
The other half of testing HTTP code with WebMocks: verifying that the right requests were made, with the right details, the right number of times.
Read more →
Build it with AI? Why the AWS SDK for Delphi still wins
Generating AWS code with AI works fine for a one-off script. The moment it deploys to AWS — credentials, STS, retries — the £149 SDK for Delphi is the cheaper choice.
Read more →