Richard Hatherall By Richard Hatherall 6 min read aws-sdk aws delphi tutorial

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.

Credentials for the AWS SDK for Delphi
Contents
  1. The credential provider chain
  2. Configuring credentials
  3. Why not just hardcode the keys?
  4. When things go wrong
  5. What about IsSuccessful?
  6. What's next

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:

  1. Environment variables.
  2. The shared credentials file, with the profile selected by AWS_PROFILE (or the default profile if unset).
  3. 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_ID
  • AWS_SECRET_ACCESS_KEY
  • AWS_SESSION_TOKEN (only if you're using temporary credentials, e.g. from STS or SSO)

Plus the region and profile selectors:

  • AWS_REGION (or AWS_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.