DEV Community

Abhishek Gupta for AWS

Posted on • Updated on • Originally published at abhishek1987.Medium

Using AWS Lambda Function URL to build a Serverless backend for Slack

A combination of AWS Lambda and Amazon API Gateway is a widely-used architecture for serverless microservices and API based solutions. They enable developers to focus on their applications, instead of spending time provisioning and managing servers.

API Gateway is a feature rich offering that includes with support for different API types (HTTP, REST, WebSocket), multiple authentication schemes, API versioning, canary deployments and much more! However, if your requirements are simpler and all you need is an HTTP(S) endpoint for your Lambda function (for example, to serve as a webhook), you can use Lambda Function URLs! When you create a function URL, Lambda automatically generates a unique HTTP(S) endpoint that is dedicated for your Lambda function.

This blog post demonstrates how to use Lambda function URL with a practical example. You will build a Go Lambda function to serve as a serverless webhook backend for Slack.

It's a step-by-step guide that covers:

  • Overview of the application
  • Configure and deploy the function (along with some gotchas you need to watch out for!)
  • How to configure Slack to enable the end to end integration
  • Test the app and have fun!

By the end of this blog, you would have configured, integrated and deployed a useful (and hopefully fun?) app using Lambda function URL. In the process, you will get an overview of this feature that you can utilise when building your own solutions!

The code is available on GitHub

How it works

The sample app presented in this blog is a trimmed down version of Giphy for Slack. The (original) Giphy Slack app returns a bunch of GIFs for a search term and the user can pick one of them. To keep things simple, I've tweaked things a bit such that the serverless backend simply returns a (single) random image for a search keyword using the Giphy Random API.

Since the solution will be integrated as a Slash Command in Slack, the end user (you!) will invoke it from a Slack workspace using /awsome <your search term> (where awsome is nothing but the name of the slash command). This in turn invokes the Lambda function URL (the configuration is covered later in the blog), which takes care of the rest.

For example, invoking it from your Slack workspace using /awsome serverless will return a random GIF (you will try this later!)

Here is an overview of what the Lambda function does:

  • Slack slash command invocation results in a base64 encoded string payload being sent to the Lambda function URL - so the first step is to decode it.
  • The function is only supposed to invoked by Slack and we need to make sure we confirm that. Slack makes this possible by allowing apps to verify requests using a signing secret - the function simply implements a Go version of the signature matching recipe presented here
  • If the signature match is successful (we return an error to the client if it fails), the Slack request is parsed to extract the search text that user sent.
  • Then, the Giphy Random API is invoked with the search term. If we get a successful response, we parse it and send it back to Slack in it's desired format

Finally, the user gets to see a GIF in their Slack workspace!

I will skip the code walk-through in order to focus on other aspects of the solution, but the function signature deserves a mention - it is similar to what you would've used in case of an API Gateway based solution:

func Funcy(r events.LambdaFunctionURLRequest) (events.LambdaFunctionURLResponse, error) {
    ...
}
Enter fullscreen mode Exit fullscreen mode

We are using events.LambdaFunctionURLRequest as input and returning events.LambdaFunctionURLResponse. Behind the scenes, Lambda maps the request to an event object before passing it to the function. Finally, the function response is then mapped to an HTTP response that Lambda sends back to the client through the function URL.

You can read up on the details in the documentation

That's quite convenient right? You can use API Gateway conventions without actually having to setup and configure one!

With that background info, let's move on to the part where you deploy the function and try it out with Slack. But, before that make sure you have the following ready:

Pre-requisites

Please note down your GIPHY API key as you will be using it later

Clone the Github repo and move into the right directory:

git clone https://github.com/abhirockzz/awsome-slack-backend
cd awsome-slack-backend/function
Enter fullscreen mode Exit fullscreen mode

The subsequent steps use AWS CLI - I've purposely used the AWS CLI in order to highlight specific aspects of the process. Please check this tutorial for CloudFormation and SAM

Build, zip and deploy the function!

export FUNC_NAME=awsome-slack-backend
export FUNC_GO_BINARY_NAME=awsome
export ZIP_NAME=function.zip

GOOS=linux go build -o $FUNC_GO_BINARY_NAME main.go

zip function.zip $FUNC_GO_BINARY_NAME
Enter fullscreen mode Exit fullscreen mode

First, create an IAM Role for Lambda and attach the AWSLambdaBasicExecutionRole policy:

export ROLE_NAME=demo-lambda-role

ROLE_ARN=$(aws iam create-role \
        --role-name $ROLE_NAME \
        --assume-role-policy-document '{"Version": "2012-10-17","Statement": [{ "Effect": "Allow", "Principal": {"Service": "lambda.amazonaws.com"}, "Action": "sts:AssumeRole"}]}' \
        --query 'Role.[Arn]' --output text)

aws iam attach-role-policy --role-name $ROLE_NAME --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Enter fullscreen mode Exit fullscreen mode

Create the function:

aws lambda create-function \
    --function-name $FUNC_NAME \
    --runtime go1.x \
    --zip-file fileb://$ZIP_NAME \
    --handler $FUNC_GO_BINARY_NAME \
    --role $ROLE_ARN
Enter fullscreen mode Exit fullscreen mode

After the function gets created, go ahead and add the Function URL:

aws lambda create-function-url-config \
    --function-name $FUNC_NAME \
    --auth-type NONE
Enter fullscreen mode Exit fullscreen mode

For the purposes of this sample app, we're using NONE as the authentication type. This means that the Lambda function URL will be publicly accessible - more on this shortly

If you navigate to the AWS console and open the function you just created, you should see the Function URL associated with it:

Image description

Let's invoke the function - copy the Function URL and paste it in a browser or use any other tool (e.g. curl)

curl -i <FUNCTION_URL>
Enter fullscreen mode Exit fullscreen mode

You should get a {"Message":"Forbidden"} response with a HTTP 403 Forbidden status code

Don't worry, this is expected - I wanted to make sure you encounter this issue and understand the root cause.

Even though we use NONE as the authentication scheme, users must still have lambda:InvokeFunctionUrl permissions in order to successfully invoke the function URL. The (slightly) tricky bit is that when you create a function URL (with auth type NONE) via the console or AWS Serverless Application Model (AWS SAM), Lambda automatically creates the resource-based policy statement for you (details in the documentation). That's not the case if you're using the AWS CLI (as in this blog), AWS CloudFormation, or the Lambda API directly - you must add permissions yourself.

Let's do that:

aws lambda add-permission \
    --function-name $FUNC_NAME \
    --action lambda:InvokeFunctionUrl \
    --statement-id FunctionURLAllowPublicAccess \
    --principal "*" \
    --function-url-auth-type NONE
Enter fullscreen mode Exit fullscreen mode

To see the policy, navigate to your Function in the AWS console: Configuration > Permissions

Invoke the function again:

curl -i <FUNCTION_URL>
Enter fullscreen mode Exit fullscreen mode

This time, you will get a different error with a HTTP 401 Unauthorized status code. This is expected as well!

Let's finish the rest of the configuration to get things working.

Configure Slack

Please note that most of the instructions in this section have been adapted from the Slack documentation

Start by signing into your Slack Workspace and creating a new Slack App.

Once that's done, create a Slash Command - head to your app's settings page, and then click the Slash Commands feature in the navigation menu. You'll be presented with a button marked Create New Command, and when you click on it, you'll see a screen where you'll be asked to define your new Slash Command with the required information.

Enter the required information - Enter /awsome for the Command and enter the Lambda Function URL in Request URL

Image description

Finally, install the app to your workspace - click the Basic Information feature in the navigation menu, choose Install your app to your workspace and click Install App to Workspace. This will install the app to your Slack workspace to test your app and generate the tokens you need to interact with the Slack API.

As soon as you finish installing the app, the App Credentials will show up on the same page. You need to grab your Slack Signing Secret from there

Make a note of your app Signing Secret as you'll be using it later

Image description

Update the function

Now that you've the Slack signing secret key, you need to make sure to configure it in the function as well. Also, don't forget the GIPHY API key since the function needs that to invoke GIPHY REST endpoint.

Let's update the function to include these as environment variables:

aws lambda update-function-configuration \
    --function-name $FUNC_NAME \
    --environment "Variables={SLACK_SIGNING_SECRET=<enter Slack signing secret>,GIPHY_API_KEY=<enter Giphy API key>}"
Enter fullscreen mode Exit fullscreen mode

The sample app uses Lambda environment variables to store keys for Slack and GIPHY - this is just for demonstration purposes. You should use a solution such as AWS Secrets Manager to securely store and manage credentials.

You're all set!

Head over to your Slack workspace and invoke the command. For example, to get a random cat GIF, just type:

/awsome cat
Enter fullscreen mode Exit fullscreen mode

I got this response. How about you? :-)

Image description

Feel free to play around with the app!

Clean up

Once you're done, delete the function along with the IAM policy and role.

aws lambda delete-function --function-name $FUNC_NAME

aws iam detach-role-policy  --role-name $ROLE_NAME --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

aws iam delete-role --role-name $ROLE_NAME
Enter fullscreen mode Exit fullscreen mode

That's all for now!

You configured and deployed a serverless backend for Slack and in the process, learnt about some of the aspects of Lambda Function URLs through the lens of this sample app. I would encourage you to explore other capabilities such as AWS_IAM authentication, CORS config, throttling limits, monitoring etc.

Happy coding!

Discussion (0)