I was trying to add JWT authentication to an AWS web socket endpoint and it wasn’t working. My problem was simple and it was even spelled out mentioned in passing somewhere in the AWS docs and it still took me a long time to figure it out. The answer is that only request style authenticators are supported, not token. The docs say that here in that it doesn’t mention a token authorizer and explicitly states that only request is supported here. This post also makes the same point.
In Serverless the configuration for the authorizer looks like this:
functions:
websocketAuthorizer:
handler: bin/websocket_authorizer
package:
include:
- ./bin/websocket_authorizer
connectionHandler:
handler: bin/connection_handler
events:
- websocket:
route: $connect
authorizer:
name: websocketAuthorizer
identitySource: 'route.request.header.Authorization'
package:
include:
- ./bin/connection_handler
Then a Golang version of the authorizer would look something like this:
package main
import (
"context"
"errors"
"os"
"strings"
"time"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
// note the APIGatewayCustomAuthorizerRequestTypeRequest that must
// be the type of the request parameter for Websocket authorizers
func handler(ctx context.Context, request events.APIGatewayCustomAuthorizerRequestTypeRequest) (events.APIGatewayCustomAuthorizerResponse, error) {
token := request.Headers["Authorization"]
tokenSlice := strings.Split(token, " ")
var bearerToken string
if len(tokenSlice) > 1 {
bearerToken = tokenSlice[len(tokenSlice)-1]
} else {
return events.APIGatewayCustomAuthorizerResponse{},
errors.New("Unauthorized")
}
err := authorizeToken( bearerToken )
if nil != err {
return events.APIGatewayCustomAuthorizerResponse{}, errors.New("Unauthorized")
}
return generatePolicy(), nil
}
func generatePolicy() events.APIGatewayCustomAuthorizerResponse {
authResponse := events.APIGatewayCustomAuthorizerResponse{PrincipalID: "principal"}
authResponse.PolicyDocument = events.APIGatewayCustomAuthorizerPolicy{
Version: "2012-10-17",
Statement: []events.IAMPolicyStatement{
{
Action: []string{"execute-api:Invoke"},
Effect: "allow",
Resource: []string{"*"},
},
},
}
return authResponse
}
func authorizeToken( token string ) error {
// do something to authorizer the token here
return nil
}
func main() {
lambda.Start(handler)
}
Note that the Serverless declaration doesn’t have a type
parameter, unlike for REST APIs, that’s the first hint. In the Go code the parameter type is the excellently named APIGatewayCustomAuthorizerRequestTypeRequest
. If instead you try to use an APIGatewayCustomAuthorizerRequest
it will be invoked properly but the AuthorizationToken
property will not be populated with your token. That’s why I’m retrieving the token explicitly from request.Headers["Authorization"]
. One of the things that confused me was the IdentitySource
in the configuration for the authorizer that can be used to reference the header, but it is either ignored or used just for pre-validation of the request.