This is a simplified test for JwtSecurityTokenHandler 4.0.0 in Linqpad. The code works well with JwtSecurityTokenHandler 3.0.2, the token is generated and validated. In 4.0.0, after the necessary changes, I keep getting SecurityTokenSignatureKeyNotFoundException: IDX10500: Signature validation failed. Unable to resolve SecurityKeyIdentifier. Obviously something has changed or I am doing something wrong and the new version is more strict. Any suggestions?
string jwtIssuer = "issuer";
string jwtAudience = "audience";
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
X509Certificate2 cert = store.Certificates.OfType<X509Certificate2>().FirstOrDefault( c => c.SubjectName.Name.Equals("CN=DEV_CERT", StringComparison.OrdinalIgnoreCase));
store.Close();
// Token generation and signing
X509SigningCredentials signingCredentials = new X509SigningCredentials(cert);
JwtSecurityTokenHandler jwtHandler = new JwtSecurityTokenHandler();
IList<System.Security.Claims.Claim> payloadClaims = new List<System.Security.Claims.Claim>() {
new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.Name , "name"),
};
#if JWT302
Lifetime lifetime = new Lifetime(DateTime.UtcNow, DateTime.UtcNow.AddSeconds(24*60*60));
JwtSecurityToken jwt = new JwtSecurityToken( jwtIssuer, jwtAudience, payloadClaims, lifetime, signingCredentials);
#else
JwtSecurityToken jwt = new JwtSecurityToken( jwtIssuer, jwtAudience, payloadClaims, DateTime.UtcNow, DateTime.UtcNow.AddSeconds(24*60*60), signingCredentials);
#endif
string token = jwtHandler.WriteToken(jwt);
// Token validation
var signingToken = new RsaSecurityToken((RSACryptoServiceProvider)cert.PublicKey.Key);
JwtSecurityTokenHandler jwtHandler2 = new JwtSecurityTokenHandler();
#if JWT302
TokenValidationParameters vp = new TokenValidationParameters() {
AllowedAudience = jwtAudience,
ValidIssuer = jwtIssuer,
ValidateIssuer = true
,SigningToken = signingToken
};
var principal = jwtHandler2.ValidateToken(token, vp);
#else
TokenValidationParameters vp = new TokenValidationParameters() {
ValidAudience = jwtAudience,
ValidIssuer = jwtIssuer,
ValidateIssuer = true
,IssuerSigningToken = signingToken
};
SecurityToken validatedToken;
var principal = jwtHandler2.ValidateToken(token, vp, out validatedToken);
#endif
This exception is thrown if:
The jwt has a 'kid'
The runtime was unable to match any of the SigningTokens.
While we investigate the issue, you can use the delegate TokenValidationParameters.IssuerSigningKeyResolver to directly return the signing key to use when checking the signature.
To achieve this set: TokenValidationParameters.IssuerSigningkeyResolver to a function that will return the same key that you set above in TokenValidationParameters.SigningToken. The purpose of this delegate is to instruct the runtime to ignore any 'matching' semantics and just try the key.
If the signature validation still fails, it may be a key issue.
If the signature validation doesn't fail, the runtime may need a fix.
If you can provide us with a jwt signed with that public key, that would help us make a fix.
thanks for giving us a try, sorry for the hassle.
Sorry you're experiencing issues. We will get some more eyes on the above to see what might be wrong. In the meanwhile, I suggest taking a look to https://github.com/AzureADSamples/WebAPI-ManuallyValidateJwt-DotNet and in particular global.asax.cs - that's the sample where we feature raw use of the JWT handler.
HTH
V.
Related
I am unable to use ConfidentialClientApplicationBuilder with userTokenCache.
Code in samples look something like this but this code is obsolete now and I am supposed to use ConfidentialClientApplicationBuilder.
ConfidentialClientApplication app;
var request = httpContext.Request;
var currentUri = UriHelper.BuildAbsolute(request.Scheme, request.Host, request.PathBase, _azureAdOptions.CallbackPath ?? string.Empty);
var credential = new ClientCredential(_azureAdOptions.ClientSecret);
TokenCache userTokenCache = _tokenCacheProvider.GetCache(httpContext, claimsPrincipal, authenticationProperties, signInScheme);
string authority = $"{_azureAdOptions.Instance}{_azureAdOptions.TenantId}/";
app = new ConfidentialClientApplication(_azureAdOptions.ClientId, authority, currentUri, credential, userTokenCache, null);
return app;
ConfidentialClientApplicationBuilder Code
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(_azureAdOptions.ClientId)
.WithAuthority(authority)
.WithRedirectUri(currentUri)
.WithCertificate(clientCertificate)
.Build();
Its done a bit differently now.
You initialize the TokenCache implementation separately and attach it to the app object. see this line for reference.
Its highly recommended you study how the Token Cache is best implemented for MSAL. The TokenCacheProviders folder has the implementations.
I am using Asp .net core 2.1
How to add custom claims after login?
in order to add custom claims after login, you can use the AddClaim method over a new ClaimsIdentity instance
var claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.Name, "some Name"));
claims.Add(new Claim(ClaimTypes.Email, "abc#xyz.com"));
var identity = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie);
var ctx = Request.GetOwinContext();
var authenticationManager = ctx.Authentication;
authenticationManager.SignIn(identity);
Then to retrieve the claims, you can use the LINQ over the ClaimsPrincipal instance:
var identity = (ClaimsPrincipal)Thread.CurrentPrincipal;
string email = identity.Claims.Where(c => c.Type == ClaimTypes.Email).Select(c => c.Value).SingleOrDefault();
Update:
While trying to explore and fix the issue myself, I found this answer which works as an alternate solution to your problem.
I need to get the token from a user right after of being generated. I make the request to my endpoint and I get the response with the token but throughout HTTP but I want at runtime if it is possible! Thats the base code.
if (!String.IsNullOrEmpty(hash))
{
Claim claim = new Claim(ClaimTypes.Name, hash);
Claim gg = new Claim(ClaimTypes.Role, "Role");
Claim[] claims = new Claim[] { claim, gg };
ClaimsIdentity claimsIdentity = new ClaimsIdentity(
claims, OAuthDefaults.AuthenticationType);
await base.OnGrantCustomExtension(c);
c.Validated(claimsIdentity);
}
Override the method TokenEndpointResponse(OAuthTokenEndpointResponseContext context) in your OAuthAuthorizationServerProvider
Im trying to sign in a user in web api without using their Username/Password combination. I have access to the User object for the user but need to "log them in" and return the access token to the client application for subsequent requests.
I've tried variations on the following but with no luck, the UserManager object is disposed as soon as I call GenerateUserIdentityAsync the first time which causes it to fail for the cookiesIdentity and its warning me that my cast OAuthGrantResourceOwnerContextCredentials is a "Suspicious type conversion or check" but the code never reaches that line anyway; this is what Ive tried, which was taken and modified from the GrantResourceOwnerCredentials method of my ApplicationOAuthProvider class. Incidentally my Token end point works perfectly with the usual username, password and grant_type request.
var user = // Super secret way of getting the user....;
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
// UserManager is not null at this point
var oAuthIdentity = await user.GenerateUserIdentityAsync(UserManager,
OAuthDefaults.AuthenticationType);
// UserManager is null at this point and so throws exception
var cookiesIdentity = await user.GenerateUserIdentityAsync(UserManager,
CookieAuthenticationDefaults.AuthenticationType);
var properties = ApplicationOAuthProvider.CreateProperties(user.UserName);
var ticket = new AuthenticationTicket(oAuthIdentity, properties);
((OAuthGrantResourceOwnerCredentialsContext)HttpContext.Current.GetOwinContext().Request.Context)
.Validated(ticket);
HttpContext.Current.GetOwinContext().Request.Context.Authentication.SignIn(cookiesIdentity);
In essence all I want to do is return an access token for a user for which I do not have the username and password but a "secret" that I want to use instead of username password. Is there a way?
OK so after much digging I found this article that helped me put together this code which works like a charm:
var user = // Super secret method of getting the user
var tokenExpiration = TimeSpan.FromDays(1);
ClaimsIdentity identity = new ClaimsIdentity(OAuthDefaults.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, user.UserName));
identity.AddClaim(new Claim("role", "user"));
var props = new AuthenticationProperties()
{
IssuedUtc = DateTime.UtcNow,
ExpiresUtc = DateTime.UtcNow.Add(tokenExpiration),
};
var ticket = new AuthenticationTicket(identity, props);
var accessToken = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket);
JObject tokenResponse = new JObject(
new JProperty("userName", user.UserName),
new JProperty("access_token", accessToken),
new JProperty("token_type", "bearer"),
new JProperty("expires_in", tokenExpiration.TotalSeconds.ToString()),
new JProperty(".issued",
ticket.Properties.IssuedUtc.GetValueOrDefault().DateTime.ToUniversalTime()),
new JProperty(".expires",
ticket.Properties.ExpiresUtc.GetValueOrDefault().DateTime.ToUniversalTime()));
return tokenResponse;
I've been doing a lot tinkering around with the authentication stuff using the .NET libraries provided by Google.
We have both a desktop and web-app side, and what we want to achieve is to authenticate ONCE, either on the desktop or the web side, and store the refresh token, and reuse it both on the web side and the desktop side.
So the situation is like so, on the desktop side, when there's no saved existing AccessToken's and RefreshToken's, we will ask the user to authenticate via this code:
using (var stream = new FileStream("client_secrets_desktop.json", FileMode.Open, FileAccess.Read))
{
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(GoogleClientSecrets.Load(stream).Secrets,
new[] { GmailService.Scope.GmailReadonly, GmailService.Scope.GmailCompose },
"someemail#gmail.com", CancellationToken.None);
}
In this case the Client ID and Secret is of an Application type Installed Application.
On the web-application side, if there's also no refresh token yet then I'm using DotNetOpenAuth to trigger the authentication, here's the code snippet:
const string clientID = "someclientid";
const string clientSecret = "somesecret";
const string redirectUri = "http://localhost/Home/oauth2callback";
AuthorizationServerDescription server = new AuthorizationServerDescription
{
AuthorizationEndpoint = new Uri("https://accounts.google.com/o/oauth2/auth"),
TokenEndpoint = new Uri("https://accounts.google.com/o/oauth2/token"),
ProtocolVersion = ProtocolVersion.V20
};
public ActionResult AuthenticateMe()
{
List<string> scope = new List<string>
{
GmailService.Scope.GmailCompose,
GmailService.Scope.GmailReadonly,
GmailService.Scope.GmailModify
};
WebServerClient consumer = new WebServerClient(server, clientID, clientSecret);
// Here redirect to authorization site occurs
OutgoingWebResponse response = consumer.PrepareRequestUserAuthorization(
scope, new Uri(redirectUri));
response.Headers["Location"] += "&access_type=offline&approval_prompt=force";
return response.AsActionResult();
}
public void oauth2callback()
{
WebServerClient consumer = new WebServerClient(server, clientID, clientSecret);
consumer.ClientCredentialApplicator =
ClientCredentialApplicator.PostParameter(clientSecret);
IAuthorizationState grantedAccess = consumer.ProcessUserAuthorization(null);
string accessToken = grantedAccess.AccessToken;
}
Here is where I want to confirm my suspicions.
When there is a RefreshToken that exists, we use the following code snippet to call the Gmail API's
UserCredential uc = new UserCredential(flow, "someemail#gmail.com", new TokenResponse()
{
AccessToken = "lastaccesstoken",
TokenType = "Bearer",
RefreshToken = "supersecretrefreshtoken"
});
var refreshState = await uc.RefreshTokenAsync(CancellationToken.None);
var svc = new GmailService(new BaseClientService.Initializer()
{
HttpClientInitializer = uc,
ApplicationName = "Gmail Test",
});
Here's the thing I noticed is that, for me to be able to use the refresh token to refresh from either the desktop or the web side, the refresh token needs to be generated through the same client ID/secret combination. I've tested it and it seems like it's fine if we use Installed application as the application type for the Client ID for both the desktop and the web, but my question I guess is, these application type's for the client IDs, do they matter so much?
Am I doing anything wrong to do it this way?
Thanks in advance