I created a test application with the identity server.
It is very simple. it has some hard coded InMemory Users,Clients and SCopes and uses the idsrv3test.pfx certificated from the samples for signing
var factory = new IdentityServerServiceFactory();
factory
.UseInMemoryUsers(MemoryUsers.All())
.UseInMemoryClients(MemoryUsers.GetClients())
.UseInMemoryScopes(MemoryUsers.GetScopes());
var cert = new X509Certificate2(#"..\certs\idsrv3test.pfx", "idsrv3test");
var options = new IdentityServerOptions()
{
Factory = factory,
EnableWelcomePage = true,
SigningCertificate = cert,
RequireSsl = false
};
app.UseIdentityServer(options);
Now I get a a token via the connect/token endpoint. as grant type I use password.
This succeeds and I got a bearer token back.
now I wanted to validated the token contents on jwt.io . I shows me all the informations of all parts of the token. but at the end of the site it shows me "invalid signature"
Is this the result of a bug ? Or just a result that I use this test certificate?
Jwt.io cannot validate RS256 signatures. Only HS256.
Related
This follows on from a previous post which started as a general issue and is now more specific.
In short, I've been following guidance (such as this from Microsoft, this from Scott Hanselman, and this from Barry Dorrans) to allow me to share the authentication cookie issued by a legacy ASP.NET web app with a new dotnet core app running on the same domain.
I'm confident that I'm using the recommended Microsoft.Owin.Security.Interop library correctly. On that side (the old ASP.NET app), the CookieAuthenticationOptions are configured with AuthenticationType and CookieName both set to the same value - SiteIdentity. This same value is also used in the interop data protector setup:
var appName = "SiteIdentity";
var encryptionSettings = new AuthenticatedEncryptorConfiguration
{
EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
};
var interopProvider = DataProtectionProvider.Create(
new DirectoryInfo(keyRingSharePath),
builder =>
{
builder.SetApplicationName(appName);
builder.SetDefaultKeyLifetime(TimeSpan.FromDays(365 * 20));
builder.UseCryptographicAlgorithms(encryptionSettings);
if (!generateNewKey)
{
builder.DisableAutomaticKeyGeneration();
}
});
ShimmedDataProtector = new DataProtectorShim(
interopProvider.CreateProtector(
"Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware",
appName,
"v2"));
I log in using this app, confirm I have a cookie named SiteIdentity then switch to a new dotnet core app running on the same domain.
There, without adding authentication middleware I can confirm that I can unprotect and deserialize the cookie. I do this by setting up data protection in Startup to match the other app:
var appName = "SiteIdentity";
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(keyRingSharePath))
.SetDefaultKeyLifetime(TimeSpan.FromDays(365 * 20))
.DisableAutomaticKeyGeneration()
.UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration()
{
EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
})
.SetApplicationName(appName);
Then in my controller I can use a data protector to manually unprotect the cookie:
var appName = "SiteIdentity";
var protector = _dataProtectionProvider.CreateProtector(
"Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware",
appName,
"v2");
var cookieValue = Request.Cookies[appName];
var format = new TicketDataFormat(protector);
var ticket = format.Unprotect(cookieValue);
I can confirm that ticket.Principal does indeed reference a claims principal representing the account which I signed in with on the other app.
However, I've found it impossible to wire up the cookie authentication middleware to properly protect my endpoints using this cookie. This is what I've added to Startup, after the data protection code above:
var protectionProvider = services.BuildServiceProvider().GetService<IDataProtectionProvider>();
var dataProtector = protectionProvider.CreateProtector(
"Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware",
appName,
"v2");
services
.AddAuthentication(appName)
.AddCookie(appName, options =>
{
options.TicketDataFormat = new TicketDataFormat(dataProtector);
options.Cookie.Name = appName;
});
By my understanding this is telling the middleware that I have an authentication scheme named "SiteIdentity" (the advice is that authentication scheme must match the ASP.NET authentication type) which expects a cookie also called "SiteIdentity" which will contain protected data that the supplied data protector can interpret.
But when I add the attribute [Authorize(AuthenticationSchemes = "SiteIdentity")] to my controller I'm kicked away to a login page.
I can't understand what I'm doing wrong. As I've shown, I can confirm that it is indeed possible to use this data protector and ticket format to interpret the authentication cookie, so I guess I must have something wrong in this middleware wiring, but I'm not sure what.
Please ignore. It turns out that my code is actually correct. I had been working on this solution for long enough that the session represented by the cookie value I was using to test had expireed. Will leave this question here in case the code benefits anyone trying to achieve the same.
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/
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"))
};
I have a scenario where I have an ASP.Net application that authenticates using the Thinktecture IdentityServer. This all works fine, it has a relationship with our ADFS and that is all working great. What I need though is to call the ShareFile-NET SDK and authenticate using the below sample code..
//SAML Authentication: This authentication support assumes you have a mechanism for obtaining a SAML assertion, samlAssertion from the user's IdP.
var sfClient = new ShareFileClient("https://secure.sf-api.com/sf/v3/");
var oauthService = new OAuthService(sfClient, "[clientid]", "[clientSecret]");
var oauthToken = await oauthService.ExchangeSamlAssertionAsync(samlAssertion,
subdomain, applicationControlPlane);
sfClient.AddOAuthCredentials(oauthToken);
sfClient.BaseUri = oauthToken.GetUri();
So I have the IdP, but I have not had any luck researching how exactly to make use of the token it has provided me to create that "samlAssertion" parameter..
I have found the answer to this.
The SAML assertion can be found in the ClaimsIdentity
var icp = System.Security.Claims.ClaimsPrincipal.Current;
var claimsIdentity = icp.Identity as System.Security.Claims.ClaimsIdentity;
var token = claimsIdentity.BootstrapContext as System.IdentityModel.Tokens.BootstrapContext;
For this to be populated you need to add the following to the web.config:
<identityConfiguration saveBootstrapContext="true">
I have some questions related to Bearer Token. In Owin you can protect a ticket Protect(ticket) like this:
ClaimsIdentity identity = new ClaimsIdentity(Startup.OAuthServerOptions.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, user.UserName));
Dictionary<string, string> properties = new Dictionary<string, string>();
properties.Add("UserId", user.Id);
properties.Add("UserName", user.UserName);
properties.Add("Role", "user");
AuthenticationProperties properties = new AuthenticationProperties(properties);
AuthenticationTicket ticket = new AuthenticationTicket(identity, properties);
DateTime currentUtc = DateTime.UtcNow;
DateTime expireUtc = currentUtc.Add(TimeSpan.FromHours(24));
ticket.Properties.IssuedUtc = currentUtc;
ticket.Properties.ExpiresUtc = expireUtc;
string token = OAuthAuthorizationServerOptions.AccessTokenFormat.Protect(ticket)
Now the token will be something like this:
nqak-9R6U64Owsm_lqn_mJzKc_Djd8iVnIw0EX77v5x2rybhf4m_zg_UnrsoO5BxDZQl0HWrSvvd4efa4ChNSf5rAGhd13aOXZlvwOJOZ5v_9bhRCq8A7tqHyiM6DqVVOyYs3lh2SU-wU1m85HH2IcYDtdTY3ijaKZ_QnP1nsqO5LRnnEL4upbETPW9zqWIZzZBX7_Y2cXi2v0K7WnlRor3gFKIZlU9J-NfidRpWXqq5744NfWWHalYADGS7eUWyuxPJCj9ykHYzaXFksJEXBw
My questions:
How this token is generated/encrypted?
Are there any chances that somebody can try to mess'up with the token and add some custom claims to it?
Example:
If you have the token string you can do this:
AuthenticationTicket ticket = OAuthAuthorizationServerOptions.AccessTokenFormat.Unprotect(token);
Now you can add custom claims to it. For example if there is a role claim with value user then you can modify that claim and add admin then re encode the ticket and you get a token that has admin role.
I actually din some tests, encoded a token on a server and then try to modify it on another system but I couldn't Unprotect it. Therefore I am thinking maybe the ticket is encrypted/decrypted using the machine key on which was originally created. However if I try to Unprotect it from the same machine it works. I can decrypt it and modify it.
Can somebody explain this process please?
How this token is generated/encrypted?
The data protection provider can be set using the SetDataProtectionProvider extension method on the IAppBuilder object. When this is not done, the data protection provider of the host is used. In case of IIS + ASP.NET, this is MachineKeyDataProtector in the assembly Microsoft.Owin.Host.SystemWeb. For self-hosting, this will be DPAPI. Basically, the token is encrypted and then MACed and that is what Protect() is all about.
Are there any chances that somebody can try to mess'up with the token and add some custom > claims to it?
No. This is not possible. Token protected in a machine cannot be unprotected somewhere else. An exception to that will be the case of a web farm where you have multiple machines. One machine can protect and if the subsequent request goes to some other machine, that machine should have the ability to unprotect. With DPAPI, this is not possible. With MachineKeyDataProtector, this is possible by having the same machineKey section in all the machines. But then if you are concerned about some MITM being able to do this, then no, it is not possible.