IdentityServer3 idsrv.partial cookie gets too big - identityserver3

After login when redirecting the user using context.AuthenticateResult = new AuthenticateResult(<destination>, subject, name, claims) the partial cookie gets so big that it contains up to 4 chunks and ends up causing "request too big" error.
The number of claims is not outrageous (in the 100 range) and I haven't been able to consistently reproduce this on other environments, even with larger number of claims. What else might be affecting the size of this cookie payload?
Running IdSrv3 2.6.1

I assume that you are using some .NET Framework clients, because all of these problems are usually connected with the Microsoft.Owin middleware, that has some encryption that causes the cookie to get this big.
The solution for you is again part of this middleware. All of your clients (using the Identity Server as authority) need to have a custom IAuthenticationSessionStore imlpementation.
This is an interface, part of Microsoft.Owin.Security.Cookies.
You need to implement it according to whatever store you want to use for it, but basically it has the following structure:
public interface IAuthenticationSessionStore
{
Task RemoveAsync(string key);
Task RenewAsync(string key, AuthenticationTicket ticket);
Task<AuthenticationTicket> RetrieveAsync(string key);
Task<string> StoreAsync(AuthenticationTicket ticket);
}
We ended up implementing a SQL Server store, for the cookies. Here is some example for Redis Implementation, and here is some other with EF DbContext, but don't feel forced to use any of those.
Lets say that you implement MyAuthenticationSessionStore : IAuthenticationSessionStore with all the values that it needs.
Then in your Owin Startup.cs when calling:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies",
SessionStore = new MyAuthenticationSessionStore()
CookieName = cookieName
});
By this, as the documentation for the IAuthenticationSessionStore SessionStore property says:
// An optional container in which to store the identity across requests. When used,
// only a session identifier is sent to the client. This can be used to mitigate
// potential problems with very large identities.
In your header you will have only the session identifier, and the identity itself, will be read from the Store that you have implemented

Related

ASP.NET Core 5 route redirection

We have an ASP.NET Core 5 Rest API where we have used a pretty simple route:
[Route("api/[controller]")]
The backend is multi-tenant, but tenant-selection has been handled by user credentials.
Now we wish to add the tenant to the path:
[Route("api/{tenant}/{subtenant}/[controller]")]
This makes cross-tenant queries simpler for tools like Excel / PowerQuery, which unfortunately tend to store credentials per url
The problem is to redirect all existing calls to the old route, to the new. We can assume that the missing pieces are available in the credentials (user-id is on form 'tenant/subtenant/username')
I had hope to simply intercept the route-parsing and fill in the tenant/subtenant route values, but have had not luck so far.
The closes thing so far is to have two Route-attributes, but that unfortunately messes up our Swagger documentation; every method will appear with and without the tenant path
If you want to transparently change the incoming path on a request, you can add a middleware to set Path to a new value, for example:
app.Use(async (context,next) =>
{
var newPath = // Logic to determine new path
// Rewrite and continue processing
context.Request.Path = newPath;
await next();
});
This should be placed in the pipeline after you can determine the tenant and before the routing happens.

How to secure REST API from replay attacks with parameter manipulation?

I am developing secure payment APIs, and I want to avoid replay attacks with manipulation of the parameters in the url. For example in the following API call:
https://api.payment.com/wallet/transfer?from_account=123&to_account=456&amount=100
Once this API call is executed, someone with enough knowledge can execute the same API call by modifying any of the three parameters to his/her own advantage. I have thought of issuing a temporary token (transaction token) for each transaction. But this also doesn't sounds like enough.
Can anyone suggest the best way to mitigate replay attacks with parameters tampering?
THE API SERVER
I am developing secure payment APIs, and I want to avoid replay attacks with manipulation of the parameters in the url.
Before we dive into addressing your concerns it's important to first clarify a common misconception among developers, that relates to knowing the difference between who vs what is accessing the API server.
The difference between who and what is accessing the API server.
This is discussed in more detail in this article I wrote, where we can read:
The what is the thing making the request to the API server. Is it really a genuine instance of your mobile app, or is it a bot, an automated script or an attacker manually poking around your API server with a tool like Postman?
The who is the user of the mobile app that we can authenticate, authorize and identify in several ways, like using OpenID Connect or OAUTH2 flows.
If the quoted text is not enough for you to understand the differences, then please go ahead and read the entire section of the article, because without this being well understood you are prone to apply less effective security measures in your API server and clients.
SECURITY LAYERS AND PARAMETERS IN THE URL
For example in the following API call:
https://api.payment.com/wallet/transfer?from_account=123&to_account=456&amount=100
Security is all about applying as many layers of defence as possible in order to make the attack as harder and laborious as possible, think of it as the many layers in an onion you need to peel to arrive to the center one.
Attackers will always look for the most easy targets, the lower hanging fruit in the tree, because they don't want to resort to use a ladder when they can take the fruit from another tree with lower hanging fruit ;)
So one of the first layers of defense is to avoid using parameters in the url for sensitive calls, thus I would use a POST request with all the parameters in the body of the request, because this type of request cannot be done by simply copy paste the url into the browser or any other tool, thus they require more effort and knowledge to be performed, aka the fruit is more high in the tree for the attacker.
Another reason is that GET requests end up in the logs of the servers, thus can be accidentally exposed and easily replayed.
REPLAY ATTACKS FOR API CALLS
Once this API call is executed, someone with enough knowledge can execute the same API call by modifying any of the three parameters to his/her own advantage.
Yes they can, and they can learn how your API works even if you don't have public documentation for it, they just need to reveres engineer it with the help of any open source tool for mobile apps and web apps.
I have thought of issuing a temporary token (transaction token) for each transaction. But this also doesn't sounds like enough.
Yes it's not enough because this temporary token can be stolen via a MitM attack, just like a show in the article Steal That Api Key With a Man in the Middle Attack:
So, in this article you will learn how to setup and run a MitM attack to intercept https traffic in a mobile device under your control, so that you can steal the API key. Finally, you will see at a high level how MitM attacks can be mitigated.
So after performing the MitM attack to steal the token it's easy to use curl, Postman or any other similar tool to make the requests to the API server just like if you are the genuine who and what the API server expects.
MITIGATE REPLAY ATTACKS
Improving on Existing Security Defence
I have thought of issuing a temporary token (transaction token) for each transaction. But this also doesn't sounds like enough.
This approach is good but not enough as you alreay noticed, but you can improve it, if not have done it already, by making this temporary token usable only one time.
Another important defence measure is to not allow the requests with same amount and same recipients(from_account, to_account) be repeated in sequence, even if they have a new temporary token.
Also don't allow requests from the same source to be made to fast, specially if they are intended to come from human interactions.
This measures on their own will not totally solve the issue, but add some more layers into the onion.
Using HMAC for the One Time Token
In order to try to help the server to be confident about who and what is making the request you can use a Keyed-Hash Message Authentication Code (HMAC) which is designed to prevent hijacking and tampering, and as per Wikipedia:
In cryptography, an HMAC (sometimes expanded as either keyed-hash message authentication code or hash-based message authentication code) is a specific type of message authentication code (MAC) involving a cryptographic hash function and a secret cryptographic key. As with any MAC, it may be used to simultaneously verify both the data integrity and the authenticity of a message.
So you could have the client creating an HMAC token with the request url, user authentication token, your temporary token, and the time stamp that should be also present in a request header. The server would then grab the same data from the request and perform it's own calculation of the HMAC token, and only proceed with the request if it's own result matches the one for the HMAC token header in the request.
For a practical example of this in action you can read part 1 and part 2 of this blog series about API protection techniques in the context of a mobile app, that also features a web app impersonating the mobile app.
So you can see here how the mobile app calculates the HMAC, and here how the Api server calculates and validates it. But you can also see here how the web app fakes the HMAC token to make the API server think that the requests is indeed from who and what it expects to come from, the mobile app.
Mobile App Code::
/**
* Compute an API request HMAC using the given request URL and authorization request header value.
*
* #param context the application context
* #param url the request URL
* #param authHeaderValue the value of the authorization request header
* #return the request HMAC
*/
private fun calculateAPIRequestHMAC(url: URL, authHeaderValue: String): String {
val secret = HMAC_SECRET
var keySpec: SecretKeySpec
// Configure the request HMAC based on the demo stage
when (currentDemoStage) {
DemoStage.API_KEY_PROTECTION, DemoStage.APPROOV_APP_AUTH_PROTECTION -> {
throw IllegalStateException("calculateAPIRequestHMAC() not used in this demo stage")
}
DemoStage.HMAC_STATIC_SECRET_PROTECTION -> {
// Just use the static secret to initialise the key spec for this demo stage
keySpec = SecretKeySpec(Base64.decode(secret, Base64.DEFAULT), "HmacSHA256")
Log.i(TAG, "CALCULATE STATIC HMAC")
}
DemoStage.HMAC_DYNAMIC_SECRET_PROTECTION -> {
Log.i(TAG, "CALCULATE DYNAMIC HMAC")
// Obfuscate the static secret to produce a dynamic secret to initialise the key
// spec for this demo stage
val obfuscatedSecretData = Base64.decode(secret, Base64.DEFAULT)
val shipFastAPIKeyData = loadShipFastAPIKey().toByteArray(Charsets.UTF_8)
for (i in 0 until minOf(obfuscatedSecretData.size, shipFastAPIKeyData.size)) {
obfuscatedSecretData[i] = (obfuscatedSecretData[i].toInt() xor shipFastAPIKeyData[i].toInt()).toByte()
}
val obfuscatedSecret = Base64.encode(obfuscatedSecretData, Base64.DEFAULT)
keySpec = SecretKeySpec(Base64.decode(obfuscatedSecret, Base64.DEFAULT), "HmacSHA256")
}
}
Log.i(TAG, "protocol: ${url.protocol}")
Log.i(TAG, "host: ${url.host}")
Log.i(TAG, "path: ${url.path}")
Log.i(TAG, "Authentication: $authHeaderValue")
// Compute the request HMAC using the HMAC SHA-256 algorithm
val hmac = Mac.getInstance("HmacSHA256")
hmac.init(keySpec)
hmac.update(url.protocol.toByteArray(Charsets.UTF_8))
hmac.update(url.host.toByteArray(Charsets.UTF_8))
hmac.update(url.path.toByteArray(Charsets.UTF_8))
hmac.update(authHeaderValue.toByteArray(Charsets.UTF_8))
return hmac.doFinal().toHex()
}
API server code:
if (DEMO.CURRENT_STAGE == DEMO.STAGES.HMAC_STATIC_SECRET_PROTECTION) {
// Just use the static secret during HMAC verification for this demo stage
hmac = crypto.createHmac('sha256', base64_decoded_hmac_secret)
log.info('---> VALIDATING STATIC HMAC <---')
} else if (DEMO.CURRENT_STAGE == DEMO.STAGES.HMAC_DYNAMIC_SECRET_PROTECTION) {
log.info('---> VALIDATING DYNAMIC HMAC <---')
// Obfuscate the static secret to produce a dynamic secret to use during HMAC
// verification for this demo stage
let obfuscatedSecretData = base64_decoded_hmac_secret
let shipFastAPIKeyData = new Buffer(config.SHIPFAST_API_KEY)
for (let i = 0; i < Math.min(obfuscatedSecretData.length, shipFastAPIKeyData.length); i++) {
obfuscatedSecretData[i] ^= shipFastAPIKeyData[i]
}
let obfuscatedSecret = new Buffer(obfuscatedSecretData).toString('base64')
hmac = crypto.createHmac('sha256', Buffer.from(obfuscatedSecret, 'base64'))
}
let requestProtocol
if (config.SHIPFAST_SERVER_BEHIND_PROXY) {
requestProtocol = req.get(config.SHIPFAST_REQUEST_PROXY_PROTOCOL_HEADER)
} else {
requestProtocol = req.protocol
}
log.info("protocol: " + requestProtocol)
log.info("host: " + req.hostname)
log.info("originalUrl: " + req.originalUrl)
log.info("Authorization: " + req.get('Authorization'))
// Compute the request HMAC using the HMAC SHA-256 algorithm
hmac.update(requestProtocol)
hmac.update(req.hostname)
hmac.update(req.originalUrl)
hmac.update(req.get('Authorization'))
let ourShipFastHMAC = hmac.digest('hex')
// Check to see if our HMAC matches the one sent in the request header
// and send an error response if it doesn't
if (ourShipFastHMAC != requestShipFastHMAC) {
log.error("\tShipFast HMAC invalid: received " + requestShipFastHMAC
+ " but should be " + ourShipFastHMAC)
res.status(403).send()
return
}
log.success("\nValid HMAC.")
Web APP code:
function computeHMAC(url, idToken) {
if (currentDemoStage == DEMO_STAGE.HMAC_STATIC_SECRET_PROTECTION
|| currentDemoStage == DEMO_STAGE.HMAC_DYNAMIC_SECRET_PROTECTION) {
var hmacSecret
if (currentDemoStage == DEMO_STAGE.HMAC_STATIC_SECRET_PROTECTION) {
// Just use the static secret in the HMAC for this demo stage
hmacSecret = HMAC_SECRET
}
else if (currentDemoStage == DEMO_STAGE.HMAC_DYNAMIC_SECRET_PROTECTION) {
// Obfuscate the static secret to produce a dynamic secret to
// use in the HMAC for this demo stage
var staticSecret = HMAC_SECRET
var dynamicSecret = CryptoJS.enc.Base64.parse(staticSecret)
var shipFastAPIKey = CryptoJS.enc.Utf8.parse($("#shipfast-api-key-input").val())
for (var i = 0; i < Math.min(dynamicSecret.words.length, shipFastAPIKey.words.length); i++) {
dynamicSecret.words[i] ^= shipFastAPIKey.words[i]
}
dynamicSecret = CryptoJS.enc.Base64.stringify(dynamicSecret)
hmacSecret = dynamicSecret
}
if (hmacSecret) {
var parser = document.createElement('a')
parser.href = url
var msg = parser.protocol.substring(0, parser.protocol.length - 1)
+ parser.hostname + parser.pathname + idToken
var hmac = CryptoJS.HmacSHA256(msg, CryptoJS.enc.Base64.parse(hmacSecret)).toString(CryptoJS.enc.Hex)
return hmac
}
}
return null
}
NOTE: While the above code is not using the exact same parameters that you would use in your case, it is a good starting pointing for you to understand the basics of it.
As you can see the way the HMAC token is calculated across mobile app, Api server and the Web app are identical in the semantics of the logic, thus resulting in the same HMAC token, and this way the Web app is able to defeat the Api server defense to only accept valid request from the mobile app.
The bottom line here is that anything you place in the client code can be reverse engineered in order to replicate it in another client. So should I use HMAC tokens in my use case?
Yes, because it's one more layer in the onion or a fruit more high in the tree.
Can I do better?
Yes you can do, just keep reading...
Enhance and Strength the Security
Can anyone suggest the best way to mitigate replay attacks with parameters tampering?
Going with the layered defence approach once more, you should look to other layered approaches that will allow your API server to be more confident about who and waht is accessing it.
So if the clients of you API server are only mobile apps, then please read this answer for the question How to secure an API REST for mobile app?.
In the case you need to secure an API that serves both a mobile and web app, then see this another answer for the question Unauthorized API Calls - Secure and allow only registered Frontend app.
GOING THE EXTRA MILE
Now I would like to recommend you the excellent work of the OWASP foundation:
The Web Security Testing Guide:
The OWASP Web Security Testing Guide includes a "best practice" penetration testing framework which users can implement in their own organizations and a "low level" penetration testing guide that describes techniques for testing most common web application and web service security issues.
The Mobile Security Testing Guide:
The Mobile Security Testing Guide (MSTG) is a comprehensive manual for mobile app security development, testing and reverse engineering.

How do I get current user in .NET Core Web API (from JWT Token)

After a lot of struggling (and a lot of tuturials, guides, etc) I managed to setup a small .NET Core REST Web API with an Auth Controller issuing JWT tokens when stored username and password are valid.
The token stores the user id as sub claim.
I also managed to setup the Web API to validate those tokens when a method uses the Authorize annotation.
app.UseJwtBearerAuthentication(...)
Now my question:
How do I read the user id (stored in the subject claim) in my controllers (in a Web API)?
It is basically this question (How do I get current user in ASP .NET Core) but I need an answer for a web api. And I do not have a UserManager. So I need to read the subject claim from somewhere.
The accepted answer did not work for me. I'm not sure if that's caused by me using .NET Core 2.0 or by something else, but it looks like the framework maps the Subject Claim to a NameIdentifier claim. So, the following worked for me:
string userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
Note that this assumes the Subject sub Claim is set in the JWT and its value is the user's id.
By default, the JWT authentication handler in .NET will map the sub claim of a JWT access token to the System.Security.Claims.ClaimTypes.NameIdentifier claim type. [Source]
There is also a discussion thread on GitHub where they conclude this behavior is confusing.
You can use this method:
var email = User.FindFirst("sub")?.Value;
In my case I'm using the email as a unique value
It seems a lot of people are looking at this question so I would like to share some more information I learned since I asked the question a while back.
It makes some things more clear (at least for me) and wasn't so obvious (for me as .NET newbie).
As Marcus Höglund mentioned in the comments:
It should be the same for "web api"..In ASP.NET Core Mvc and Web Api are merged to use the same controller.
That's definitely true and absolutely correct.
Because it is all the same across .NET and .NET Core.
Back than I was new to .NET Core and actually the full .NET world. The important missing information was that in .NET and .NET Core all the authentication can be trimmed down to System.Security.Claims namespace with its ClaimsIdentity, ClaimsPrinciple and Claims.Properties. And therefore it is used in both .NET Core controller types (API and MVC or Razor or ...) and is accessible via HttpContext.User.
An important side note all of the tutorials missed to tell.
So if you start doing something with JWT tokens in .NET don't forget to also get confident with ClaimsIdentity, ClaimsPrinciple and Claim.Properties. It's all about that. Now you know it. It was pointed out by Heringer in one of the comments.
ALL the claim based authentication middlewares will (if correctly implemented) populate the HttpContext.User with the claims received during authentication.
As far as I understand now this means one can safely trust on the values in the HttpContext.User. But wait a bit to know what to mind when selecting middleware. There are a lot of different authentication
middleware already available (in addition to .UseJwtAuthentication()).
With small custom extension methods you can now get the current user id (more accurate the subject claim) like that
public static string SubjectId(this ClaimsPrincipal user) { return user?.Claims?.FirstOrDefault(c => c.Type.Equals("sub", StringComparison.OrdinalIgnoreCase))?.Value; }
Or you use the version in the answer of Ateik.
BUT WAIT: there is one strange thing
The next thing that confused me back than: according to the OpenID Connect spec I was looking for "sub" claim (the current user) but couldn't find it. Like Honza Kalfus couldn't do in his answer.
Why?
Because Microsoft is "sometimes" "a bit" different. Or at least they do a bit more (and unexpected) things. For example the official Microsoft JWT Bearer authentication middleware mentioned in the original question.
Microsoft decided to convert claims (the names of the claims) in all of their official authentication middleware (for compatibility reasons I don't know in more detail).
You won't find a "sub" claim (though it is the single one claim specified by OpenID Connect). Because it got converted to these fancy ClaimTypes. It's not all bad, it allows you to add mappings if you need to map different claims into a unique internal name.
Either you stick with the Microsoft naming (and have to mind that when you add/use a non Microsoft middleware) or you find out how to turn the claim mapping of for the Microsoft middleware.
In case of the JwtBearerAuthentication it is done (do it early in StartUp or at least before adding the middleware):
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
If you want to stick with the Microsoft namings the subject claim (don't beat me, I am not sure right now if Name is the correct mapping):
public static string SubjectId(this ClaimsPrincipal user) { return user?.Claims?.FirstOrDefault(c => c.Type.Equals(ClaimTypes.NameIdentifier, StringComparison.OrdinalIgnoreCase))?.Value; }
Note that the other answers use the more advanced and way more convenient FindFirst method. Though my code samples show it without those you may should go with them.
So all your claims are stored and accessible (via one name or the other) in the HttpContext.User.
But where is my token?
I don't know for the other middleware but the JWT Bearer Authentication allows to save the token for each request. But this needs to be activated (in StartUp.ConfigureServices(...).
services
.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options => options.SaveToken = true);
The actual token (in all it's cryptic form) as string (or null) can then be accessed via
HttpContext.GetTokenAsync("Bearer", "access_token")
There has been an older version of this method (this works for me in .NET Core 2.2 without deprecated warning).
If you need to parse and extract values from this string may the question How to decode JWT token helps.
Well, I hope that summary helps you too.
If you use Name to store the ID here:
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, user.Id.ToString())
}),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
In each controller method you can get the ID of the current user by:
var claimsIdentity = this.User.Identity as ClaimsIdentity;
var userId = claimsIdentity.FindFirst(ClaimTypes.Name)?.Value;
I used the HttpContext and it works well:
var email = string.Empty;
if (HttpContext.User.Identity is ClaimsIdentity identity)
{
email = identity.FindFirst(ClaimTypes.Name).Value;
}
you can do this using.
User.Identity.Name
In my case I set ClaimTypes.Name to unique user email before JWT token generation:
claims.Add(new Claim(ClaimTypes.Name, user.UserName));
Then I stored unique user id to ClaimTypes.NameIdentifier:
claims.Add(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()));
Then in the controller's code:
int GetLoggedUserId()
{
if (!User.Identity.IsAuthenticated)
throw new AuthenticationException();
string userId = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value;
return int.Parse(userId);
}
Mine worked using the following code in .net core 5 web api
User.Claims.First(x => x.Type == "id").Value;
asp.net core identity get user id
public async Task<IActionResult> YourMethodName()
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier) // will give the user's userId
var userName = User.FindFirstValue(ClaimTypes.Name) // will give the user's userName
ApplicationUser applicationUser = await _userManager.GetUserAsync(User);
string userEmail = applicationUser?.Email; // will give the user's Email
}
.net core identity get user id
public static class ClaimsPrincipalExtensions
{
public static T GetLoggedInUserId<T>(this ClaimsPrincipal principal)
{
if (principal == null)
throw new ArgumentNullException(nameof(principal));
var loggedInUserId = principal.FindFirstValue(ClaimTypes.NameIdentifier);
if (typeof(T) == typeof(string))
{
return (T)Convert.ChangeType(loggedInUserId, typeof(T));
}
else if (typeof(T) == typeof(int) || typeof(T) == typeof(long))
{
return loggedInUserId != null ? (T)Convert.ChangeType(loggedInUserId, typeof(T)) : (T)Convert.ChangeType(0, typeof(T));
}
else
{
throw new Exception("Invalid type provided");
}
}
public static string GetLoggedInUserName(this ClaimsPrincipal principal)
{
if (principal == null)
throw new ArgumentNullException(nameof(principal));
return principal.FindFirstValue(ClaimTypes.Name);
}
public static string GetLoggedInUserEmail(this ClaimsPrincipal principal)
{
if (principal == null)
throw new ArgumentNullException(nameof(principal));
return principal.FindFirstValue(ClaimTypes.Email);
}
}

.Net Core 2.0 Web API using JWT - Adding Identity breaks the JWT authentication

(Edit - Found proper fix! see below)
OK - this is my first attempt at .Net Core 2.0 and authentication, though I've done things with Web API 2.0 in the past, and have worked fairly extensively on various MVC and Webforms ASP projects over the last couple of years.
I'm trying to create a Web API ONLY project using .Net Core. This will form the back end of a multi-tenant application for producing some reports, so I need to be able to authenticate users. It seems the usual approach is to use JWT - first authenticate the user to generate a token, then pass that to the client to use on every API request. Data will be stored and retrieved using EF Core.
I followed this post for a basic way to get this set up, and I managed to get this to work ok - I have a controller which accepts a username/password and returns a token if valid, and some Authorization policies set up based on the claims.
The next thing I need is to actually manage the users/passwords/etc. I thought I'd just use .Net Core Identity for this as that way I would have lots of ready-made code for worry about users/roles, passwords etc. I was using custom User class and UserRole classes which derived from the standard IdentityUser and IdentityRole classes, but I've since reverted to the standard ones now.
The problem I have is that I can't quite figure out how to add identity & register all the various Services (rolemanager, usermanager, etc) without also breaking the authentication - basically as soon as I add this line to my Startup.ConfigureServices class:
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<MyContext>();
It all goes wrong, and I can no longer see any claims when I receive a request, so all the policies just lock down and you can't get to anything.
If I don't have those lines, then I end up with errors related to UserManager, RoleManager, UserStore etc. all not being registered for DI.
So... how (if it's possible) can I register Identity and hook it up to the Context correctly, but avoid/Remove any changes to the actual Authorisation mechanism?
I've looked around a fair bit online, but a lot of this has changed since .Net Core 1.x so a lot of the tutorials etc. aren't really valid any more.
I'm not intending this API application to have any front-end code, so I don't need any cookie authentication for forms or anything for now.
Edit
ok, I've now found that in this code setting up the JWT authentication in the Startup.ConfigureServices() method:
services.AddAuthentication(
JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
>>breakpoint>>> options.TokenValidationParameters =
new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "Blah.Blah.Bearer",
ValidAudience = "Blah.Blah.Bearer",
IssuerSigningKey =
JwtSecurityKey.Create("verylongsecretkey")
};
});
If I put a breakpoint at the line indicated (via ">>breakpoint>>>") then it gets hit when I don't add the lines to add identity services, but if I do add those lines then it never gets hit. This is true no matter where in the method I put the services.AddIdentity() call. I get that this is simply a lambda so it's getting executed at a later point, but is there any way I can get the AddIdentity stuff to NOT set up Authentication, or make the code immediately remove it? I assume at some point there's some code which elects to not run the Lambda for config I've set there as the Identity stuff has already set it...
Thanks for reading all that if you have :)
EDIT - Found an answer
ok, I eventually found this GH issue which is basically exactly this problem:
https://github.com/aspnet/Identity/issues/1376
Basically what I had to do was twofold:
Ensure that the call to services.AddIdentity<IdentityUser, IdentityContext() was made first
Change the call to add auth from:
services.AddAuthentication(
JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
...
To:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
...
This does annoyingly result in a cookie being created, but this isn't then used for authentication as far as I can tell - it's purely using the bearer token on requests to controllers/actions which have [Authorize(Policy = "Administrator")] or similar set at least.
I need to test more, and I'll try to come back here an update this if I find it is not working in some way.
(Edited - put proper solution in as an answer now)
I eventually put together the solution, so on the suggestion of user alwayslearning I've edited my post and I'm putting this in as an actual answer.
ok, This can be done properly. First, you need to use the Authentication options I pointed out in my edit above - that's fine.
Then you need to useservices.AddIdentityCore<TUser>() rather than services.AddIdentity<TUser>(). This however, doesn't add a whole bunch of things for role management, and is apparently lacking the proper constructor to give it the type of Role you want to use. This means that in my case I had to do this:
IdentityBuilder builder = services.AddIdentityCore<IdentityUser>(opt =>
{
opt.Password.RequireDigit = true;
opt.Password.RequiredLength = 8;
opt.Password.RequireNonAlphanumeric = false;
opt.Password.RequireUppercase = true;
opt.Password.RequireLowercase = true;
}
);
builder = new IdentityBuilder(builder.UserType, typeof(IdentityRole), builder.Services);
builder
.AddEntityFrameworkStores<MyContext>();
//.AddDefaultTokenProviders();
builder.AddRoleValidator<RoleValidator<IdentityRole>>();
builder.AddRoleManager<RoleManager<IdentityRole>>();
builder.AddSignInManager<SignInManager<IdentityUser>>();
Having done that, the next thing is to make sure that when validating a user login (prior to sending a token), you make sure to use the SignInManager method CheckPasswordSignInAsync and not PasswordSignInAsync:
public async Task<IdentityUser> GetUserForLogin(string userName, string password)
{
//find user first...
var user = await _userManager.FindByNameAsync(userName);
if (user == null)
{
return null;
}
//validate password...
var signInResult = await _signInManager.CheckPasswordSignInAsync(user, password, false);
//if password was ok, return this user.
if (signInResult.Succeeded)
{
return user;
}
return null;
}
if you use the PasswordSignInAsync method then you'll get a runtime error re. No IAuthenticationSignInHandler being configured.
I hope this helps someone at some point.
I have extracted the AddIdentity code from github and created an extension method based on it that doesn't add the default Cookie Authenticator, It's now pretty similar to the built in AddIdentityCore but can accept IdentityRole.
/// <summary>
/// Contains extension methods to <see cref="IServiceCollection"/> for configuring identity services.
/// </summary>
public static class IdentityServiceExtensions
{
/// <summary>
/// Adds the default identity system configuration for the specified User and Role types. (Without Authentication Scheme)
/// </summary>
/// <typeparam name="TUser">The type representing a User in the system.</typeparam>
/// <typeparam name="TRole">The type representing a Role in the system.</typeparam>
/// <param name="services">The services available in the application.</param>
/// <returns>An <see cref="IdentityBuilder"/> for creating and configuring the identity system.</returns>
public static IdentityBuilder AddIdentityWithoutAuthenticator<TUser, TRole>(this IServiceCollection services)
where TUser : class
where TRole : class
=> services.AddIdentityWithoutAuthenticator<TUser, TRole>(setupAction: null);
/// <summary>
/// Adds and configures the identity system for the specified User and Role types. (Without Authentication Scheme)
/// </summary>
/// <typeparam name="TUser">The type representing a User in the system.</typeparam>
/// <typeparam name="TRole">The type representing a Role in the system.</typeparam>
/// <param name="services">The services available in the application.</param>
/// <param name="setupAction">An action to configure the <see cref="IdentityOptions"/>.</param>
/// <returns>An <see cref="IdentityBuilder"/> for creating and configuring the identity system.</returns>
public static IdentityBuilder AddIdentityWithoutAuthenticator<TUser, TRole>(this IServiceCollection services, Action<IdentityOptions> setupAction)
where TUser : class
where TRole : class
{
// Hosting doesn't add IHttpContextAccessor by default
services.AddHttpContextAccessor();
// Identity services
services.TryAddScoped<IUserValidator<TUser>, UserValidator<TUser>>();
services.TryAddScoped<IPasswordValidator<TUser>, PasswordValidator<TUser>>();
services.TryAddScoped<IPasswordHasher<TUser>, PasswordHasher<TUser>>();
services.TryAddScoped<ILookupNormalizer, UpperInvariantLookupNormalizer>();
services.TryAddScoped<IRoleValidator<TRole>, RoleValidator<TRole>>();
// No interface for the error describer so we can add errors without rev'ing the interface
services.TryAddScoped<IdentityErrorDescriber>();
services.TryAddScoped<ISecurityStampValidator, SecurityStampValidator<TUser>>();
services.TryAddScoped<ITwoFactorSecurityStampValidator, TwoFactorSecurityStampValidator<TUser>>();
services.TryAddScoped<IUserClaimsPrincipalFactory<TUser>, UserClaimsPrincipalFactory<TUser, TRole>>();
services.TryAddScoped<UserManager<TUser>>();
services.TryAddScoped<SignInManager<TUser>>();
services.TryAddScoped<RoleManager<TRole>>();
if (setupAction != null)
{
services.Configure(setupAction);
}
return new IdentityBuilder(typeof(TUser), typeof(TRole), services);
}
}
Now you can use the above code normally from a WebApi project like so
.AddIdentityWithoutAuthenticator<User, IdentityRole>()

Cannot Validate AccessToken with IdentityServer

We are using IdentityServer for authentication and we are validating the access token using JwtSecurityTokenHandler ValidateToken. This used to work fine, but after we upgraded our client application to ASP.NET Core 1.0 RTM (from RC1), the validation fails. The received error is:
IDX10501: Signature validation failed. Unable to match 'kid'
When I look at the KeyID of the used certificate and the kid of the token, I can see that they are different. I checked the IdentityServer jwks-endpoint to check that I had the correct certificate and noticed that the kid and certificate key id are different from that endpoint too. From what I've understood, they are supposed to be the same?
Any ideas why the code broke during the upgrade since the certificate, token and IdentityServer are still the same and only the client app core was upgraded.
EDIT (More information)
I suspect that ValidateIssuerSigningKey is false by default and the key has not even been validated before (thus it was working). Now it seems that the ValidateIssuerSigningKey is being ignored (as bad practice?) and thus the validation fails.
Workaround/Fix
By setting the IssuerSigningKeyResolver manually and giving the key to use in validation explicitly, fixes the issue and validation passes. Not sure how good the workaround is and why the default doesn't work, but at least I can move on for now.
simplified code...
new JwtSecurityTokenHandler().ValidateToken(authTokens.AccessToken,
new TokenValidationParameters()
{
IssuerSigningKeys = keys,
ValidAudience = audience,
ValidIssuer = issuer,
IssuerSigningKeyResolver = (arbitrarily, declaring, these, parameters) => new List<X509SecurityKey> { securityKey }
}, out securityToken);
The Client and API should refer to the same instance of IdentityServer. We are running IdentityServerHost in Azure, which has different slots (main and staging) and two applications inconsistently referred to different slots. The Client received access token issued by IdSrv-main provider and passed it to API, that expected it from different provider IdSrv-staging. API validated it and returned error.
The problem is that the errror doesn't give a hint to the actual cause of the issue. MS should provide much more detailed error message to help debugging.
The current error message is not sufficient to identify the cause.