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!

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.