smithy-lang / smithy-typescript

Smithy code generators for TypeScript. (in development)
Apache License 2.0
211 stars 78 forks source link

unable to combine `httpBearerAuth` with `httpApiKeyAuth` #1293

Closed OlafConijn closed 1 month ago

OlafConijn commented 1 month ago

We generate node/typescript clients using the smithy-aws-typescript-codegen package. We want our clients to support 2 different authentication schemes: bearerAuth (we use for JWT tokens) and httpApiKeyAuth (used for API keys). choosing which authentication scheme to use would be up to the client.

we configure our service as follows:

@httpBearerAuth
@httpApiKeyAuth(scheme: "Key", name: "Authorization", in: "header")
@auth([httpApiKeyAuth, httpBearerAuth])
service EchoService {

expecting either authentication scheme to work and if both are provided the api key to take precedence over the bearer token.

we use the smithy CLI and the smithy-aws-typescript-codegen to generate typescript clients.

our client seems to support both apiKey and token to be passed in its configuration. however, if the token is not present in the configuration passed to the client it fails with the error: TokenProviderError: Could not load token from any providers

below a couple of examples of what works and what doesnt:

  // this works, uses the token for auth.
  const echoService = new EchoResponder({token: {token: "my token"}});

  // this works, uses the api key for auth.
  const echoService = new EchoResponder({apiKey: "my api key", token: {token: "dummy token"}});

  // this does not work!
  // the expectation is that it should use the api key for auth.
  // instead it fails with the error: TokenProviderError: Could not load token from any providers
  const echoService = new EchoResponder({apiKey: "my api key" });
syall commented 1 month ago

Hi @OlafConijn!

Wanted to confirm, is this for a new service or an existing one? Want to narrow down if this was encountered during an upgrade or not.

however, if the token is not present in the configuration passed to the client it fails with the error: TokenProviderError: Could not load token from any providers

In AWS SDKs (which use smithy-aws-typescript-codegen), tokens are resolved through a "default chain". If the SDK cannot resolve a token through this chain, it will throw an error.

For the @auth order, this is a known limitation of the default implementation of smithy-typescript currently, and the only current workaround would be to enable experimentalIdentityAndAuth, coupled with undoing the AWS SDK customization in the consuming clients via an integration (could be done through an internally vended Java library).

  1. In each smithy-build.json, enable experimentalIdentityAndAuth in the typescript-client-codegen plugin, e.g.
{
    // ...
    "plugins": {
        "typescript-client-codegen": {
            // ...
            "package": "PACKAGE_NAME",
            "packageVersion": "PACKAGE_VERSION",
            // Enable experimentalIdentityAndAuth that supports `@auth` ordering
            "experimentalIdentityAndAuth": true
        }
    },
    // ...
}
  1. "Undo" the AWS SDK Bearer Auth customization to look like the original Bearer Auth scheme.

Note that this approach would change the shape of how API keys are passed in, as we standardized what API keys look like (although this could also be customized via codegen similar to Bearer Auth):

// Instead of this:
{
  apiKey: "ABC"
}
// API Keys look like this:
{
  apiKey: {
    apiKey: "ABC"
  }
}

Another option would be a feature request: allow for flags to turn on / off specific AWS integrations, which would also be configured via smithy-build.json. However, this would be a feature request, and is not currently supported.

Let me know if you have any more questions!


Notes:

  // this works, uses the token for auth.
  const echoService = new EchoResponder({token: {token: "my token"}});

This won't throw an error since token is explicitly provided, and won't search in the default chain.

  // this works, uses the api key for auth.
  const echoService = new EchoResponder({apiKey: "my api key", token: {token: "dummy token"}});

This won't throw an error since token is explicitly provided, and won't search in the default chain.

  // this does not work!
  // the expectation is that it should use the api key for auth.
  // instead it fails with the error: TokenProviderError: Could not load token from any providers
  const echoService = new EchoResponder({apiKey: "my api key" });

This fails since a token is not provided, and will search in the default chain. This chain will look for AWS-related config, and if could not be found, will throw an error.

OlafConijn commented 1 month ago

thanks! experimentalIdentityAndAuth seems to work well for us.

happy to close this issue (or leave it open) - whichever you prefer