[C#] JWT authorized request sends back 401 Unauthorized without further information

Hey,

really struggling to get the new Authorization to work…

I created my JWT and checked it with jwt.io. Header seems to be correct:

{
“alg”: “ES256”,
“kid”: “organizations/orgKey/apiKeys/tradeApiKey”, //the part called “name” in the json file
“typ”: “JWT”
}

Payload looks alright to me too:

{
“aud”: “retail_rest_api_proxy”,
“iss”: “coinbase-cloud”,
“exp”: 1709021479,
“iat”: 1709021359,
“nbf”: 1709021359,
“sub”: “organizations/orgKey/apiKeys/tradeApiKey”,
“uri”: “GET api.coinbase.com/api/v3/brokerage/accounts/:account_uuid”, //endpoint works with the same uuid in legacy auth
“nonce”: “4b8873969a8ca1ad8e07282512b3473955f9e98e382822b653890189b956e0f0”
}

When I add the public and private key to check the signature it shows Signature Verified.
Deconstructing and validating the JWT with the public key on my end also shows the same header and payload.

tokenHandler.ValidateToken(token, tokenValidationParameters, out var validatedToken);

does not throw any errors.
TokenValidationParameters:

var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = securityKey, // ECDsaSecurityKey from public key
ValidateIssuer = true,
ValidIssuer = “coinbase-cloud”,
ValidateAudience = true,
ValidAudience = “retail_rest_api_proxy”,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromSeconds(5)
};

Maybe the issue is the bearer header with the token? Implemented that like this:

client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(“Bearer”, jwtToken);

If anyone can spot a possible error any help would be highly appreciated! Getting back nothing more than Unauthorized is quite frustrating as debugging seems lowkey impossible.

Thanks for reading, have a nice day!

You don’t have nonce in your header!

1 Like

Oh, does it have to be in the header instead of the payload or both? Thanks already!

As far as I know it needs to be in header only.

1 Like

Thanks so much, lifesaver! Working as intended now.
Funny how i missed that, clearly visible in the header in both python and js >.<

Still wish there was a way to give some more information in the error response, shouldn’t be too hard to tell the user the nonce is missing in the header. Then again i guess that would make attacks easier too, if they literally get told what’s wrong about the jwt :smiley:

1 Like

I will happily enjoy some coffee, beer or some crypto if my help has been lifesaver. :slight_smile:

They definitely could say in error message that required fields are missing and probably other things that could help with debugging authorization issues. I doubt that it would affect security, without valid private key none will be able to generate valid JWT.

1 Like

Hmm, well here i am with that obnoxious Unauthorized error again. The JWT works for everything except move portfolio funds… which makes the rest utterly redundant.

I made sure my Default portfolio key has transfer permissions and double checked the source/target_portfolio_uuid’s. Can someone spot a potential error? Super confusing as the validation work with all other portfolio endpoints.

PrepareHttpRequest(client, HttpMethod.Post, Url + "/move_funds", sourceName); // URL: https://api.coinbase.com/api/v3/brokerage/portfolios
        
var jsonContent = $@"{{""funds"":{{""value"":""{value}"",""currency"":""{currency}""}},""source_portfolio_uuid"":""{sourceUuid}"",""target_portfolio_uuid"":""{targetUuid}""}}";
var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
        
var response = await client.PostAsync(Url + "/move_funds", content);

Thanks in advance!

In what direction you are trying to move funds? If you are trying to move funds from non-default portfolio then you need to use api key created for that portfolio and it also needs transfer permission!

Nope, i’m trying to move funds from the default portfolio to the newly created one. Using my default portfolio key with full permissions.

Are you generating JWT for /api/v3/brokerage/portfolios/move_funds? I guess you will need to post more complete code…

Oof, i’m stupid, good catch… i accidentally used the full url instead of only the request path.
Still getting an error that doesn’t really make sense - 429 Too many requests.

I’m only doing one ListPortfolios() for the uuid’s and then the MovePortfolioFunds().
Rate limit is specified as 10 per second per IP?

Guess this might be down to the server being overloaded or Coinbase having general issues (again)… can’t even create an order manually right now, what a convenience with heavy price swings :upside_down_face:

1 Like

Advanced Trade API private endpoints are throttled by user at 30 requests per second.

Advanced Trade API public endpoints are throttled by IP address at 10 requests per second

Yeah, works now, the server was overloaded so i couldn’t send any requests for a while. Thanks again!