SCC

Brasil

os cloud gurus

Software Cloud Consulting

Your software development, cloud, consulting & shoring company

Api Gateway with CDK


By Wolfgang Unger


Lets have a look how to create a API Gateway with CDK (Python)
The first approach is using the RestApi Class and code the resources and methods.
The second by using a Swagger/Open API file.
Both APIs will use lambda integrations.
We will also see how to use Authorizers with Cognito and Custom Lambda.

RestApi CDK Class


To create a Rest API you basically can use the following code:
api = aws_apigateway.RestApi(
 self,
  f"{stage}-cdk-api",
  deploy_options=deploy_options,
  default_cors_preflight_options={
    "allow_origins": aws_apigateway.Cors.ALL_ORIGINS,
    "allow_methods": aws_apigateway.Cors.ALL_METHODS,
  },
)

The deploy_options are not mandatory, if used like in this example you need to define them first.

log_group = aws_logs.LogGroup(self, "CDK-Api-Logs")
deploy_options = aws_apigateway.StageOptions(
  access_log_destination=aws_apigateway.LogGroupLogDestination(log_group),
  access_log_format=aws_apigateway.AccessLogFormat.json_with_standard_fields(
   caller=False,
   http_method=True,
   ip=True,
   protocol=True,
  request_time=True,
   resource_path=True,
   response_length=True,
   status=True,
  user=True,
  ),
  metrics_enabled=True,
)

For the lambda integration you need of course a lambda function.
This function is not part of this tutorial.
Let's assume it was created ahead of this stack and the function is passed as a parameter to the API stack:
lambda_integration = aws_apigateway.LambdaIntegration(
  handler.fn
)

Now we can create our resources and methods.
First the base resources:
api_resource = api.root.add_resource("api")
v1_resource = api_resource.add_resource("v1")

Now the api specific resources we want to define using the lambda integraion:
items_resource = v1_resource.add_resource("items")
items_resource.add_method(
  "GET",
  lambda_integration,
  authorizer=authorizer,
  authorization_type=aws_apigateway.AuthorizationType.COGNITO,
)
Now we can add more resources and methods. We can also use path variables:
item_name_resource = items_resource.add_resource("{item_id}")
item_name_resource.add_method(
  "GET",
  lambda_integration,
  authorizer=authorizer,
  authorization_type=aws_apigateway.AuthorizationType.COGNITO,
)
This is basically the API, there is just one thing missing, as you might have noticed.
I am using a congnito authorizer here, of course we must initialize this class first.
user_pool = aws_cognito.UserPool.from_user_pool_id(
  self, "auth-user-pool", "eu-west-1_xxxxx"
)
authorizer = aws_apigateway.CognitoUserPoolsAuthorizer(
  self, id="api-authorizer", cognito_user_pools=[user_pool]
)

This is all we need to do to create the API by cdk code.
There is one problem here, if we have a bigger API with a lots of methods and resources, the cdk code might grow and become less clear.

SpecRestApi CDK Class


To avoid this you can use the SpecRestApi Class and define your API resources and methods with Swagger or Open API.
api = aws_apigateway.SpecRestApi(
  self,
  "api-swagger",
  api_definition=aws_apigateway.ApiDefinition.from_asset(
   os.path.abspath(
    os.path.join(
     os.path.dirname(__file__), "swagger/api-oas30-apigateway.yaml"
    )
   )
  ),
  deploy_options = deploy_options
)

With this approach the cdk code becomes much shorter, the definition of the resources and methods is no longer required in the python cdk class.
It will be defined with the Swagger / OpenAPI json or yaml file.
Lets have a look on a OpenAPI 3.0 definition yaml.
openapi: "3.0.1"
info:
  title: "api-swagger"
  version: "2022-09-23T14:26:52Z"
servers:
- url: "https://juxxxxxxc65.execute-api.${AWS::Region}.amazonaws.com/{basePath}"
  variables:
   basePath:
    default: "/prod"
paths:

/api/v1/items:
  get:
   security:
   - AuthCustomAuthorizer: []
   x-amazon-apigateway-integration:
    type: "aws_proxy"
    httpMethod: "POST"
    uri: "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::Account}:function:item-handler/invocations"
    passthroughBehavior: "when_no_match"
    contentHandling: "CONVERT_TO_TEXT"
 options:
   responses:
    "204":
     description: "204 response"
     content: {}
    x-amazon-apigateway-integration:
     type: "aws_proxy"
     responses:
      default:
       statusCode: "204"
      responseParameters:
       method.response.header.Access-Control-Allow-Methods: "'OPTIONS,GET,PUT,POST,DELETE,PATCH,HEAD'"
       method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent'"
       method.response.header.Access-Control-Allow-Origin: "'*'"
      requestTemplates:
      application/json: "{ statusCode: 200 }"
     passthroughBehavior: "when_no_match"

There is aws specific code in this yaml (if you need lambda proxy integration), for example x-amazon-apigateway-integration.
You can use also variables like ${AWS::Region} and ${AWS::Account}.
Also there is a authorizer used, this time a custom authorizer, not a cognito authorizer.
It must be defined also in the yaml file:

components:
  securitySchemes:
    AuthCustomAuthorizer:
      type: "apiKey"
      name: "Authorization"
      in: "header"
      x-amazon-apigateway-authtype: "custom"
      x-amazon-apigateway-authorizer:
        type: "token"
        authorizerUri:
"arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::Account}:my-function:-access-control-CustomAuthorizor/invocations"
        authorizerResultTtlInSeconds: 300



Of yourse we must first create this Custom Authorizer in the CDK Code before we can use it in
the API definition (please replace the region by a variable):
custom_authorizer_arn =
 "arn:aws:lambda:eu-west-1:111111111111:function:my-api-access-control-CustomAuthorizor"
authorizer_uri="arn:aws:apigateway:{}:lambda:path/2015-03-31/functions/{}/invocations".format(
  "eu-west-1", custom_authorizer_arn)

custom_authorizer_cfn = aws_apigateway.CfnAuthorizer(self, 'AuthCustomAuthorizer',
  rest_api_id=api.rest_api_id,
 name='AuthCustomAuthorizer',
  type='TOKEN',
  authorizer_uri=authorizer_uri,
  identity_source='method.request.header.Authorization',
  authorizer_result_ttl_in_seconds=300
)


This way you can create Rest APIs either with Swagger/OpenAPi or directly by cdk code and you can use a Cognito Authorizer or also your own Custom Authorizer. Have fun with it !

  • Back to Blog Overview
  • Autor


    ...

    Wolfgang Unger

    AWS Architect & Developer

    5 x AWS Certified

    1 x Azure Certified

    A Cloud Guru Instructor

    Certified Oracle JEE Architect

    Certified Scrum Master

    Certified Java Programmer

    Passionate surfer & guitar player