cognito-authorizer
cognito-authorizer copied to clipboard
Build your AWS API Gateway custom authorizer lambda without the need to handle tokens by yourself. Just implement the logic...
Cognito authorizer
A golang packages that abstract out work with JSON web access/identity tokens for AWS API Gateway custom authorizer.
These packages handle:
- access, id and standard tokens
- token verification
- token payload decrypting (claims)
- building proper responses from a custom authorizer
- a M2M token signer helper
You don't need to worry about JWT. The GetIDClaims, GetAccessClaims and GetStandardClaims will do the work for you, so you can focus only on building APIGatewayCustomAuthorizerPolicy.
Docs
About resource server context
You can pass a context created by your custom authorizer to the resource server. This is done by satisfying ContextBuilder interface. The method should return a map[string]interface{} (this is how AWS golang SDK works) but keys and values in this map have to be strings. More info here.
Example
Custom authorizer main package
package main
import (
"context"
"fmt"
"os"
"strings"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
log "github.com/sirupsen/logrus"
cognitoAuthorizer "github.com/nordcloud/cognito-authorizer/pkg/authorizer"
)
type PolicyEffect string
const (
allow PolicyEffect = "allow"
deny PolicyEffect = "deny"
)
type Policy struct {
Context *cognitoAuthorizer.Context
}
func (b Policy) BuildPolicy(encodedToken string) (events.APIGatewayCustomAuthorizerPolicy, error) {
accessClaims := &cognitoAuthorizer.AccessTokenClaims{}
err := cognitoAuthorizer.GetAccessClaims(encodedToken, b.Context.DecryptionKeys, accessClaims)
if err != nil {
return events.APIGatewayCustomAuthorizerPolicy{}, err
}
resources := []string{
fmt.Sprintf(
"arn:aws:execute-api:%s:*:%s/%s/*/*",
b.Context.Region,
b.Context.ApplicationID,
b.Context.Stage,
),
}
policy := events.APIGatewayCustomAuthorizerPolicy{
Version: "2012-10-17",
Statement: []events.IAMPolicyStatement{
{
Action: []string{"execute-api:Invoke"},
Effect: string(allow),
Resource: resources,
},
},
}
return policy, nil
}
func (b Policy) BuildContext(encodedToken string) (map[string]interface{}, error) {
accessClaims := &cognitoAuthorizer.AccessTokenClaims{}
err := cognitoAuthorizer.GetAccessClaims(encodedToken, b.Context.DecryptionKeys, accessClaims)
if err != nil {
return map[string]interface{}{}, err
}
context := map[string]interface{}{
"token_scope": accessClaims.Scope,
}
return context, nil
}
var (
sharedContext *cognitoAuthorizer.Context
)
// Init is called on lambda cold start. In this function we pull Cognito keys to verify tokens.
func init() {
sharedContext = &cognitoAuthorizer.Context{
Region: os.Getenv("REGION"),
ApplicationID: os.Getenv("API_APPLICATION_ID"),
Stage: os.Getenv("API_STAGE"),
AllowedUserPoolID: os.Getenv("API_ALLOWED_USER_POOL_ID"),
CognitoClients: strings.Split(os.Getenv("COGNITO_CLIENTS"), ","),
DecryptionKeys: nil,
}
keys, err := cognitoAuthorizer.GetDecryptionKeys(sharedContext.Region, sharedContext.AllowedUserPoolID)
if err != nil {
log.WithField("error", err).Error("Unable to get decryption keys.")
}
sharedContext.DecryptionKeys = keys
log.WithFields(log.Fields{
"region": sharedContext.Region,
"application_id": sharedContext.ApplicationID,
"stage": sharedContext.Stage,
"allowed_user_pool_id": sharedContext.AllowedUserPoolID,
"cognito_clients": sharedContext.CognitoClients,
}).Info("Finished initialization.")
}
func handler(ctx context.Context, event events.APIGatewayCustomAuthorizerRequest) (
events.APIGatewayCustomAuthorizerResponse, error) {
log.WithField("method_arn", event.MethodArn).Info("Authorizer lambda invoked.")
policy := &Policy{
Context: sharedContext,
}
responseBuilder := &cognitoAuthorizer.ResponseBuilder{
Context: sharedContext,
PolicyBuilder: policy,
ContextBuilder: policy,
}
return responseBuilder.BuildResponse(event.AuthorizationToken)
}
func main() {
lambda.Start(handler)
}
Authors
- Grzegorz Bednarski, Nordcloud 🇵🇱
- Kamil Piotrowski, Nordcloud 🇵🇱
- Artur Kowalski, Nordcloud 🇵🇱