Skip to content

How to use the typescript client for IAM based api gateway #1514

Open
@purnasrivatsa96

Description

@purnasrivatsa96

I have a smithy model with lambda authorizer configuration -

@title("sample service")
@service(sdkId: "serviceName", arnNamespace: "execute-api")
@cors(origin: "*")
@integration(
    type: "aws_proxy"
    httpMethod: "POST"
    uri: "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaFunctionArn}/invocations"
    credentials: "${APIGatewayExecutionRoleArn}"
)
@httpApiKeyAuth(name: "x-session-token", in: "header")
@authorizers(
    PROTECTED_API_AUTH: {
        scheme: "smithy.api#httpApiKeyAuth"
        type: "request"
        uri: "authorizer-lambdaFunction-arn"
        resultTtlInSeconds: 0
    }
)
@authorizer("NONE")
@restJson1
@requestValidator("full")
service ServiceName {
    version: "2024-11-10"
    operations: [
        Create
        List
        Get
    ]
    errors: [
        smithy.framework#ValidationException
    ]
}


@http(method: "POST", uri: "/object")
@authorizer("PROTECTED_API_AUTH")
operation Create {
    input: CreateObjectRequest
    output: CreateObjectResponse
}

I have a typescript client generated via adding this in smithy-build.json -

"typescript-client-codegen": {
      "package": "@com.base/service-name-client",
      "packageVersion": "0.0.1"
    },

i use the openapi file generated by this smithy model in my cdk code for configuring api gateway.
Also i am editing the open api file slightly so i can configure a second api gateway as well that does not have lambda authorizer and is IAM based instead(keeping everything else same) -

    const api = new SpecRestApi(this, "ServiceApi", {
      restApiName: "ServiceApi",
      deployOptions: {
        stageName: props.stageName,
        loggingLevel: MethodLoggingLevel.INFO,
        dataTraceEnabled: true,
        metricsEnabled: true,
        accessLogDestination: new cdk.aws_apigateway.LogGroupLogDestination(
          accessLogs
        ), // Enable access logs
        accessLogFormat: cdk.aws_apigateway.AccessLogFormat.custom(
          `$context.extendedRequestId $context.identity.sourceIp $context.identity.caller $context.identity.user [$context.requestTime] "$context.httpMethod $context.resourcePath $context.protocol" $context.status $context.responseLength $context.requestId`
        ),
      },
      apiDefinition: this.restApiDefinitionWithLambdaIntegration(
        resolve(__dirname, "../generated/openapi/ServiceName.openapi.json"),
        [["LambdaFunctionArn", lambdaFunction]],
        apiExecutionRole
      ),
    });

    const apiDef = this.restApiDefinitionWithLambdaIntegration(
      resolve(__dirname, "../generated/openapi/ServiceName.openapi.json"),
      [["LambdaFunctionArn", lambdaFunction]],
      apiExecutionRole
    );

    const apiDefinitionObject = (apiDef as any).definition;

    if (!apiDefinitionObject || typeof apiDefinitionObject !== "object") {
      throw new Error(
        "Invalid API definition object. Check if the 'definition' field is present."
      );
    }

    const apiDefSecurityFieldRemoved = JSON.parse(
      JSON.stringify(apiDefinitionObject, (key, value) =>
        key === "security" ? undefined : value
      )
    );

    const apiDefIamAuthorizer = this.createRestApiWithIamAuthorizer(
      apiDefSecurityFieldRemoved
    );


  createRestApiWithIamAuthorizer(apiDef: any): any {
    apiDef.security = [{ "aws.auth.sigv4": [] }];
    apiDef.components.securitySchemes = {
      "aws.auth.sigv4": {
        type: "apiKey",
        description: "AWS Signature Version 4 authentication",
        name: "Authorization",
        in: "header",
        "x-amazon-apigateway-authtype": "awsSigv4",
      },
    };
    return ApiDefinition.fromInline(apiDef);
  }

    const apiWithIamAuth = new SpecRestApi(this, "ServiceNameApiWithIam", {
      restApiName: "ServiceNameApiInternal",
      deployOptions: {
        stageName: props.stageName,
        loggingLevel: MethodLoggingLevel.INFO,
        dataTraceEnabled: true,
        metricsEnabled: true,
        accessLogDestination: new cdk.aws_apigateway.LogGroupLogDestination(
          accessLogsIamApig
        ), // Enable access logs
        accessLogFormat: cdk.aws_apigateway.AccessLogFormat.custom(
          `$context.extendedRequestId $context.identity.sourceIp $context.identity.caller $context.identity.user [$context.requestTime] "$context.httpMethod $context.resourcePath $context.protocol" $context.status $context.responseLength $context.requestId`
        ),
      },
      apiDefinition: apiDefIamAuthorizer,
    });

    // Give the the rest api execute ARN permission to invoke the lambda.
    lambdaFunction.addPermission("ApiInvokeLambdaPermission", {
      principal: new iam.ServicePrincipal("apigateway.amazonaws.com"),
      action: "lambda:InvokeFunction",
      sourceArn: api.arnForExecuteApi(),
    });

    lambdaFunction.addPermission("IamAuthApiInvokeLambdaPermission", {
      principal: new iam.ServicePrincipal("apigateway.amazonaws.com"),
      action: "lambda:InvokeFunction",
      sourceArn: apiWithIamAuth.arnForExecuteApi(),
    });
  }

This give me 2 api gateways -

Image Image

I am using this the smithy generated client to make a request from another lambda function to the IAM based api gateway -

import { Service } from "@com.base/service-name-client";

// Configure the client
export const ServiceClient = new Service({
  endpoint: {
    protocol: "https",
    hostname: "IAMBasedApiGIDValue.execute-api.us-east-1.amazonaws.com",
    path: "/Prod",
  },
});


      await ServiceClient.create({
        Id: someValue,
      });

In the lambda logs from where this client invokes that IAM based APIG, I see -

2025-01-27T20:17:23.064Z	c26f0cf3-a99f-41b6-8e73-ff9510ef75c4	INFO	Received an unexpected error Error: HttpAuthScheme `smithy.api#httpApiKeyAuth` did not have an IdentityProvider configured.
    at /var/task/server/dist/node_modules/@com.base/service-name-client/node_modules/@smithy/core/dist-cjs/index.js:92:11
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async /var/task/server/dist/node_modules/@com.base/service-name-client/node_modules/@aws-sdk/middleware-logger/dist-cjs/index.js:34:22
    at async /var/task/server/dist/operations/Search.js:26:30
    at async handle (/var/task/server/dist/node_modules/@com.base/search-server/dist-cjs/server/SearchService.js:42:22)
    at async Runtime.handler (/var/task/server/dist/apigateway.js:14:34)

Why is it looking for httpApiKeyAuth ? What does this error mean ?
How do i fix this ?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions