I have wired up the JwtAuthForWebAPI nuget project but I am not able to validate the generated tokens. I end up getting a 500 error. I am using the exact same key value for both token generation and also when configuring JwtAuthenticationMessageHandler.
This is the code to generate a token:
var tokenHandler = new JwtSecurityTokenHandler();
var symmetricKey = JsonWebTokenSecretKey.GetBytes();
var now = DateTime.UtcNow;
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(
new[]{
new Claim(JwtClaimKeys.Audience, SessionManager.Current.ApplicationId.ToString()),
new Claim(JwtClaimKeys.Subject, userLoginRequest.ApplicationInstanceId.ToString())
}),
TokenIssuerName = "My Company",
Lifetime = new Lifetime(now, now.AddMinutes(tokenLifetimeInMinutes)),
SigningCredentials = new SigningCredentials(
new InMemorySymmetricSecurityKey(symmetricKey),
"http://www.w3.org/2001/04/xmldsig-more#hmac-sha256",
"http://www.w3.org/2001/04/xmlenc#sha256")
};
tokenDescriptor.Subject.AddClaims(GetRoles(userLoginRequest));
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
This is the code to register the authentication handler:
var keyBuilder = new SecurityTokenBuilder();
var jwtHandler = new JwtAuthenticationMessageHandler
{
Issuer = "My Company",
AllowedAudience = ApplicationId.ToString(),
SigningToken = keyBuilder.CreateFromKey(JsonWebTokenSecretKey),
PrincipalTransformer = new MyUserPrincipleTransformer()
};
config.MessageHandlers.Add(jwtHandler);
This is the error I get:
{"Message":"An error has occurred.","ExceptionMessage":"IDX10503: Signature validation failed. Keys tried: 'System.IdentityModel.Tokens.InMemorySymmetricSecurityKey\r\n'.\nExceptions caught:\n ''.\ntoken: '{\"typ\":\"JWT\",\"alg\":\"HS256\"}.{\"aud\":\"1\",\"sub\":\"3\",\"role\":[\"User\",\"Admin\"],\"iss\":\"My Company\",\"exp\":1429547369,\"nbf\":1429543769}'","ExceptionType":"System.IdentityModel.SignatureVerificationFailedException",
"StackTrace":"
at System.IdentityModel.Tokens.JwtSecurityTokenHandler.ValidateSignature(String token, TokenValidationParameters validationParameters)\r\n
at System.IdentityModel.Tokens.JwtSecurityTokenHandler.ValidateToken(String securityToken, TokenValidationParameters validationParameters, SecurityToken& validatedToken)\r\n
at JwtAuthForWebAPI.JwtSecurityTokenHandlerAdapter.ValidateToken(IJwtSecurityToken securityToken, TokenValidationParameters validationParameters)\r\n
at JwtAuthForWebAPI.JwtAuthenticationMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)\r\n
at System.Web.Http.HttpServer.<SendAsync>d__0.MoveNext()"}
This is an example JSON token:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIxIiwic3ViIjoiMyIsInJvbGUiOlsiVXNlciIsIkFkbWluIl0sImlzcyI6Ik15IENvbXBhbnkiLCJleHAiOjE0Mjk1NTE4MjgsIm5iZiI6MTQyOTU0ODIyOH0.9wA_RBir9u7Cn_-Fy2T-Q_IDUfz6B928IEbIgXD9Bug
Interestingly, I am able to validate the token with my key using http://jwt.io. I suspect it may have something to do with the JwtAuthForWebAPI library looking at something different than what the System.Identity JWT library is generating?
this is Jamie (author of the JwtAuthForWebAPI package). The server config code - specifically, SecurityTokenBuilder.CreateFromKey(string) - assumes the given string is base64 encoded. It was either that, or assumptions or parameters are needed that would indicate which encoding to use for converting to a byte array. I chose to assume the string was base64 encoded. I'm sure there's a clearer way to go about converting the string key into a SecurityToken, but that's the way the code is today.
In SmokeTests.cs within the JwtAuthForWebAPI.SampleClient project, you can see that I used the Convert.FromBase64String() method, as opposed to using the GetBytes() method from an Encoding class:
public const string SymmetricKey = "YQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBuAG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6ADAAMQAyADMANAA1AA==";
// ...
var key = Convert.FromBase64String(SymmetricKey);
var credentials = new SigningCredentials(
new InMemorySymmetricSecurityKey(key),
"http://www.w3.org/2001/04/xmldsig-more#hmac-sha256",
"http://www.w3.org/2001/04/xmlenc#sha256");
Feel free to keep using your current token generation code, but on the server...
Please try specifying a base64 encoded version of JsonWebTokenSecretKey in the server configuration code. You can use a site like https://www.base64encode.org/ to encode it, or try code like this:
var base64key = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonWebTokenSecretKey));
var keyBuilder = new SecurityTokenBuilder();
var jwtHandler = new JwtAuthenticationMessageHandler
{
Issuer = "My Company",
AllowedAudience = ApplicationId.ToString(),
SigningToken = keyBuilder.CreateFromKey(base64key),
PrincipalTransformer = new MyUserPrincipleTransformer()
};
Let me know whether or not that works.
Also, I'm going to update the library to catch the SignatureVerificationFailedException exception and return a 401, as opposed to letting an internal server error happen. You'll still need to specify your key as a base64 string, but at least such configuration issues won't cause a 500 error.
Again, please let me know if that does the trick.
it's just my code sample base on #Jamie answer
protected string GetUsername(string token)
{
string secret = "keyyyyy!#3";
var key = Convert.FromBase64String(secret);
var IssuerSigningKey = new SymmetricSecurityKey(key);
IdentityModelEventSource.ShowPII = true;
var SigningCredentials = new SigningCredentials(
IssuerSigningKey,
SecurityAlgorithms.HmacSha256Signature);
var handler = new JwtSecurityTokenHandler();
var tokenSecure = handler.ReadToken(token) as SecurityToken;
var validations = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = IssuerSigningKey,
ValidateIssuer = false,
ValidateAudience = false
};
var claims = handler.ValidateToken(token, validations, out tokenSecure);
return claims.Identity.Name;
}
Related
Looking for the proper code to change a Google email group's owner...
what I have currently (not working). The credential/service are all fine, as I'm using them to do a bunch of other GoogleAPIs stuff which is working correctly. I'm just not sure whether I should be messing with a user or the group.
String serviceAccountEmail = "asdfasdfasf#asdfasdfsdfsdf-323423.iam.gserviceaccount.com";
var certificate = new X509Certificate2(#"c:\asdf\PasswordReset2.p12", "notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
User = "andy#asdfasdfa.com",
Scopes = new[] { DirectoryService.Scope.AdminDirectoryUser, DirectoryService.Scope.AdminDirectoryGroup }
}.FromCertificate(certificate));
var service = new DirectoryService
(
new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "jimmyjohn",
ApiKey = "asdkfjasl;dkjfaskdjfasdfasdf"
}
);
Group g = new Group();
g = service.Groups.Get(groupemail).Execute();
// NEED HELP HERE
service.Groups.Update(g, groupemail).Execute();
//Member newMember = new Member();
//newMember.Email = useremail;
//newMember.Role = "OWNER"; //MANAGER , OWNER
//newMember.Kind = "admin#directory#member";
//service.Members.Update(newMember, groupemail, useremail).Execute();```
I already had the answer, but due to a facepalm bug I was always telling it to set the owner to myself (in a previous function feeding this one), hence never seeing anything change in Google Groups...
Member newMember = new Member
{
Email = useremail,
Role = "OWNER" //MANAGER , OWNER
};
service.Members.Update(newMember, groupemail, useremail).Execute();
I have the following pre-request script that i am using to attempt to generate a JWT for Google Api - Google uses the RS256 encryption which is where I think I am getting stuck - the CryptoJS seems to support HmacSHA256 only - Any advise would be helpful:
Here's my pre-request script from Postman:
function base64url(source) {
// Encode in classical base64
encodedSource = CryptoJS.enc.Base64.stringify(source);
// Remove padding equal characters
encodedSource = encodedSource.replace(/=+$/, '');
// Replace characters according to base64url specifications
encodedSource = encodedSource.replace(/\+/g, '-');
encodedSource = encodedSource.replace(/\//g, '_');
return encodedSource;
}
function addIAT(request) {
var iat = Math.floor(Date.now() / 1000) - 100;
data.iat = iat;
return data;
}
function addEXP(request) {
var exp = Math.floor(Date.now() / 1000) + 3300;
data.exp = exp;
return data;
}
var header = {
"alg": "RS256",
"typ": "JWT",
"kid": "xxx"
};
var data = {
"iss": "xxx#iam.gserviceaccount.com",
"aud": "https://oauth2.googleapis.com/token",
"scope": "https://www.googleapis.com/auth/cloud-platform"
};
data = addIAT(data);
data = addEXP(data);
var privateKEY = "-----BEGIN PRIVATE KEY-----xxxxxxxxxxxxxxxLcGGNkna2Y3URoT0vDNneqzaasmJk4JZ97BcFLOulTihA49z8zmQKBgQDKX9AWS88cnfyiXSxtRNUFYvN4SzMDTJ1o59Gm6Sk77t7Ylfm+8PKA00SQeN7FuU/cbU4PkbAyzcp7eAGE3KXoLC/pWQ14srWGAsQkti1PmBo400ajRQReJPs3XxIl3yl4swlRgn+w9x6xy3CRnWrZRQSxRrtdDkBJcp7Lml+4mwKBgQCCeiTsktlMTOy9LqxOUMh6Lt7Z5jceNSwusW8Z4YVsewiSsRezufLBRcTywifgPOyUTP3S7etEfW2CKF0smpM0drfxd/3Ic7oKr7ESY5zwNcV7Q3NUGxaqGy8yoxEhKkLsYkOzYUdyNyfJd5Sh8yq7ICrX7/UGkVLOi44VrdaluQKBgC3kmH3V5zvoH/h6BK8q4tv72pa3BvSClVfK6mJdkbpDq0mWiTJh1bydLHlOz8YrBg9IwmEJetmqjXZ+emm01/LUwnC6fzGV5VBkpDJnFdNs/NVSJDy2VA09ebLO3oC0IOV8RGq1m1t4Tv+m0PpUpnxrCGtjTO4HY1DEq3okofxtAoGABjq4QegVIlImU5LSAEKgnUiwA1CHGW3+ZzfCczAv2VRfk/DSlYLmsxLRIfjsCVEo79NiVGyIsKmt5TJRxVLXp++ydKCEN/YRrjqEFgHNoH0rDCuV/IKAeN17/TYKphuebSX6mVsfo7GXI1kSoGJkDDnPKR4peiIF/YC9BTqQgIs=-----END PRIVATE KEY-----"
var secret = 'myjwtsecret';
// encode header
var stringifiedHeader = CryptoJS.enc.Utf8.parse(JSON.stringify(header));
var encodedHeader = base64url(stringifiedHeader);
// encode data
var stringifiedData = CryptoJS.enc.Utf8.parse(JSON.stringify(data));
var encodedData = base64url(stringifiedData);
//encode privatekey
//var stringifiedPrivatekey = CryptoJS.enc.Utf8.parse(JSON.stringify(privateKEY));
//var encodedPrivatekey = base64url(stringifiedPrivatekey);
// build token
var token = encodedHeader + "." + encodedData
// sign token
//var signature = CryptoJS.HmacSHA256(token, secret);
//signature = base64url(signature);
var signature = CryptoJS.RS256(token , privateKEY);
signedToken = base64url(signature);
var jwt = token + "." + signedToken
postman.setEnvironmentVariable("payload", jwt);
I have found this problem a couple of time in my projects so I decide to create an easy way to do this, here https://joolfe.github.io/postman-util-lib/ i have publish a ¨library¨ to easy do cryptographic operations like generate jwt, PKCE challenges... in ¨Pre-request¨ and ¨Tests¨ scripts in postman, have a look and contact me if you have any doubts.
Best Regards.
i would like to reconstruction my last project.
in past, i did't use any Web API.
can i just use the ODataQueryOptions to do $filter, $orderby , $top ,$skip
for my query in my own handler.ashx ?
some thing like.
var option = new ODataQueryOptions(request.params);
var query = option.ApplyTo(db.products);
Based on sfuqua's answer above I made my own helper class that builds OdataQueryOptions class based on Odata Uri:
using System.Linq;
using System.Net.Http;
using System.Web.Http.OData;
using System.Web.Http.OData.Builder;
using System.Web.Http.OData.Query;
namespace OdataHelpers
{
public static class ODataBuilder<T>
{
public static ODataQueryOptions<T> BuildOptions(string oDataUri)
{
var baseUri = "";
var odUri = "";
var spl = oDataUri.Split('?');
if (spl.Count() == 0)
odUri = spl[0];
else
{
baseUri = spl[0];
odUri = spl[1];
}
if (string.IsNullOrEmpty(baseUri))
baseUri = "http://localhost/api/" + typeof(T).Name;
var request = new HttpRequestMessage(HttpMethod.Get, baseUri + "?" + oDataUri.Replace("?", ""));
var modelBuilder = new ODataConventionModelBuilder();
modelBuilder.AddEntity(typeof(T));
var edmModel = modelBuilder.GetEdmModel();
var oDataQueryContext = new ODataQueryContext(edmModel, typeof(T));
return new ODataQueryOptions<T>(oDataQueryContext, request);
}
}
}
Example use:
var OdataStuff = ODataBuilder<CustomerIntView>.BuildOptions("$orderby=Id");
One way to accomplish this is by manually constructing the request URI and setting that in the request parameter of the ODataQueryOptions constructor. So this may not be precisely what the original poster was looking for (question needed some clarification).
In my case I have a unit test, and I wanted to validate that the odata options were being applied to my queryable object. In the following sample code, assume that you are testing a ProductController that has a ProductName field in it.
// Manually set an OData query parameter
const string restUrl = "http://localhost/api/product?$orderby=ProductName";
// Need to construct an HTTP Context and a Request, then inject them into the controller
var config = new HttpConfiguration();
var request = new HttpRequestMessage(HttpMethod.Post, restUrl);
var route = config.Routes.MapHttpRoute(WebApiConfig.DefaultRouteName, "api/{controller}/{id}");
var routeData = new HttpRouteData(route, new HttpRouteValueDictionary { { "controller", "Product" } });
var controller = new ProductController()
{
Request = request,
ControllerContext = new HttpControllerContext(config, routeData, request),
Url = new UrlHelper(request)
};
// Build up the OData query parameters
var modelBuilder = new ODataConventionModelBuilder();
modelBuilder.AddEntity(typeof(Product));
var edmModel = modelBuilder.GetEdmModel();
var oDataQueryContext = new ODataQueryContext(edmModel, typeof(Product));
var oDataQueryOptions = new ODataQueryOptions<Product>(oDataQueryContext, _controller.Request);
// Finally, call the controller
var result = controller.Get(oDataQueryOptions);
I do think you can if you can constructor an instance of ODataQueryOptions.
But, What's this:
var option = new ODataQueryOptions(request.params);
Web API doesn't provide such constructor. Is it your own implementation?
Thanks.
i try to send private message to followers of a user who is already authenticated with my_app, here is the code :
var authent = new MvcAuthorizer
{
Credentials = new SessionStateCredentials()
{
ConsumerKey = this.client.ConsumerKey,
ConsumerSecret = this.client.ConsumerSecret,
OAuthToken = identity.Token.Token
}
};
var twitterCtx = new TwitterContext(authent);
list_friend.ToList().ForEach(x => twitterCtx.NewDirectMessage(x.InvitedFriendID, messageWithPlaceHolders.Replace("[FRIEND_NAME]", x.Name)));
list_friend is the list of followers of the user who is authenticated.
Pleaaaase i need your help.
the solution is to use the InMemoryCrendentials rather than SessionStateCredentials and add the token secret to crendential, and after we should add DateTime.Now to the message because twitter don't allow duplicate message, here is the code off the solution it work well :
var authent = new MvcAuthorizer
{
Credentials = new InMemoryCredentials()
{
ConsumerKey = this.client.ConsumerKey,
ConsumerSecret = this.client.ConsumerSecret,
OAuthToken = identity.Token.Token,
AccessToken = identity.Token.Secret
}
};
var twitterCtx = new TwitterContext(authent);
list_friend.ToList().ForEach(x => twitterCtx.NewDirectMessage(x.SocialId, messageWithPlaceHolders.Replace("[FRIEND_NAME]", x.Name) +DateTime.Now.ToString()));
Thanks
I am making a request to a service and getting a response. Service works fine and I am deserializing an object without a problem.
Below is an example of my code. The problem is the result object is null at the end. I do not know why am I losing a reference. What is the proper solution?
HttpWebRequest hwrq = (HttpWebRequest)WebRequest.Create("http://service.svc/Login");
hwrq.ContentType = "application/x-www-form-urlencoded; encoding='utf-8'";
hwrq.Accept = "text/xml";
hwrq.Method = "POST";
Users result = null; // object initializaiton
hwrq.BeginGetRequestStream(ar =>
{
var requestStream = hwrq.EndGetRequestStream(ar);
using (var sw = new StreamWriter(requestStream, System.Text.Encoding.UTF8))
{
sw.Write("Username Password");
sw.Close();
}
hwrq.BeginGetResponse(a =>
{
var response = hwrq.EndGetResponse(a);
var responseStream = response.GetResponseStream();
using (var sr = new StreamReader(responseStream))
{
returnedXML = sr.ReadToEnd();
XmlSerializer xds = new XmlSerializer(typeof(Users));
byte[] byteArray = Encoding.UTF8.GetBytes(returnedXML);
MemoryStream stream = new MemoryStream(byteArray);
result = (Users)xds.Deserialize(stream); // object is correct
}
responseStream.Close();
response.Close();
}, null);
}, null);
return result; // object is null!
Just like MarcinJuraszek suggested, the proper way is to make a callback and handle the results there.