.NET 7 supports JWT-signature verification against PUK ECDsa and Brainpool P256R1 alg? - c#

EDIT: Issue solved.I am able to validate jwts signature against the public key of the x5c cert chain. Thank you, Topaco, your comment lead me to check my data once again. Solution on the bottom.
Im new on this topic, but Im trying to implement a jwt signature verification and dont want to use bouncy castle. The SigningCert(Chain) lies in the JWT's 'x5c' header. jwt-alg header says "bp256r1".
My problem is Im getting following ArgumentException as Validation Result:
IDX10645: Elliptical Curve not supported for curveId: 'brainpoolP256r1'
To be fair: That message doesnt leave much room for interpretation. But I thought, that with Win10 and Ongoing, BP256R1 algorithm is supported?.
What am i Missing?
thats my Code, I'd wish validation result is true, but it inst:
public async Task<JwtSecurityToken> GetDiscoveryDocument()
{
//Get Token
var response = await _client.GetAsync(".well-known/openid-configuration");
response.EnsureSuccessStatusCode();
//parse Token
string token = await response.Content.ReadAsStringAsync();
var jwtHandler = new JwtSecurityTokenHandler();
var jwt = jwtHandler.ReadJwtToken(token);
//Get SigningCert
var x5cCerts = JsonSerializer.Deserialize<object[]>(jwt.Header["x5c"].ToString());
X509Certificate2 cert = new X509Certificate2(Encoding.UTF8.GetBytes(x5cCerts.First().ToString()));
//Get PublicKey and Set validationParameters, ValidateSignature
var ecdsa = cert.GetECDsaPublicKey();
var vaidationResult = await jwtHandler.ValidateTokenAsync(token, new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new ECDsaSecurityKey(ecdsa),
});
return jwt;
}
I also tried to use:
var ecdsa = cert.GetECDsaPublicKey();
byte[] data = ....;
byte[] signature = ....;
ecdsa.VerifyData(data, signature, HashAlgorithmName.SHA256);
Solution:
public async Task<JwtSecurityToken> GetDiscoveryDocument()
{
//Get Token
var response = await _client.GetAsync(".well-known/openid-configuration");
response.EnsureSuccessStatusCode();
//parse Token
string token = await response.Content.ReadAsStringAsync();
var jwtHandler = new JwtSecurityTokenHandler();
var jwt = jwtHandler.ReadJwtToken(token);
//Get SigningCert
var x5cCerts = JsonSerializer.Deserialize<object[]>(jwt.Header["x5c"].ToString());
X509Certificate2 cert = new X509Certificate2(Encoding.UTF8.GetBytes(x5cCerts.First().ToString()));
//Get PublicKey and Set validationParameters, ValidateSignature
var ecdsa = cert.GetECDsaPublicKey();
var valid = ecdsa.VerifyData(Encoding.UTF8.GetBytes(jwt.EncodedHeader + "." + jwt.EncodedPayload), Base64UrlEncoder.DecodeBytes(jwt.RawSignature), HashAlgorithmName.SHA256);
return jwt;
}

Related

C# .net Core read JWT rs256 payload [duplicate]

I don't understand how this library works. Could you help me please ?
Here is my simple code :
public void TestJwtSecurityTokenHandler()
{
var stream =
"eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJJU1MiLCJzY29wZSI6Imh0dHBzOi8vbGFyaW0uZG5zY2UuZG91YW5lL2NpZWxzZXJ2aWNlL3dzIiwiYXVkIjoiaHR0cHM6Ly9kb3VhbmUuZmluYW5jZXMuZ291di5mci9vYXV0aDIvdjEiLCJpYXQiOiJcL0RhdGUoMTQ2ODM2MjU5Mzc4NClcLyJ9";
var handler = new JwtSecurityTokenHandler();
var jsonToken = handler.ReadToken(stream);
}
This is the error :
The string needs to be in compact JSON format, which is of the form: Base64UrlEncodedHeader.Base64UrlEndcodedPayload.OPTIONAL,Base64UrlEncodedSignature'.
If you copy the stream in jwt.io website, it works fine :)
I found the solution, I just forgot to Cast the result:
var stream = "[encoded jwt]";
var handler = new JwtSecurityTokenHandler();
var jsonToken = handler.ReadToken(stream);
var tokenS = jsonToken as JwtSecurityToken;
Or, without the cast:
var token = "[encoded jwt]";
var handler = new JwtSecurityTokenHandler();
var jwtSecurityToken = handler.ReadJwtToken(token);
I can get Claims using:
var jti = tokenS.Claims.First(claim => claim.Type == "jti").Value;
new JwtSecurityTokenHandler().ReadToken("") will return a SecurityToken
new JwtSecurityTokenHandler().ReadJwtToken("") will return a JwtSecurityToken
If you just change the method you are using you can avoid the cast in the above answer
You need the secret string which was used to generate encrypt token.
This code works for me:
protected string GetName(string token)
{
string secret = "this is a string used for encrypt and decrypt token";
var key = Encoding.ASCII.GetBytes(secret);
var handler = new JwtSecurityTokenHandler();
var validations = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
var claims = handler.ValidateToken(token, validations, out var tokenSecure);
return claims.Identity.Name;
}
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Email, model.UserName),
new Claim(JwtRegisteredClaimNames.NameId, model.Id.ToString()),
};
var token = new JwtSecurityToken(_config["Jwt:Issuer"],
_config["Jwt:Issuer"],
claims,
expires: DateTime.Now.AddMinutes(30),
signingCredentials: creds);
Then extract content
var handler = new JwtSecurityTokenHandler();
string authHeader = Request.Headers["Authorization"];
authHeader = authHeader.Replace("Bearer ", "");
var jsonToken = handler.ReadToken(authHeader);
var tokenS = handler.ReadToken(authHeader) as JwtSecurityToken;
var id = tokenS.Claims.First(claim => claim.Type == "nameid").Value;
Using .net core jwt packages, the Claims are available:
[Route("api/[controller]")]
[ApiController]
[Authorize(Policy = "Bearer")]
public class AbstractController: ControllerBase
{
protected string UserId()
{
var principal = HttpContext.User;
if (principal?.Claims != null)
{
foreach (var claim in principal.Claims)
{
log.Debug($"CLAIM TYPE: {claim.Type}; CLAIM VALUE: {claim.Value}");
}
}
return principal?.Claims?.SingleOrDefault(p => p.Type == "username")?.Value;
}
}
I write this solution and it's work for me
protected Dictionary<string, string> GetTokenInfo(string token)
{
var TokenInfo = new Dictionary<string, string>();
var handler = new JwtSecurityTokenHandler();
var jwtSecurityToken = handler.ReadJwtToken(token);
var claims = jwtSecurityToken.Claims.ToList();
foreach (var claim in claims)
{
TokenInfo.Add(claim.Type, claim.Value);
}
return TokenInfo;
}
Extending on cooxkie answer, and dpix answer, when you are reading a jwt token (such as an access_token received from AD FS), you can merge the claims in the jwt token with the claims from "context.AuthenticationTicket.Identity" that might not have the same set of claims as the jwt token.
To Illustrate, in an Authentication Code flow using OpenID Connect,after a user is authenticated, you can handle the event SecurityTokenValidated which provides you with an authentication context, then you can use it to read the access_token as a jwt token, then you can "merge" tokens that are in the access_token with the standard list of claims received as part of the user identity:
private Task OnSecurityTokenValidated(SecurityTokenValidatedNotification<OpenIdConnectMessage,OpenIdConnectAuthenticationOptions> context)
{
//get the current user identity
ClaimsIdentity claimsIdentity = (ClaimsIdentity)context.AuthenticationTicket.Identity;
/*read access token from the current context*/
string access_token = context.ProtocolMessage.AccessToken;
JwtSecurityTokenHandler hand = new JwtSecurityTokenHandler();
//read the token as recommended by Coxkie and dpix
var tokenS = hand.ReadJwtToken(access_token);
//here, you read the claims from the access token which might have
//additional claims needed by your application
foreach (var claim in tokenS.Claims)
{
if (!claimsIdentity.HasClaim(claim.Type, claim.Value))
claimsIdentity.AddClaim(claim);
}
return Task.FromResult(0);
}
Use this:
public static string Get_Payload_JWTToken(string token)
{
var handler = new JwtSecurityTokenHandler();
var DecodedJWT = handler.ReadJwtToken(token);
string payload = DecodedJWT.EncodedPayload; // Gives Payload
return Encoding.UTF8.GetString(FromBase64Url(payload));
}
static byte[] FromBase64Url(string base64Url)
{
string padded = base64Url.Length % 4 == 0
? base64Url : base64Url + "====".Substring(base64Url.Length % 4);
string base64 = padded.Replace("_", "/").Replace("-", "+");
return Convert.FromBase64String(base64);
}
Though this answer is not answering the original question but its a really very useful feature for C# developers, so adding it as the answer.
Visual Studio 2022 has added a feature to decode the value of a token at runtime.
You can check the feature in Visual Studio 2022 preview (version 17.5.0 preview 2.0)
Mouse over the variable containing the JWT and then select the string manipulation as JWT Decode, and you can see the token value.

How to implement ISecureDataFormat<AuthenticationTicket> Unprotect method?

Background:
I am attempting to support an authorization code flow to enable SSO from my app to a third party application.
Now I am stuck on the implementation of the Unprotect method which is supposed to return an AuthenticationTicket.
OAuth2 Server Configuration:
var allowInsecureHttp = bool.Parse(ConfigurationManager.AppSettings["AllowInsecureHttp"]);
var oAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = allowInsecureHttp,
TokenEndpointPath = new PathString("/oauth2/token"),
AuthorizeEndpointPath = new PathString("/oauth2/authorize"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
Provider = new CustomOAuthProvider(HlGlobals.Kernel),
AccessTokenFormat = new CustomJwtFormat(_baseUrl, HlGlobals.Kernel),
AuthorizationCodeProvider = new SimpleAuthenticationTokenProvider(),
AuthorizationCodeFormat = new CustomJwtFormat(_baseUrl, HlGlobals.Kernel)
};
// OAuth 2.0 Bearer Access Token Generation
app.UseOAuthAuthorizationServer(oAuthServerOptions);
JWT Token Generation / Protect method:
public string Protect(AuthenticationTicket data)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
// Get the client and assign to GUID -- the audience is api this token will be valid against
string audienceId = ConfigurationManager.AppSettings["as:AudienceId"];
Guid clientId;
bool isValidAudience = Guid.TryParse(audienceId, out clientId);
// Check for a valid Client Guid in the Auth ticket properties
if (!isValidAudience)
{
throw new InvalidOperationException("AuthenticationTicket.Properties does not include audience");
}
// Create the JWT token
string symmetricKeyAsBase64 = ConfigurationManager.AppSettings["as:AudienceSecret"];
var keyByteArray = TextEncodings.Base64Url.Decode(symmetricKeyAsBase64);
var signingKey = new HmacSigningCredentials(keyByteArray);
var issued = data.Properties.IssuedUtc;
var expires = data.Properties.ExpiresUtc;
var token = new JwtSecurityToken(_issuer, audienceId, data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime, signingKey);
var handler = new JwtSecurityTokenHandler();
var jwt = handler.WriteToken(token);
// Return the JWT Token
return jwt;
}
Finally, the 'Unprotect' method which is responsible for validation of the JWT and returning and authentication ticket:
public AuthenticationTicket Unprotect(string protectedText)
{
string symmetricKeyAsBase64 = ConfigurationManager.AppSettings["as:AudienceSecret"];
string audienceId = ConfigurationManager.AppSettings["as:AudienceId"];
Guid clientId;
bool isValidAudience = Guid.TryParse(audienceId, out clientId);
var keyByteArray = TextEncodings.Base64Url.Decode(symmetricKeyAsBase64);
var signingKey = new HmacSigningCredentials(keyByteArray);
var tokenValidationParameters = new TokenValidationParameters
{
ValidAudience = audienceId,
ValidIssuer = _issuer,
IssuerSigningKey = signingKey // Cannot convert HMAC Signing Credentials to System.IdentityModel.Tokens.SecurityKey
ValidateLifetime = true,
ValidateAudience = true,
ValidateIssuer = true,
RequireSignedTokens = true,
RequireExpirationTime = true,
ValidateIssuerSigningKey = true
};
var handler = new JwtSecurityTokenHandler();
SecurityToken token = null;
var principal = handler.ValidateToken(protectedText, tokenValidationParameters, out token);
var identity = principal.Identities;
return new AuthenticationTicket(identity.First(), new AuthenticationProperties());
}
One issue right off the jump is the issuer signing key. I am having trouble coming up with an acceptable parameter. I am seeing the error message:
Cannot convert HMAC Signing Credentials to
System.IdentityModel.Tokens.SecurityKey
To be honest, I am unsure why the Protect method needs to fire. I thought the flow would end with the returning of the JWT token, but apparently not. Now I am struggling with the implementation of the Unprotect method as it is something I have never had to struggle with previously.
Even if I set all of the options to 'false' on the tokenValidationParamters I am still getting the following error on validation:
An exception of type
'System.IdentityModel.SignatureVerificationFailedException' occurred
in System.IdentityModel.Tokens.Jwt.dll but was not handled in user
code
Additional information: IDX10503: Signature validation failed. Keys
tried: ''.
Exceptions caught:
''.
token:
'{"typ":"JWT","alg":"HS256"}.{"iss":"http://myissuer.com","aud":"346e886acabf457cb9f721f615ff996c","exp":1510925372,"nbf":1510925072}'
When I compare the values to the decrypted token using JWT.IO all of the values match as expected.
Any guidance on what I may be doing wrong here, or how to call validateToken with a valid signing key on the tokenvalidationparamters would be most helpful and appreciated.
Thanks in advance!
EDIT:
I am curious why the Unprotect is firing at all... I thought I should be returning a JWT token to the client. The method to return a JWT fires before the Unprotect method and the JWT is never returned to the client.
Do I have something configured incorrectly?
In your Startup.cs file call the following methods. The Protect method in your customJwtFormat is called when the user actually tries to sign in to the authentication server endpoint. The unprotect method is called when the user tries to access a protected api url via the "Bearer [token]" authentication model. If you don't say for example app.UseOAuthBearerAuthentication in your Startup.cs file you'll end up getting errors such as
Microsoft.Owin.Security.OAuth.OAuthBearerAuthenticationMiddleware
Warning: 0 : invalid bearer token received
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
{
AccessTokenFormat = _tokenFormat
});
Create your customjwtFormat like below, you can alter the implementation as required.
public string Protect(AuthenticationTicket data)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
string audienceId = ConfigurationManager.AppSettings["as:AudienceId"];
string symmetricKeyAsBase64 = ConfigurationManager.AppSettings["as:AudienceSecret"];
var keyByteArray = TextEncodings.Base64Url.Decode(symmetricKeyAsBase64);
//var signingKey = new HmacSigningCredentials(keyByteArray);
var securityKey = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(keyByteArray);
securityKey.KeyId = ConfigurationManager.AppSettings["as:AudienceId"];
var signingCredentials = new Microsoft.IdentityModel.Tokens.SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature);
var issued = data.Properties.IssuedUtc;
var expires = data.Properties.ExpiresUtc;
var token = new JwtSecurityToken(_issuer, audienceId, data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime, signingCredentials);
var handler = new JwtSecurityTokenHandler();
var jwt = handler.WriteToken(token);
return jwt;
}
public AuthenticationTicket Unprotect(string protectedText)
{
string symmetricKeyAsBase64 = ConfigurationManager.AppSettings["as:AudienceSecret"];
string audienceId = ConfigurationManager.AppSettings["as:AudienceId"];
string authority = ConfigurationManager.AppSettings["Authority"];
var keyByteArray = TextEncodings.Base64Url.Decode(symmetricKeyAsBase64);
var signingKey = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(keyByteArray);
var tokenValidationParameters = new TokenValidationParameters
{
ValidAudience = audienceId,
ValidIssuer = _issuer,
IssuerSigningKey = signingKey,
ValidateLifetime = true,
ValidateAudience = true,
ValidateIssuer = true,
RequireSignedTokens = true,
RequireExpirationTime = true,
ValidateIssuerSigningKey = true
};
var handler = new JwtSecurityTokenHandler();
SecurityToken token = null;
// Unpack token
var pt = handler.ReadJwtToken(protectedText);
string t = pt.RawData;
var principal = handler.ValidateToken(t, tokenValidationParameters, out token);
var identity = principal.Identities;
return new AuthenticationTicket(identity.First(), new AuthenticationProperties());
}
you should use something like this
public AuthenticationTicket Unprotect(string protectedText)
{
if (string.IsNullOrWhiteSpace(protectedText))
{
throw new ArgumentNullException("protectedText");
}
string audienceId = ConfigurationManager.AppSettings["as:AudienceId"];
string symmetricKeyAsBase64 = ConfigurationManager.AppSettings["as:AudienceSecret"];
var handler = new JwtSecurityTokenHandler();
var token = handler.ReadToken(protectedText);
SecurityToken validatedToken;
string t = ((JwtSecurityToken)token).RawData;
var keyByteArray = TextEncodings.Base64Url.Decode(symmetricKeyAsBase64);
//var signingKey = new HmacSigningCredentials(keyByteArray);
var validationParams = new TokenValidationParameters()
{
ValidateLifetime = false,
ValidAudience = audienceId,
ValidIssuer = audienceId,
ValidateIssuer = false,
ValidateAudience = false,
IssuerSigningToken = new BinarySecretSecurityToken(keyByteArray)
};
var principal = handler.ValidateToken(t, validationParams, out validatedToken);
var identity = principal.Identities;
return new AuthenticationTicket(identity.First(), new AuthenticationProperties());
}

How to validate Azure AD security token?

The following code gives me Azure AD security token, I need to validate that token is valid or not. How to achieve this?
// Get OAuth token using client credentials
string tenantName = "mytest.onmicrosoft.com";
string authString = "https://login.microsoftonline.com/" + tenantName;
AuthenticationContext authenticationContext = new AuthenticationContext(authString, false);
// Config for OAuth client credentials
string clientId = "fffff33-6666-4888-a4tt-fbttt44444";
string key = "123v47o=";
ClientCredential clientCred = new ClientCredential(clientId, key);
string resource = "http://mytest.westus.cloudapp.azure.com";
string token;
Task<AuthenticationResult> authenticationResult = authenticationContext.AcquireTokenAsync(resource, clientCred);
token = authenticationResult.Result.AccessToken;
Console.WriteLine(token);
// How can I validate this token inside my service?
There are two steps to verify the token. First, verify the signature of the token to ensure the token was issued by Azure Active Directory. Second, verify the claims in the token based on the business logic.
For example, we need to verify the iss and aud claim if you were developing a single tenant app. And you also need to verify the nbf to ensure the token is not expired. For more claims you can refer here.
Below description is from here about the detail of signature verifying. (Note: The example below uses the Azure AD v2 endpoint. You should use the endpoint that corresponds to the endpoint the client app is using.)
The access token from the Azure AD is a JSON Web Token(JWT) which is signed by Security Token Service in private key.
The JWT includes 3 parts: header, data, and signature. Technically, we can use the public key to validate the access token.
First step – retrieve and cache the signing tokens (public key)
Endpoint: https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration
Then we can use the JwtSecurityTokenHandler to verify the token using the sample code below:
public JwtSecurityToken Validate(string token)
{
string stsDiscoveryEndpoint = "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration";
ConfigurationManager<OpenIdConnectConfiguration> configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint);
OpenIdConnectConfiguration config = configManager.GetConfigurationAsync().Result;
TokenValidationParameters validationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = false,
IssuerSigningTokens = config.SigningTokens,
ValidateLifetime = false
};
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
SecurityToken jwt;
var result = tokenHandler.ValidateToken(token, validationParameters, out jwt);
return jwt as JwtSecurityToken;
}
And if you were using the OWIN components in your project, it is more easy to verify the token. We can use the code below to verify the token:
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Audience = ConfigurationManager.AppSettings["ida:Audience"],
Tenant = ConfigurationManager.AppSettings["ida:Tenant"]
});
Then we can use the code below to verify the ‘scope’ in the token:
public IEnumerable<TodoItem> Get()
{
// user_impersonation is the default permission exposed by applications in AAD
if (ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/scope").Value != "user_impersonation")
{
throw new HttpResponseException(new HttpResponseMessage {
StatusCode = HttpStatusCode.Unauthorized,
ReasonPhrase = "The Scope claim does not contain 'user_impersonation' or scope claim not found"
});
}
...
}
And here is a code sample which protected the web API with Azure AD:
Protect a Web API using Bearer tokens from Azure AD
Just wanted to add to Fei's answer for people using .net Core 2.0
You'll have to modify 2 lines of the Validate(string token) method.
var configManager = new ConfigurationManager<OpenIdConnectConfiguration>(
stsDiscoveryEndpoint,
new OpenIdConnectConfigurationRetriever()); //1. need the 'new OpenIdConnect...'
OpenIdConnectConfiguration config = configManager.GetConfigurationAsync().Result;
TokenValidationParameters validationParameters = new TokenValidationParameters
{
//decode the JWT to see what these values should be
ValidAudience = "some audience",
ValidIssuer = "some issuer",
ValidateAudience = true,
ValidateIssuer = true,
IssuerSigningKeys = config.SigningKeys, //2. .NET Core equivalent is "IssuerSigningKeys" and "SigningKeys"
ValidateLifetime = true
};
But if you are not using OWIN in your projects, it is going to be a little hard or at least time consuming..
This articleHere is great resource.
And because I do not have much to add on the above, except the detailed code.. Here is something that can be useful to you:
public async Task<ClaimsPrincipal> CreatePrincipleAsync()
{
AzureActiveDirectoryToken azureToken = Token.FromJsonString<AzureActiveDirectoryToken>();
var allParts = azureToken.IdToken.Split(".");
var header = allParts[0];
var payload = allParts[1];
var idToken = payload.ToBytesFromBase64URLString().ToAscii().FromJsonString<AzureActiveDirectoryIdToken>();
allParts = azureToken.AccessToken.Split(".");
header = allParts[0];
payload = allParts[1];
var signature = allParts[2];
var accessToken = payload.ToBytesFromBase64URLString().ToAscii().FromJsonString<AzureActiveDirectoryAccessToken>();
var accessTokenHeader = header.ToBytesFromBase64URLString().ToAscii().FromJsonString<AzureTokenHeader>();
var isValid = await ValidateToken(accessTokenHeader.kid, header, payload, signature);
if (!isValid)
{
throw new SecurityException("Token can not be validated");
}
var principal = await CreatePrincipalAsync(accessToken, idToken);
return principal;
}
private async Task<bool> ValidateToken(string kid, string header, string payload, string signature)
{
string keysAsString = null;
const string microsoftKeysUrl = "https://login.microsoftonline.com/common/discovery/keys";
using (var client = new HttpClient())
{
keysAsString = await client.GetStringAsync(microsoftKeysUrl);
}
var azureKeys = keysAsString.FromJsonString<MicrosoftConfigurationKeys>();
var signatureKeyIdentifier = azureKeys.Keys.FirstOrDefault(key => key.kid.Equals(kid));
if (signatureKeyIdentifier.IsNotNull())
{
var signatureKey = signatureKeyIdentifier.x5c.First();
var certificate = new X509Certificate2(signatureKey.ToBytesFromBase64URLString());
var rsa = certificate.GetRSAPublicKey();
var data = string.Format("{0}.{1}", header, payload).ToBytes();
var isValidSignature = rsa.VerifyData(data, signature.ToBytesFromBase64URLString(), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
return isValidSignature;
}
return false;
}
There are some functions that I use in here that are not available for you, they are self descriptive.

Verifying/validating a JWT token created in PHP in .NET

I have a JWT token created using PHP that I need to then use in a .NET app (framework version 4.5.1). The token is generated in PHP using the following code (relies on https://github.com/lcobucci/jwt library):
use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Signer\Hmac\Sha256;
$tokenBuilder = new Builder();
$tokenSigner = new Sha256();
$token = (string)$tokenBuilder
->setIssuer('localhost:8081')
->setAudience('myaudience')
->setIssuedAt(time())
->setExpiration(time() + 86400)
->sign($tokenSigner, '710VWV0zby')
->getToken();
return $token;
I've been able to read the token fine in C#, but am struggling mightily to figure out how to validate and verify the token signature.
TokenValidationParameters validationParameters = new TokenValidationParameters
{
ValidateIssuer = true
};
var tokenHandler = new JwtSecurityTokenHandler();
// THIS IS TO TEST IF TOKEN CAN BE READ
/*var jwtToken = tokenHandler.ReadJwtToken(token);
HttpContext.Current.Response.Write(jwtToken.Issuer);*/
SecurityToken validatedToken = null;
try
{
tokenHandler.ValidateToken(token, validationParameters, out validatedToken);
}
catch (Exception)
{
HttpContext.Current.Response.Write("Invalid! :(");
}
if (validatedToken != null) {
HttpContext.Current.Response.Write("Valid! :)");
}
Obviously, my code can't verify any signature, given that there's not even any mention of a SHA-256 key anywhere. I'm assuming I need to include that somehow in the TokenValidationParameters there's a property I need to set, and I'm guessing SigningToken would be the one, but I don't really know where to start to specify an HMAC SHA 256 key.
You need somehow export certificate that was using for token creation. It can be file in .pem format for example. After that create crypto provider using data from that certificate
public static RSACryptoServiceProvider CreateRsaCryptoProviderFromX509Certificate()
{
byte[] certData = Convert.FromBase64String(_CERTIFICATE);
X509Certificate2 x509Cert = new X509Certificate2();
x509Cert.Import(certData);
var x509PublicKeyXml = x509Cert.PublicKey.Key.ToXmlString(false);
RSACryptoServiceProvider RsaProvider = new RSACryptoServiceProvider();
RsaProvider.FromXmlString(x509PublicKeyXml);
return RsaProvider;
}
where _CERTIFICATE store data from .pem file without leading coments
string _CERTIFICATE = #" DD5NYXRyaXg0Ml9mNThlMzdkLWU2ZjktNGU0Yi05MzVlLTNhMDFi
NzU2N2I5YjAeFw0xNjEyMzExNTE1MjNaFw00MjAxMDcxNTE1MjNaMEkxRzBFBgNVBAMMPk1hdHJp
eDQyX0FDU19SZWx5aW5nUGFydHlfZmY1OGUzN2QtZTZmOS00ZTRiLTkzNWUtM2EwMWI3NTY3Yjli
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAho5G6pY9QJs/945aQ1w8oiF/17ZNGsNY
ul5G/+TprN7KfgzT9u+A588f4Z4B8z5QJlwIUeH33iuRcV0AIHd9MnEKR56IdOLLlNWNPvRAG5FJ
Wt4XPlaG+bE/oyuqxqpQM1KJ0iN74K/WLXM8ZdQlq7gTgtLS+icZH3i2arC8rdobh3zRk1wbUVXn
kjR4CASy+07LZwbVVp2g3pOsuy5AWBURIynQ7z3zj+u7NMF42htLOEzISl3Qb3BMOoXFMm93UGwp
B/Ae+zpWFWeh6190ipcUMXoAOfdh9VZUZX9C7OI/3plOiwKUvwfBQyLR8C/4uiTcCTp1i8fS0bta
jkPhdQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQA3fmwEgej+BhB7dkw+0TWEDiIC9cXR4uW7kElM
7+L7ARmUYVpAx05Z8oarsR0zm1u3ZYR00y3eLhw5RcXN6hC5jb5HYSQZERdqlzvS6bU6xJ57H7tC
KuPADkYmuPnRM/cdMKPeSG3ZHnHcTgJx62hFloPWbGPr9VLVp4R4coUgtuZMtlFvXamjpCNYSpob
N9wzk36r/4c+Nd/n+4DwqIaVzgEXHXkOUtOZhTYh7SG5WJVUSep5cIq3SBGzLn8oXCjiqn72zJ7C
vn5/ekaC1nzMDMcga5qWQNdLd/rXt65ZMbB/JhM+Ee9TIvmrrDXlvRh2cv7GtoTtPYEbIdVvrF+W";
for validation create token handler, validation parameters and validate token with cryptoprovider
public static bool ValidateJwt(string jwt)
{
JwtSecurityTokenHandler securityTokenHandler = new JwtSecurityTokenHandler();
RSACryptoServiceProvider rsa = CreateRsaCryptoProviderFromX509Certificate();
TokenValidationParameters validationParameters = new TokenValidationParameters()
{
ValidAudience ="urn:6c23aaa7-6da8-4941-98b0-62f63cd146",
ValidIssuer = "https://accounts.issuer.com",
IssuerSigningKey = new RsaSecurityKey(rsa)
};
SecurityToken token;
ClaimsPrincipal claimsPrincipal = securityTokenHandler.ValidateToken(jwt, validationParameters, out token);
return claimsPrincipal.IsInRole("Admin");
}

OAuth Bearer token Authentication is not passing signature validation

I get the following error on the token consumer. Any help resolving this will be most appreciated. Thanks.
"IDX10503: Signature validation failed.
Keys tried:
'System.IdentityModel.Tokens.SymmetricSecurityKey '. Exceptions
caught: 'System.InvalidOperationException: IDX10636:
SignatureProviderFactory.CreateForVerifying returned null for key:
'System.IdentityModel.Tokens.SymmetricSecurityKey',
signatureAlgorithm:
'http://www.w3.org/2001/04/xmldsig-more#hmac-sha256'. at
Microsoft.IdentityModel.Logging.LogHelper.Throw(String message, Type
exceptionType, EventLevel logLevel, Exception innerException) at
System.IdentityModel.Tokens.JwtSecurityTokenHandler.ValidateSignature(Byte[]
encodedBytes, Byte[] signature, SecurityKey key, String algorithm) at
System.IdentityModel.Tokens.JwtSecurityTokenHandler.ValidateSignature(String
token, TokenValidationParameters validationParameters) '. token:
'token info was here'"
Token Generation Code on OAuth server
using (var ctlr = new EntityController())
{
var authRepo = ctlr.GetAuthModelRepository();
string clientId;
ticket.Properties.Dictionary.TryGetValue(WebConstants.OwinContextProps.OAuthClientIdPropertyKey, out clientId);
if (string.IsNullOrWhiteSpace(clientId))
{
throw new InvalidOperationException("AuthenticationTicket.Properties does not include audience");
}
//audience record
var client = authRepo.FindAuthClientByOAuthClientID(clientId);
var issued = ticket.Properties.IssuedUtc;
var expires = ticket.Properties.ExpiresUtc;
var hmac = new HMACSHA256(Convert.FromBase64String(client.Secret));
var signingCredentials = new SigningCredentials(
new InMemorySymmetricSecurityKey(hmac.Key),
Algorithms.HmacSha256Signature, Algorithms.Sha256Digest);
TokenValidationParameters validationParams =
new TokenValidationParameters()
{
ValidAudience = clientId,
ValidIssuer = _issuer,
ValidateLifetime = true,
ValidateAudience = true,
ValidateIssuer = true,
RequireSignedTokens = true,
RequireExpirationTime = true,
ValidateIssuerSigningKey = true,
IssuerSigningToken = new BinarySecretSecurityToken(hmac.Key)
};
var jwtHandler = new JwtSecurityTokenHandler();
var jwt = new JwtSecurityToken(_issuer, clientId, ticket.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime, signingCredentials);
jwtOnTheWire = jwtHandler.WriteToken(jwt);
SecurityToken validatedToken = null;
jwtHandler.ValidateToken(jwtOnTheWire, validationParams,out validatedToken);
if (validatedToken == null)
return "token_validation_failed";
}
return jwtOnTheWire;
Token Consumption\validation ASP.Net 5 vNext site within Owin Startup.cs
public void ConfigureServices(IServiceCollection services)
services.ConfigureOAuthBearerAuthentication(config =>
{
//oauth validation
var clientSecret = "not the real secret";
var hmac = new HMACSHA256(Convert.FromBase64String(clientSecret));
var signingCredentials = new SigningCredentials(
new SymmetricSecurityKey(hmac.Key), Algorithms.HmacSha256Signature, Algorithms.Sha256Digest);
config.TokenValidationParameters.ValidAudience = "myappname";
config.TokenValidationParameters.ValidIssuer = "mydomain.com";
config.TokenValidationParameters.RequireSignedTokens = true;
config.TokenValidationParameters.RequireExpirationTime = true;
config.TokenValidationParameters.ValidateLifetime = true;
config.TokenValidationParameters.ValidateIssuerSigningKey = true;
config.TokenValidationParameters.ValidateSignature = true;
config.TokenValidationParameters.ValidateAudience = true;
config.TokenValidationParameters.IssuerSigningKey = signingCredentials.SigningKey;
});
public void Configure(IApplicationBuilder app)
app.UseOAuthBearerAuthentication(config =>
{
config.AuthenticationScheme = "Bearer";
config.AutomaticAuthentication = true;
});
I was able to add my own signature validation to the TokenValidationParameters Then I compared the incoming Raw signature of the JWT to the compiled signature in this code and if it matches the signature is valid.
Why this didn't happen using the builtin signature validation is beyond me, maybe it's a possible bug in beta 6 of the vNext Identity token framework.
public void ConfigureServices(IServiceCollection services)
config.TokenValidationParameters.SignatureValidator =
delegate (string token, TokenValidationParameters parameters)
{
var clientSecret = "not the real secret";
var jwt = new JwtSecurityToken(token);
var hmac = new HMACSHA256(Convert.FromBase64String(clientSecret));
var signingCredentials = new SigningCredentials(
new SymmetricSecurityKey(hmac.Key), SecurityAlgorithms.HmacSha256Signature, SecurityAlgorithms.Sha256Digest);
var signKey = signingCredentials.SigningKey as SymmetricSecurityKey;
var encodedData = jwt.EncodedHeader + "." + jwt.EncodedPayload;
var compiledSignature = Encode(encodedData, signKey.Key);
//Validate the incoming jwt signature against the header and payload of the token
if (compiledSignature != jwt.RawSignature)
{
throw new Exception("Token signature validation failed.");
}
return jwt;
};
Encode helper method
public string Encode(string input, byte[] key)
{
HMACSHA256 myhmacsha = new HMACSHA256(key);
byte[] byteArray = Encoding.UTF8.GetBytes(input);
MemoryStream stream = new MemoryStream(byteArray);
byte[] hashValue = myhmacsha.ComputeHash(stream);
return Base64UrlEncoder.Encode(hashValue);
}

Categories