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
Top comments (0)