Skip to content

Instantly share code, notes, and snippets.

@chris
Created June 4, 2021 19:23
Show Gist options
  • Save chris/a879ce7012e38cd91888676fb423e9b1 to your computer and use it in GitHub Desktop.
Save chris/a879ce7012e38cd91888676fb423e9b1 to your computer and use it in GitHub Desktop.
Example of an API Gateway Lambda authorizer
package main
import (
"context"
"errors"
"regexp"
"strings"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
const (
UserIDHeader = "x-user-id"
UserTokenHeader = "x-user-token"
)
var (
// allowedResourceEndpoints is a list of the API Gateway ARN suffix parts that
// this authorizer allows. See `allowedResource` below.
allowedResourceEndpoints = []string{
"/POST/things",
"/GET/things",
"/GET/someotherthing",
"/PUT/users",
"/DELETE/users",
}
)
func validUserToken(userID, userToken string) bool {
user, err := repo.GetUser(userID)
if err != nil || user == nil {
return false
}
return user.AccessToken == userToken
}
// allowedResources returns the list of allowed resources for the policy document
// so that we authorize the user for all (user authorized) API's in this
// system with a single call. We use wildcards so that we don't have to do any
// environment (i.e. dev vs. prod) handling. We list all these resources, because
// API Gateway caches this policy, so if we use the exact methodArn of the HTTP
// call, it winds up then blocking that same user from calling other APIs because
// the policy is cached with just the first API call's methodArn.
// See:
// https://forum.serverless.com/t/help-really-weird-access-denied-issue/12676/6
// This function strips off everything after the first encountered slash, where
// the beginning part is the API Gateway API ID, and then adds the list of
// endpoints that this particular authorizer is used for, with a wildcard for
// the account/environment (dev vs. prod) part. e.g. a methodArn of:
// arn:aws:execute-api:us-east-1:123456789000:jol83sbu0b/dev/POST/things
// will be converted to:
// arn:aws:execute-api:us-east-1:123456789000:jol83sbu0b/*/POST/things
// (as well as the other API endpoints this authorizer pertains to).
func allowedResources(methodArn string) (resources []string) {
parts := strings.Split(methodArn, "/")
prefix := parts[0] + "/*"
for _, endpoint := range allowedResourceEndpoints {
resources = append(resources, prefix+endpoint)
}
return
}
func generatePolicy(principalID, effect string, resources []string) events.APIGatewayCustomAuthorizerResponse {
authResponse := events.APIGatewayCustomAuthorizerResponse{PrincipalID: principalID}
if effect != "" && len(resources) != 0 {
authResponse.PolicyDocument = events.APIGatewayCustomAuthorizerPolicy{
Version: "2012-10-17",
Statement: []events.IAMPolicyStatement{
{
Action: []string{"execute-api:Invoke"},
Effect: effect,
Resource: resources,
},
},
}
}
return authResponse
}
func handler(ctx context.Context, request events.APIGatewayCustomAuthorizerRequestTypeRequest) (events.APIGatewayCustomAuthorizerResponse, error) {
if validUserToken(request.Headers[UserIDHeader], request.Headers[UserTokenHeader]) {
return generatePolicy("user", "Allow", allowedResources(request.MethodArn)), nil
}
return events.APIGatewayCustomAuthorizerResponse{}, errors.New("Unauthorized")
}
func main() {
lambda.Start(handler)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment