Go-jwt-middleware CustomClaims validation type failure

I have a Go backend that uses github user zett-8’s go-clean-echo as a template. Their template uses a middleware explicitly using auth0. I have a React frontend that properly uses auth0 for login and for getting JWT tokens to interact with the backend, which uses an auth0 API in the jwt middleware.

I am attempting to use logic from the example, though in an Echo-y way. So, I am having problems extracting the CustomClaims.

The middleware is as follows:

package middlewares

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"os"
	"strings"
	"time"

	"tlmgateway/logger"

	"github.com/auth0/go-jwt-middleware/v2/jwks"
	"github.com/auth0/go-jwt-middleware/v2/validator"
	"github.com/labstack/echo/v4"
	"go.uber.org/zap"
	// "github.com/zett-8/go-clean-echo/configs"
)

// CustomClaims contains custom data we want from the token.
type CustomClaims struct {
	Scope       string   `json:"scope" form:"scope"`
	Permissions []string `json:"permissions" form:"permissions"`
}

// Validate does nothing for this example, but we need
// it to satisfy validator.CustomClaims interface.
func (c CustomClaims) Validate(ctx context.Context) error {
	return nil
}

func JwtMiddleware() (echo.MiddlewareFunc, error) {
	issuerURL, err := url.Parse("https://" + os.Getenv("AUTH0_DOMAIN") + "/")
	if err != nil {
		log.Fatalf("Failed to parse the issuer url: %v", err)
	}

	provider := jwks.NewCachingProvider(issuerURL, 5*time.Minute)

	jwtValidator, err := validator.New(
		provider.KeyFunc,
		validator.RS256,
		issuerURL.String(),
		[]string{os.Getenv("AUTH0_AUDIENCE")},
		validator.WithCustomClaims(
			func() validator.CustomClaims {
				return &CustomClaims{}
			},
		),
		validator.WithAllowedClockSkew(time.Minute),
	)
	if err != nil {
		log.Fatalf("Failed to set up the jwt validator")
	}

	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {

			authorization := c.Request().Header.Get("Authorization")
			if authorization == "" {
				return echo.NewHTTPError(http.StatusUnauthorized, "No Authorization Header")
			}

			if !strings.HasPrefix(authorization, "Bearer ") {
				return echo.NewHTTPError(http.StatusUnauthorized, "Invalid Authorization Header")
			}

			token := strings.TrimPrefix(authorization, "Bearer ")

			claims, err := jwtValidator.ValidateToken(c.Request().Context(), token)
			if err != nil {
				logger.Error("Invalid Token: ", zap.Error(err))
				return echo.NewHTTPError(http.StatusUnauthorized, "Invalid Token")
			}

			c.Set("claims", claims.(*validator.ValidatedClaims))
			c.Set("customClaims", claims.(*validator.CustomClaims))

			return next(c)
		}
	}, nil
}

// HasScope checks whether our claims have a specific scope.
func (c CustomClaims) HasScope(expectedScope string) bool {
	result := strings.Split(c.Scope, " ")
	for i := range result {
		if result[i] == expectedScope {
			return true
		}
	}

	return false
}

// HasPermission checks whether our claims have a specific permission.
func (c CustomClaims) HasPermission(expectedPermission string) bool {
	for _, permission := range c.Permissions {
		if permission == expectedPermission {
			return true
		}
	}

	return false
}

When the API using this middleware is hit, I get the following error:

interface conversion: interface {} is *validator.ValidatedClaims, not *validator.CustomClaims

Which is perplexing, given that the type of *validator.CustomClaims should theoretically indeed be of type CustomClaims.

Do I need to actually implement Validate properly, unlike what the comments say in the auth0 example (copied over to my code)? How would I get ValidateToken to give back a CustomClaims struct somewhere?

OP here. Solution was to instead use the auth0 example almost line-for-line, but instead of using the EnsureValidToken() handler function as middleware (which doesn’t play nice with Echo), to simply wrap EnsureValidToken() in Echo’s very useful echo.WrapMiddleware function.

func EchoEnsureValidToken() (echo.MiddlewareFunc, error) {
	mw := echo.WrapMiddleware(EnsureValidToken())
	return mw, nil
}

And simply use that middleware for an endpoint or group or the entire set of handlers.

1 Like

Hey @mbonnet glad you were able to get this sorted, and thanks for following up with the solution!