JWT Invalid Signatures in .NET libraries - c#

I am currently struggling with creating signatures with any .net library to access Twitch api endpoints. The jwt.io page always says the signature is invalid when I copy paste my generated jwt there. When I edit the secret the signature is obviously adjusted and when I use the adjusted jwt I can make the Twitch api call without a problem. When I use my generated jwt I always end up with a 401 authentication failed.
I tried pretty much all of the .net libraries now and all of them generate a wrong signature for HS256… I am not sure what I do wrong since I am new to JWT and couldn’t find anything that points me to the right direction. Here is my code to generate the jwt using the jwt.net library (https://github.com/jwt-dotnet/jwt). Would highly appreciate if someone could help me with this.
var payload = new Dictionary<string, object>
{
{ "exp", DateTimeOffset.Now.AddSeconds(360).ToUnixTimeSeconds()},
{ "user_id", "668312333"},
{ "role", "external"},
};
var secret = Encoding.UTF8.GetString(Convert.FromBase64String("MY SHARED EXTENSION SECRET FROM TWITCH"));
IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); // symmetric
IJsonSerializer serializer = new JsonNetSerializer();
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
var token = encoder.Encode(payload, secret);

you need to set the secret to be a byte array as that is what is needed by the underlying library :-
var secret = Convert.FromBase64String("MY SHARED EXTENSION SECRET FROM TWITCH");

Related

How to pass an integer as the Claim value when creating a new System.Security.Claim in C# (.NET6)?

I'm trying to create a JWT token by signing it with a private key stored in an Azure key vault. The code I have works fine and it generates the token, but the token just wasn't working with the API I'm trying to log into. Generating the token via Python worked fine. I used jwt.io to decode the payload data and compared the tokens generated via C# and Python. I noticed that that the Issued At claim ("iat") is not surrounded by double-quotes in the Python generated token, but it had the quotes in the other one.
Is there a way to create a System.Security.Claim with an integer as the claim value? I tried including "Integer64" as the ValueType parameter, but it didn't work. In the code below I'm simply removing the quotes around the "iat" claim value and it works. Is this what I'm supposed to do?
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.Now.ToUnixTimeSeconds().ToString(), "Integer64")
};
var payload = JsonSerializer.Serialize(new JwtPayload(claims));
payload = payload.Replace("iat\":\"", "iat\":").Replace("\"}", "}");
var headerAndPayload = $"{Base64UrlEncoder.Encode(header)}.{Base64UrlEncoder.Encode(payload)}";
Here is what the API expects (no double-quotes):
jwt.io decoded payload
In C# I tried removing the double-quotes around the "iat" claim value and it worked. This wasn't necessary when using jwt.encode in Python.
A better approach is to use the build in .NET Libraries to generate your JWT tokens, just to make sure you get everything right.
There are two different libraries in .NET for generating tokens and you can read about this here:
Why we have two classes for JWT tokens JwtSecurityTokenHandler vs JsonWebTokenHandler?
Here's a resource to explore for manually creating a token:
https://www.c-sharpcorner.com/article/jwt-validation-and-authorization-in-net-5-0/

Azure App Config HMAC authentication returns Unauthorized error

I'm following this tutorial on making a REST call to Azure App Config to add a key-value entry using HMAC authentication. Both the JS and c# versions don't work - i end up with a 401 Unauthorized error saying
HMAC-SHA256 error="invalid_token", error_description="Invalid Signature"
My calling code for the C# implementation is:
var credential = "<my app config id>";
var secret = Encoding.ASCII.GetBytes(Base64Encode("<my app config secret>"));
UserQuery.MsgRouterConfigValue body = new MsgRouterConfigValue();
var key = "asd1";
body.clientId = Guid.NewGuid();
body.serviceUrl = new Uri("https://someuri");
using (var client = new HttpClient())
{
var request = new HttpRequestMessage()
{
RequestUri = new Uri($"<my app config url>/kv/{key}?label=dev&api-version=1.0"),
Method = HttpMethod.Put,
Content = new StringContent(JsonSerializer.Serialize(body))
};
Sign(request,credential, secret);
var resp = await client.SendAsync(request);
}
public static string Base64Encode(string plainText)
{
var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
return System.Convert.ToBase64String(plainTextBytes);
}
With regards to the values i'm using for credential and secret, they are from the portal for app config:
Can anyone advise why i'm getting a 401 error ?
EDIT:
So this error is down to:
Reason: The Signature provided doesn't match what the server expects.
Solution: Make sure the String-To-Sign is correct. Make sure the
Secret is correct and properly used (base64 decoded prior to using).
So in terms of the 'Make sure the String-To-Sign is correct' i'm using the same code as provided in the example, which leaves the secret being wrong but this is the same value as defined in the portal for the app config.
#auburg the secret must be base64 decoded before using. The code you shared doesn't do it. You may want to call Convert.FromBase64String.
I got the same message from Azure, even though my credentials were valid. Turns out, my local Date/Time settings were incorrect (made some clock changes that messed up current time). Once fixed the date/time to the actual current time, it went back to normal.
This happens because Azure issues a short lived token and compares your UTC time to theirs to make sure the token is valid. Maybe not the case for all "Invalid Token" issues but definitely worth the check.
Could be a TLS requirement
try:
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
before you create the client.

Is there a ready to use "GenerateJwt" method?

I'm using the Microsoft.AspNetCore.Authentication.JwtBearer and System.IdentityModel.Tokens.Jwt for my .NET Core project. In my Startup file I run the configuration setup for the [Authorize] annotation. This works fine for me when I'm generating new tokens with my own method (sample)
public object GenerateToken(Dictionary<string, object> payload)
{
DateTime tokenExpiresAt = DateTime.Now.AddMilliseconds(1); // From config
byte[] symmetricKey = Convert.FromBase64String("secret"); // from config
SymmetricSecurityKey symmetricSecurityKey = new SymmetricSecurityKey(symmetricKey);
SecurityTokenDescriptor tokenDescriptor = new SecurityTokenDescriptor
{
Claims = payload,
Expires = tokenExpiresAt,
SigningCredentials = new SigningCredentials(symmetricSecurityKey, SecurityAlgorithms.HmacSha256Signature)
};
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
SecurityToken securityToken = tokenHandler.CreateToken(tokenDescriptor);
string token = tokenHandler.WriteToken(securityToken);
return new { token, tokenExpiresAt };
}
The validation of a token doesn't need to be implemented because it's done with the [Authorize] annotation. I would like to know if there is a method I can use to generate a token and don't have to code it on my own? I'm storing the generated tokens to a database and also need to return the expiration time.
So yes, the solution above works fine for me but maybe it's redundant :)
Is there a method that takes the token secret, the payload and the time the token will expire? E.g. TokenGenerator.Sign("secret", payload, tokenExpiresAt)?
Microsoft libraries don't support issuing tokens natively, so there's no one command in a Microsoft library like you're looking for. However Microsoft does issue tokens as an identity server using their service azure ad, that would probably be their easiest way.
The way you're doing is basically fine if you're just doing that. and not full authentication framework, here's an example of people doing very similar thing to you: https://jasonwatmore.com/post/2019/10/11/aspnet-core-3-jwt-authentication-tutorial-with-example-api
If you are looking to implement your own complete authentication service that can issue tokens. there are some relatively common 3rd party libraries that will help you not have to reinvent the wheel, one of which is identityserver4:
https://identityserver4.readthedocs.io/en/latest/index.html
it's a full identity provider solution.
another one is openiddict https://devblogs.microsoft.com/aspnet/bearer-token-authentication-in-asp-net-core/

Encrypt JWT token

We are using the JWT Nuget to create and validate token. Here is the code that we use to create token
private string CreateAccessToken(Dictionary<string, object> payload)
{
IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
IJsonSerializer serializer = new JsonNetSerializer();
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
var token = encoder.Encode(payload, GetJWTKey());
return token;
}
My understanding is, this doesn't encrypt the token as I was able to parse the token by visting jwt.io and was able to read the contents. I would like to encrypt the token such that it should not be parsed. I wasn't able to find any method in JWT Nuget through which I can encrypt the token.
So how do I sign and encrypt the token using JWT Nuget?
Edit:
I understand that JWT doesn't require any encryption as only the authenticated user will be able to read the token which means, I am reading about my own contents and also, the actual communication will be over secured layer. So actually there is no need to encrypt the token yet, my requirement is the token shouldn't be human readable
Your understanding is correct but you are missing an important feature of JWT: encrypting the token is not a purpose of JWT.
The secret used by the algorithm is used to sign the token and not for encrypting it, for more information take a look to the RFC 7515.
As suggested in the comments below there is also the RFC 7516, Json Web Encryption (JWE).
For using JWE inside a C# application you have to use the System.IdentityModel.Tokens.Jwt package, and then something like:
var handler = new JwtSecurityTokenHandler();
var tokenDescriptor = new SecurityTokenDescriptor
{
Audience = "audience",
//other property
EncryptingCredentials = new X509EncryptingCredentials(new X509Certificate2("path/to/public/key"))
};

Validating Google ID tokens in C#

I need to validate a Google ID token passed from a mobile device at my ASP.NET web api.
Google have some sample code here but it relies on a JWT NuGet package which is .Net 4.5 only (I am using C#/.Net 4.0). Is anyone aware of any samples which do this without these packages or has achieved this themselves? The use of the package makes it very difficult to work out what I need to do without it.
According to this github issue, you can now use GoogleJsonWebSignature.ValidateAsync method to validate a Google-signed JWT. Simply pass the idToken string to the method.
var validPayload = await GoogleJsonWebSignature.ValidateAsync(idToken);
Assert.NotNull(validPayload);
If it is not a valid one, it will return null.
Note that to use this method, you need to install Google.Apis.Auth nuget firsthand.
The challenge is validating the JWT certificate in the ID token. There is currently not a library I'm aware of that can do this that doesn't require .Net 4.5 and until there is a solution for JWT validation in .NET 4.0, there will not be an easy solution.
However, if you have an access token, you can look into performing validation using oauth2.tokeninfo. To perform basic validation using token info, you can do something like the following:
// Use Tokeninfo to validate the user and the client.
var tokeninfo_request = new Oauth2Service().Tokeninfo();
tokeninfo_request.Access_token = _authState.AccessToken;
var tokeninfo = tokeninfo_request.Fetch();
if (userid == tokeninfo.User_id
&& tokeninfo.Issued_to == CLIENT_ID)
{
// Basic validation succeeded
}
else
{
// The credentials did not match.
}
The information returned from the Google OAuth2 API tells you more information about a particular token such as the client id it was issued too as well as its expiration time.
Note You should not be passing around the access token but instead should be doing this check after exchanging a one-time code to retrieve an access token.
ClientId also needs to be passed, which should be set from Google API Console. If only pass TokenId, GoogleJsonWebSignature throws error. This answer is in addition to #edmundpie answer
var settings = new GoogleJsonWebSignature.ValidationSettings()
{
Audience = new List<string>() { "[Placeholder for Client Id].apps.googleusercontent.com" }
};
var validPayload = await GoogleJsonWebSignature.ValidateAsync(model.ExternalTokenId, settings);

Categories

Resources