Roberto Guerra
Posted on January 28, 2020
I found myself migrating a serverless service written in node to Go
, and needed to write a lambda authorizer. An authorizer is simply an AWS Lambda that the API Gateway will invoke to verify if the request is authorized.
The AWS Go SDK has a type for the API Gateway Request that the authorizer will receive. The authorizer I need is very simple for now. It will only concern itself with validating the JWT token. This token is represented by AuthorizationToken
in the APIGatewayCustomAuthorizeRequest
.
type APIGatewayCustomAuthorizerRequest struct {
Type string `json:"type"`
AuthorizationToken string `json:"authorizationToken"`
MethodArn string `json:"methodArn"`
}
I looked around for a library that I could leverage and found github.com/apibillme/auth0
. However, it didn't quite suit my use case. The function signature for validating a token is:
Validate(jwkURL, audience, issuer string, req *http.Request) (*jwt.Token, error)
The main obstacle to me using it is that the authorizer won't be receiving an http.Request
. I needed a function where the token was a string:
Validate(jwkURL, audience, issuer, jwtToken string) (*jwt.Token, error)
So I cloned the repo and made the changes I needed. Since the most recent activity in that repo was over a year ago, I figured that it was being actively maintained. But I still found some value in the code, so I decided to fork it and publish my own version.
The other interesting feature of this code is that it also includes a cache. The tokens are stored in the cache and for every request, the cache is inspected, and if the token is found in the cache, then we don't need to re-validate that token. So, there was one minor update I decided to make. The original code base requires that a cache be created explicitly before we can validate a token:
New(128, 5)
Validate(jwkEndpoint, audience, issuer, ctx)
I was confused initially because I didn't know what New
was doing. So, I created an Auth0
type, along with a constructor:
type Auth0 struct {
cache cache.Cache
}
// Create a new Auth0 client.
// keyCapacity indicates the maximum number of keys the Cache will hold.
// ttl is the time to live in seconds for keys to live in the Cache.
func NewAuth0(keyCapacity int, ttl int64) Auth0 {
globalTTL := time.Duration(ttl)
Cached := cache.New(keyCapacity, cache.WithTTL(globalTTL*time.Second))
return Auth0{cache: Cached}
}
// Validate - validate with JWK & JWT Auth0 & audience & issuer for net/http
func (a Auth0) Validate(jwkURL, audience, issuer, jwtToken string) (*jwt.Token, error) {
// process token
tokenParts := strings.Split(jwtToken, " ")
token, err := verifyBearerToken(tokenParts)
if err != nil {
return nil, err
}
return a.processToken(token, jwkURL, audience, issuer)
}
Now using it is pretty simple:
func handler(ctx Context, req APIGatewayCustomAuthorizerRequest) APIGatewayCustomAuthorizerResponse {
//Retrieve the issuer, audience and the JWKS_URI from the environment.
token := req.AuthorizationToken
auth0 := NewAuth0(128, 60)
jwtToken, err := auth0.Validate(JWKS_URI, audience, issuer, token)
// Use the jwtToken to create the response
}
I'm still trying to figure out how to unit test this library. My main struggle is creating a token and a key for the tests using Go. I'll write down my notes for the next blog post, before documenting how to eventually create the APIGatewayCustomAuthorizerResponse
Posted on January 28, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.