Calling IAM-secured Lambda function urls with Amplify and javascript

Nathan Hanks
4 min readApr 27, 2024
Photo by JESHOOTS.COM on Unsplash

Maybe my Googling skills are not what they should be, or I just couldn’t work my GitHub Copilot magic, but you would think there would be readily available documentation for this. Since I couldn’t find it easily, I decided to create it. It turns out that its not that hard, and as usual, Amplify makes the hard part “easy”.

The Lambda Function

Starting with the Lambda function, I will hit the pertinent points to consider, and my recommendations on implementing Lambda function URLs.

Security Model for Lamba Function URL

There are 2 options for securing lambda function URLs and there is plenty of documentation so I don’t intend to cover that here. For reference see the AWS documentation.

Powertools for Lambda

Next, and this is my (strong?) opinion, I almost always recommend using use powertools for lambda. There are many reasons that I like using powertools, so to start this conversation let’s first look at a code snippet (from the powertools documentation):

import requests
from requests import Response
from aws_lambda_powertools import Logger, Tracer
from aws_lambda_powertools.event_handler import LambdaFunctionUrlResolver
from aws_lambda_powertools.logging import correlation_paths
from aws_lambda_powertools.utilities.typing import LambdaContext

logger = Logger()
app = LambdaFunctionUrlResolver()

@app.get("/todos")
def get_todos():
todos: Response = requests.get("https://jsonplaceholder.typicode.com/todos")
todos.raise_for_status()

# for brevity, we'll limit to the first 10 only
return {"todos": todos.json()[:10]}


# You can continue to use other utilities just as before
@logger.inject_lambda_context(log_event=True, correlation_id_path=correlation_paths.LAMBDA_FUNCTION_URL)
def lambda_handler(event: dict, context: LambdaContext) -> dict:
return app.resolve(event, context)

Some quick callouts:

  • the logging capability is worth investigating. Notice the log_event=True parameter on the lambda_handler — this logs the event incoming event to CloudWatch, so if you have questions about what your client is sending, this helps you resolve those questions. I always enable this and it saves me large amounts of time, every time.
  • Next, and most important, note the LambdaFunctionUrlResolver() object. I’ll simply point you to the documentation. For those familiar with Flask, this probably looks familiar.
  • Last, notice the correlation_id_path=correlation_paths.LAMBDA_FUNCTION_URL parameter on the lambda_handler decorator. Similarly, refer to the documentation for explanation and details.

Configuring your Lambda Function Url

I didn’t find this documented anywhere, except StackOverflow, but this was critical, or else I kept getting errors:

For each header sent by your client, make sure that you add that header to the “Allow Headers” section of your Lambda Function Url configuration. Here are the headers I had to add for this to work:

  • And at the very bottom of the configuration section, toggle ON the “Allow Credentials” toggle button, since you will be passing that information in.
  • You probably are going to also need to modify the CORS configuration, so do so accordingly. Your requirements will vary so I will leave that to you instead of give prescriptive advice.

Now the javascript function:

First, import the aws javascript v3 sdk. Note that this SDK is modular so you import the necessary modules. Notice a couple of points:

  • most of the examples online will show the defaultProvider object — you do NOT need that when using Amplify, which I will show shortly.
import { fetchAuthSession } from 'aws-amplify/auth'
import { Sha256 } from '@aws-crypto/sha256-js'
import { HttpRequest } from '@aws-sdk/protocol-http'
import { SignatureV4 } from '@aws-sdk/signature-v4'
//import { defaultProvider } from '@aws-sdk/credential-provider-node'

Next, probably in an async function, you need to call fetchAuthSession, which is part of the Amplify Auth category

const { credentials } = await fetchAuthSession()

Next, setup your HttpRequest object (nothing should need to be called out here):

let lambdaUrl = 'https://thepathtoyour.lambda-url.yourregion.on.aws/someresource'
const url = new URL(lambdaUrl)

// set up the HTTP request
const request = new HttpRequest({
hostname: url.hostname,
path: url.pathname,
body: JSON.stringify(someJson),
method: 'POST',
headers: {
'Content-Type': 'application/json',
host: url.hostname
}
})

Next, sign your HttpRequest with the SignatureV4 object. The most important thing to notice here is the credentials parameter — this is just the return value from fetchAuthSession, called earlier. That is the secret to this whole thing, imo.

// create a signer object with the credentials, the service name and the region
const signer = new SignatureV4({
credentials: credentials,
service: 'lambda',
region: 'us-east-2,
sha256: Sha256
})

// sign the request and extract the signed headers, body and method
const { headers, body, method } = await signer.sign(request)

Last, but not least, call fetch:

// send the signed request and extract the response as JSON
fetch(lambdaUrl, {
headers,
body,
method
})
.then((res) => {
return res.json()
})
.then((json) => {
logger.info('fetch report succeeded: ')
//do something with the returned json result
})
.catch((e) => {
logger.error('fetch failed: ', e.response)
})
} catch (e) {
logger.error('call failed: ', e.response)
}

Summary

And that’s it. I’ve never had to use the Signature V4 package before and I couldn’t find any documentation on combining Amplify with SigV4. And interestingly enough, there is not a tremendous amount of documentation on calling lambda function urls from javascript (Vue) front ends, especially using the v3 aws sdk.

So hopefully this helps you and fills that gap.

If you know of a better/easier way to do this, drop me your comments and/or questions. Thanks!

--

--

Nathan Hanks

I like to talk and think about complex problems, in the domains of data science, software engineering, innovation, and CrossFit (yes, I’m that guy).