Skip to content

CustomClaims with multiple audiences cannot be unmarshalled #79

Closed
@arawden

Description

@arawden

Description

I believe this is an isssue with the underlying jwt-go code, but I've been spending a lot of time struggling to make progress with this. I've updated my go modules and I'm currently on v1 of the jwt-middleware and v3.2.2 of the form3tech-oss/jwt-go packages

When presented with an audience which is an array, the library cannot unmarshal into the CustomClaims object and gives:
json: cannot unmarshal string into Go struct field CustomClaims.aud of type []string

Provide a clear and concise description of the issue, including what you expected to happen.

I believe the core issue lies in ParseWithClaims, but I can't say for sure

Reproduction

I'm mostly going off of the Auth0 Go Quickstart, and my code looks like this:
Middleware:

func (api API) SetupJWTMiddleware() *jwtmiddleware.JWTMiddleware {
	authJWTMiddleware := jwtmiddleware.New(jwtmiddleware.Options{
		ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
			checkAudience := token.Claims.(jwt.MapClaims).VerifyAudience(api.AuthConfig.Audience, false)
			if !checkAudience {
				return token, errors.New("could not verify token audience")
			}

			checkIssuer := token.Claims.(jwt.MapClaims).VerifyIssuer(api.AuthConfig.Issuer, false)
			if !checkIssuer {
				return token, errors.New("could not verify token issuer")
			}

			cert, err := api.getPemCert(token)
			if err != nil {
				panic(err)
			}

			result, _ := jwt.ParseRSAPublicKeyFromPEM([]byte(cert))

			return result, nil
		},
		SigningMethod: jwt.SigningMethodRS256,
	})

	return authJWTMiddleware
}

Authenticated request handler:

func (api API) AuthedEndpoint(apiEndpoint http.HandlerFunc) http.HandlerFunc {
	return func(response http.ResponseWriter, request *http.Request) {
		err := api.AuthMiddleware.CheckJWT(response, request)
		if err != nil {
			return // CheckJWT writes to response for us
		}

		token, err := jwtmiddleware.FromAuthHeader(request)

		if err != nil {
			http.Error(response, err.Error(), http.StatusForbidden)

			return
		}

		hasScope := api.checkScope("some-scope", token)
		if !hasScope {
			http.Error(response, "insufficient scope to access endpoint", http.StatusForbidden)

			return
		}

		apiEndpoint(response, request)
	}
}

checkScope:

func (api API) checkScope(scope string, tokenString string) bool {
	token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
		cert, err := api.getPemCert(token)

		if err != nil {
			return nil, err
		}

		result, _ := jwt.ParseRSAPublicKeyFromPEM([]byte(cert))

		return result, nil
	})

	if err != nil {
		fmt.Printf(err.Error())
	}

	claims, ok := token.Claims.(*CustomClaims)

	hasScope := false

	if ok && token.Valid {
		scopes := strings.Split(claims.Scope, " ")
		for index := range scopes {
			if scopes[index] == scope {
				hasScope = true
			}
		}
	}

	return hasScope
}

I ran into this issue when trying to hit authenticated endpoints with a token generated with the React useAuth0 hook, specifically getAccessTokenSilently, which gives me a token with:

 "aud": [
    "my.project.domain/api/",
    "https://project.us.auth0.com/userinfo"
  ],

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions