<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Johannes</title>
    <description>The latest articles on DEV Community by Johannes (@johannesfloriangeiger).</description>
    <link>https://dev.to/johannesfloriangeiger</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3409738%2F582f48de-2045-484b-a5f1-d8c92190954e.jpeg</url>
      <title>DEV Community: Johannes</title>
      <link>https://dev.to/johannesfloriangeiger</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/johannesfloriangeiger"/>
    <language>en</language>
    <item>
      <title>Testing Cognito secured AWS API Gateways with AWS Custom Resources</title>
      <dc:creator>Johannes</dc:creator>
      <pubDate>Sun, 03 Aug 2025 18:00:55 +0000</pubDate>
      <link>https://dev.to/johannesfloriangeiger/testing-cognito-secured-aws-api-gateways-with-aws-custom-resources-3pjg</link>
      <guid>https://dev.to/johannesfloriangeiger/testing-cognito-secured-aws-api-gateways-with-aws-custom-resources-3pjg</guid>
      <description>&lt;p&gt;Recently I experimented with Cognito secured AWS API Gateways and came across an annoying problem - how can I test my application end to end without having to log in to Cognito with a Browser? Removing the Cognito Authorizer for testing purposes would be one option, but there are cases where applications make use of the tokens for access control or other mechanisms and changing the application behaviour with regards to tokens just for tests doesn't seem be right. Fortunately, CDK supports custom AWS resources that can be used to create dummy users with all the attributes necessary!&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 1: Basic Setup
&lt;/h2&gt;

&lt;p&gt;First, we define a Lambda that backs our API. For the purposes of this, the Lambda will just return the AWS Region in which it runs.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;producer/main.go&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package main

import (
    "context"
    "encoding/json"
    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"
    "github.com/aws/aws-sdk-go-v2/config"
)

type Handler struct {
    Region string
}

type Response struct {
    Region string `json:"region"`
}

func (h Handler) handleRequest(_ context.Context, _ events.APIGatewayProxyRequest) (*events.APIGatewayProxyResponse, error) {
    response := Response{
        Region: h.Region,
    }
    bytes, err := json.Marshal(response)
    if err != nil {
        return nil, err
    }

    return &amp;amp;events.APIGatewayProxyResponse{
        StatusCode: 200,
        Body:       string(bytes),
    }, nil
}

func main() {
    cfg, err := config.LoadDefaultConfig(context.TODO())
    if err != nil {
        panic(err)
    }

    handler := Handler{
        Region: cfg.Region,
    }
    lambda.Start(handler.handleRequest)
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The respective CDK constructs then declare the Lambda, an API with a method and an Authorizer that makes use of an imported User Pool.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;userPool := awscognito.UserPool_FromUserPoolId(stack, jsii.String("UserPool"), jsii.String(props.UserPoolId))

// ...

producer := awscdklambdagoalpha.NewGoFunction(stack, jsii.String("Producer"), &amp;amp;awscdklambdagoalpha.GoFunctionProps{
    Entry:        jsii.String("./producer"),
    FunctionName: jsii.String("Producer"),
    Runtime:      awslambda.Runtime_PROVIDED_AL2(),
    LogGroup: awslogs.NewLogGroup(stack, jsii.String("ProducerLogGroup"), &amp;amp;awslogs.LogGroupProps{
        LogGroupName:  jsii.String("/aws/lambda/Producer"),
        RemovalPolicy: awscdk.RemovalPolicy_DESTROY,
    }),
})

restApi := awsapigateway.NewRestApi(stack, jsii.String("RestApi"), &amp;amp;awsapigateway.RestApiProps{
    RestApiName: jsii.String("API"),
})
resource := restApi.Root().AddResource(jsii.String("hello"), nil)
resource.AddMethod(jsii.String("GET"), awsapigateway.NewLambdaIntegration(producer, nil), &amp;amp;awsapigateway.MethodOptions{
    Authorizer: awsapigateway.NewCognitoUserPoolsAuthorizer(stack, jsii.String("CognitoAuthorizer"), &amp;amp;awsapigateway.CognitoUserPoolsAuthorizerProps{
        AuthorizerName: jsii.String("CognitoAuthorizer"),
        CognitoUserPools: &amp;amp;[]awscognito.IUserPool{
            userPool,
        },
    }),
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can &lt;code&gt;curl&lt;/code&gt; our API and as expected get a &lt;code&gt;401&lt;/code&gt; with &lt;code&gt;{"message":"Unauthorized"}&lt;/code&gt;. If we "somehow" would get a valid ID token, we could call the API and get the proper result, but depending on the Cognito setup that requires us to perform a few requests with a Browser etc. and we also need a user that depending on our Cognito setup might be federated and not as easy to prepare for tests. We could create test users via AWS CLI that allow us to use the existing login method but without further actions we could still not login headless and instead would still need a Browser to login and we would need to manually make sure the user gets also torn down after our tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 2: Dummy User &amp;amp; Headless Login
&lt;/h2&gt;

&lt;p&gt;So, to avoid this extra work we use a different approach: Define a new User Pool Client that allows username and password login and create a dummy user with credentials that allows us to login headless:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;userPoolClient := awscognito.NewUserPoolClient(stack, jsii.String("UserPoolClient"), &amp;amp;awscognito.UserPoolClientProps{
    AuthFlows: &amp;amp;awscognito.AuthFlow{
        UserPassword: jsii.Bool(true),
    },
    UserPoolClientName: jsii.String("UserPoolClient"),
    UserPool:           userPool,
})

dummyUser := customresources.NewAwsCustomResource(stack, jsii.String("DummyUserCustomResource"), &amp;amp;customresources.AwsCustomResourceProps{
    OnCreate: &amp;amp;customresources.AwsSdkCall{
        Service: jsii.String("CognitoIdentityServiceProvider"),
        Action:  jsii.String("adminCreateUser"),
        Parameters: map[string]interface{}{
            "UserPoolId": jsii.String(props.UserPoolId),
            "Username":   jsii.String("dummy"),
        },
        PhysicalResourceId: customresources.PhysicalResourceId_Of(jsii.String("DummyUserCustomResource")),
    },
    OnDelete: &amp;amp;customresources.AwsSdkCall{
        Service: jsii.String("CognitoIdentityServiceProvider"),
        Action:  jsii.String("adminDeleteUser"),
        Parameters: map[string]interface{}{
            "UserPoolId": jsii.String(props.UserPoolId),
            "Username":   jsii.String("dummy"),
        },
    },
    Policy: customresources.AwsCustomResourcePolicy_FromSdkCalls(&amp;amp;customresources.SdkCallsPolicyOptions{
        Resources: customresources.AwsCustomResourcePolicy_ANY_RESOURCE(),
    }),
})

dummyUserCredentials := customresources.NewAwsCustomResource(stack, jsii.String("DummyUserCredentialsCustomResource"), &amp;amp;customresources.AwsCustomResourceProps{
    OnCreate: &amp;amp;customresources.AwsSdkCall{
        Service: jsii.String("CognitoIdentityServiceProvider"),
        Action:  jsii.String("adminSetUserPassword"),
        Parameters: map[string]interface{}{
            "UserPoolId": jsii.String(props.UserPoolId),
            "Username":   jsii.String("dummy"),
            "Password":   jsii.String("Password1!"),
            "Permanent":  jsii.Bool(true),
        },
        PhysicalResourceId: customresources.PhysicalResourceId_Of(jsii.String("DummyUserCredentialsCustomResource")),
    },
    Policy: customresources.AwsCustomResourcePolicy_FromSdkCalls(&amp;amp;customresources.SdkCallsPolicyOptions{
        Resources: customresources.AwsCustomResourcePolicy_ANY_RESOURCE(),
    }),
})
dummyUserCredentials.Node().AddDependency(dummyUser)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The additional benefit of the AWS Custom Resources is that we already declared the behaviour for &lt;code&gt;cdk destroy&lt;/code&gt;: The deletion of the user.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 3: Successful Request
&lt;/h2&gt;

&lt;p&gt;Now that we have our own User Pool Client and User we can get a token ourselves: &lt;code&gt;aws cognito-idp initiate-auth --auth-flow USER_PASSWORD_AUTH --client-id $CLIENT_ID --auth-parameters USERNAME=dummy,PASSWORD=Password1!&lt;/code&gt; and perform a request: and finally get the correct result! Putting the same logic into code is similarly easy:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;consumer/main.go&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package main

import (
    "context"
    "fmt"
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider"
    "github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider/types"
    "github.com/aws/jsii-runtime-go"
    "io"
    "net/http"
    "os"
)

func main() {
    clientId := os.Getenv("CLIENT_ID")
    apiUrl := os.Getenv("API_URL")
    idToken := login(context.TODO(), clientId, "dummy", "Password1!")
    callApi(apiUrl + "/hello", idToken)
}

func login(ctx context.Context, clientId string, username string, password string) string {
    cfg, err := config.LoadDefaultConfig(ctx)
    if err != nil {
        panic(err)
    }

    cognitoClient := cognitoidentityprovider.NewFromConfig(cfg)

    initiateAuthOutput, err := cognitoClient.InitiateAuth(ctx, &amp;amp;cognitoidentityprovider.InitiateAuthInput{
        AuthFlow: types.AuthFlowTypeUserPasswordAuth,
        ClientId: jsii.String(clientId),
        AuthParameters: map[string]string{
            "USERNAME": username,
            "PASSWORD": password,
        },
    })
    if err != nil {
        panic(err)
    }

    return *initiateAuthOutput.AuthenticationResult.IdToken
}

func callApi(url string, idToken string) {
    request, err := http.NewRequest("GET", url, nil)
    if err != nil {
        panic(err)
    }

    request.Header.Add("Authorization", idToken)

    httpClient := &amp;amp;http.Client{}
    response, err := httpClient.Do(request)
    if err != nil {
        panic(err)
    }

    body, err := io.ReadAll(response.Body)
    if err != nil {
        panic(err)
    }

    fmt.Printf("Response: %s\n", body)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>aws</category>
      <category>cognito</category>
      <category>cdk</category>
      <category>go</category>
    </item>
  </channel>
</rss>
