gobitfly/eth2-beaconchain-explorer

API Authorization Issue: Inconsistent Authorization Mechanism Hinders Validator Node Automation

Closed this issue · 3 comments

Describe the bug
We are currently attempting to automate the process of adding validator nodes to our notifications center through the API.

The API call for this purpose (https://beaconcha.in/api/v1/user/validator/%7Bpubkey%7D/add) returns an error stating: "invalid_request" and "missing authorization header".

Upon checking the codebase, it's clear that the API requires an "Authorization" header, which contradicts the swagger docs which say that one should provide a header "apikey" containing the .

The API key can be provided in the Header or as a query string parameter. Key as a query string parameter: curl https://beaconcha.in/api/v1/slot/1?apikey=<your_key> Key in a request header: curl -H 'apikey: <your_key>' https://beaconcha.in/api/v1/slot/1

Once I provide a proper Authorization header, I no longer get asked for "missing authorization header" but then encounter a "unauthorized_client" and "authorization failed" error when running the API in postman.

To Reproduce
Steps to reproduce the behavior:

Attempt to make an API call to e.g 'https://beaconcha.in/api/v1/user/validator/123/add'
See the "invalid_request" and "missing authorization header" error
Add an "Authorization" header with a valid user API token to the request
Run the API call again
Encounter the "unauthorized_client" and "authorization failed" error

Expected behavior
We expected that adding the "Authorization" header with a valid user API token to the request would resolve the "missing authorization header" error and allow us to make the API call successfully. This would enable us to automate the process of adding validator nodes to our notifications center, as required for our use case.

Looking into the code, it sure does look like the code expects a proper JWT token in BEARER format for it to be able to validate:

func stripOffBearerFromToken(tokenString string) (string, error) {
	if len(tokenString) > 6 && strings.ToUpper(tokenString[0:6]) == "BEARER" {
		return tokenString[7:], nil
	}
	return tokenString, nil //"", errors.New("Only bearer tokens are supported, got: " + tokenString)
}

func accessTokenGetClaims(tokenStringFull string, validate bool) (*CustomClaims, error) {
	tokenString, err := stripOffBearerFromToken(tokenStringFull)
	if err != nil {
		return nil, err
	}

	token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
		return getSignKey()
	})

	if err != nil && validate {
		if !strings.Contains(err.Error(), "token is expired") && token != nil {
			logger.WithFields(
				logrus.Fields{
					"error":       err,
					"token":       token,
					"tokenString": tokenString,
				},
			).Warn("Error parsing jwt token")
		}

		return nil, err
	}

	if token == nil {
		return nil, fmt.Errorf("error token is not defined %v", tokenStringFull)
	}

	// Make sure header hasnt been tampered with
	if token.Method != signingMethod {
		return nil, errors.New("only SHA256hmac as signature method is allowed")
	}

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

	// Check issuer claim
	if claims.Issuer != Config.Frontend.JwtIssuer {
		return nil, errors.New("invalid issuer claim")
	}

	valid := ok && token.Valid

	if valid || !validate {
		return claims, nil
	}

	return nil, errors.New("token validity or claims cannot be verified")
}

This obviously would not work with the singular API key from the frontend which is not a JWT token.

Indeed going over the list in the Users section of the swagger documentation, none of the endpoints seem to work . All reproduce the same behavior. E.g. https://beaconcha.in/api/v1/docs/index.html#/User/get_api_v1_user_mobile_settings when authorized with the API key also fails.

I'm guessing the endpoint is made to authorize JWT tokens from the mobile app - so maybe a strategy is needed here for looking up a provided API key and generating a JWT token on the fly for the request?

Buttaa commented

Have to close this as described in discord. :(

After speaking with our developers, it seems that there was a misunderstanding regarding the availability of the /user/ endpoints. Currently, these endpoints cannot be used for non-app purposes, so the use case you discussed is not possible.
However, we are working on v2 and will consider adding this feature . Unfortunately, I cannot offer an immediate solution, and I apologize for any inconvenience this may have caused