Can we tune the performance of default asp.net web api 2 identity provider.
If I am not using the roles or claims, can I exclude them from the query generated by the identity provider. This may help improve the performance a lot.
I am referring to the class ApplicationOAuthProvider.cs created by the default web api project.
The steps that it follows is:
var user1 = await userManager.FindByNameAsync(context.UserName);
if (user1 == null)
{
context.SetError("invalid_grant", "The user is not registered.");
return;
}
//Query gets executed once here
ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
// Query gets executed once again here
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager,
OAuthDefaults.AuthenticationType);
//This one generates one more query
ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager,
CookieAuthenticationDefaults.AuthenticationType);
//Fires the same query as above
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
THE WHOLE PROCESS TAKES 4 SECONDS. AM I DOING ANYTHING WRONG HERE?
Can we eliminate some process / merge few of them and optimize the
same.
Thanks
We sort of have the same setup.
Does it happen on every single request?
We've had quite alot of problems with entity framework cold queries and view generation, however, the "second" query always goes really fast.
If that is the case you can try using ADO.net with Identity instead and pregenerate views for entity framework. However, it's still a messy solution and something I really think MS should adress.
The article is still very relevant regarding identity, and there by entity framework, "warm-up".
Related
I am trying to use IdentityServer4 in a new project. I have seen in the PluralSight video 'Understanding ASP.NET Core Security' that IdentityServer4 can be used with claims based security to secure a web API. I have setup my IdentityServer4 as a separate project/solution.
I have also seen that you can add an IProfileService to add custom claims to the token which is returned by IdentityServer4.
One plan is to add new claims to users to grant them access to different parts of the api. However I can't figure out how to manage the claims of the users on the IdentityServer from the api project. I assume I should be making calls to IdentotyServer4 to add and remove a users claims?
Additionally is this a good approach in general, as I'm not sure allowing clients to add claims to the IdentityServer for their own internal security purposes makes sense - and could cause conflicts (eg multiple clients using the 'role' claim with value 'admin'). Perhaps I should be handling the security locally inside the api project and then just using the 'sub' claim to look them up?
Does anyone have a good approach for this?
Thanks
Old question but still relevant. As leastprivilege said in the comments
claims are about identity - not permissions
This rings true, but identity can also entail what type of user it is (Admin, User, Manager, etc) which can be used to determine permissions in your API. Perhaps setting up user roles with specific permissions? Essentially you could also split up Roles between clients as well for more control if CLIENT1-Admin should not have same permissions as CLIENT2-Admin.
So pass your Roles as a claim in your IProfileService.
public class ProfileService : IProfileService
{
private readonly Services.IUserService _userService;
public ProfileService(Services.IUserService userService)
{
_userService = userService;
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
try
{
switch (context.Client.ClientId)
{
//setup profile data for each different client
case "CLIENT1":
{
//sub is your userId.
var userId = context.Subject.Claims.FirstOrDefault(x => x.Type == "sub");
if (!string.IsNullOrEmpty(userId?.Value) && long.Parse(userId.Value) > 0)
{
//get the actual user object from the database
var user = await _userService.GetUserAsync(long.Parse(userId.Value));
// issue the claims for the user
if (user != null)
{
var claims = GetCLIENT1Claims(user);
//add the claims
context.IssuedClaims = claims.Where(x => context.RequestedClaimTypes.Contains(x.Type)).ToList();
}
}
}
break;
case "CLIENT2":
{
//...
}
}
}
catch (Exception ex)
{
//log your exceptions
}
}
// Gets all significant user claims that should be included
private static Claim[] GetCLIENT1Claims(User user)
{
var claims = new List<Claim>
{
new Claim("user_id", user.UserId.ToString() ?? ""),
new Claim(JwtClaimTypes.Name, user.Name),
new Claim(JwtClaimTypes.Email, user.Email ?? ""),
new Claim("some_other_claim", user.Some_Other_Info ?? "")
};
//----- THIS IS WHERE ROLES ARE ADDED ------
//user roles which are just string[] = { "CLIENT1-Admin", "CLIENT1-User", .. }
foreach (string role in user.Roles)
claims.Add(new Claim(JwtClaimTypes.Role, role));
return claims.ToArray();
}
}
Then add [Authorize] attribute to you controllers for your specific permissions. This only allow specific roles to access them, hence setting up your own permissions.
[Authorize(Roles = "CLIENT1-Admin, CLIENT2-Admin, ...")]
public class ValuesController : Controller
{
//...
}
These claims above can also be passed on authentication for example if you are using a ResourceOwner setup with custom ResourceOwnerPasswordValidator. You can just pass the claims the same way in the Validation method like so.
context.Result = new GrantValidationResult(
subject: user.UserId.ToString(),
authenticationMethod: "custom",
claims: GetClaims(user));
So like leastprivilege said, you dont want to use IdentityServer for setting up permissions and passing that as claims (like who can edit what record), as they are way too specific and clutter the token, however setting up Roles that -
grant them access to different parts of the api.
This is perfectly fine with User roles.
Hope this helps.
I am pretty new to this.. so any help would be greatly appreciated.
I have a WebApi service that uses OAUTH token and refresh token authentication.
All works well at the moment:
step1: I send in the user and password and it generates an authentication token and a refresh token. The refresh token is saved in the DB.
step2. I can now use the refresh token and i receive the authentication token and a new refresh token. I want a way to use the same refresh token i sent and not reuse a new one.
This is my code for the refresh token:
public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider
{
public async Task CreateAsync(AuthenticationTokenCreateContext context)
{
RefreshTokensRepository _repo = new RefreshTokensRepository();
var clientid = context.Ticket.Properties.Dictionary["as:client_id"];
//HERE I regenerate the token, but I have no idea how to retrieve the already sent one.
var refreshTokenId = Guid.NewGuid().ToString("n");
//saving in BD:
var refreshTokenLifeTime = context.OwinContext.Get<string>("as:clientRefreshTokenLifeTime");
var token = new RefreshTokens()
{
Id = Helper.GetHash(refreshTokenId),
ClientId = clientid,
Subject = context.Ticket.Identity.Name,
IssuedUtc = DateTime.UtcNow,
ExpiresUtc = DateTime.UtcNow.AddMinutes(Convert.ToDouble(refreshTokenLifeTime))
};
context.Ticket.Properties.IssuedUtc = DateTime.UtcNow;
context.Ticket.Properties.ExpiresUtc = DateTime.UtcNow.AddMinutes(Convert.ToDouble(refreshTokenLifeTime));
token.ProtectedTicket = context.SerializeTicket();
var result = _repo.Add(token);
if(!string.IsNullOrEmpty(result))
context.SetToken(refreshTokenId);
}
public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin");
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
string hashedTokenId = Helper.GetHash(context.Token);
RefreshTokensRepository _repo = new RefreshTokensRepository();
var refreshToken = _repo.FindById(hashedTokenId);
if (refreshToken != null)
{
//Get protectedTicket from refreshToken class
context.DeserializeTicket(refreshToken.ProtectedTicket);
_repo.Remove(hashedTokenId);
}
}
void IAuthenticationTokenProvider.Create(AuthenticationTokenCreateContext context)
{
throw new NotImplementedException();
}
void IAuthenticationTokenProvider.Receive(AuthenticationTokenReceiveContext context)
{
throw new NotImplementedException();
}
}
My code is based on this samples:
http://bitoftech.net/2014/07/16/enable-oauth-refresh-tokens-angularjs-app-using-asp-net-web-api-2-owin/
I would like to use the same sent refresh token, but I have no idea how to use the already sent one in this context.
Any ideas?
Disclaimer: I don't condone reusing refresh tokens.
However, this does provide a good opportunity for everyone to improve knowledge of how this process works and there could be a good reason for reusing past refresh tokens in certain scenarios. I'm basing my answer upon:
Question: "I want a way to use the same refresh token i sent and not reuse a new one."
Code comment, "//HERE I regenerate the token, but I have no idea how to retrieve the already sent one."
PseudoCode Steps:
Store a user identifier as a property in AuthenticationProperties in the GrantResourceOwnerCredentials() method. From the sample code, it looks like you may already be doing this with "userName":
var props = new AuthenticationProperties(new Dictionary<string, string>
{
{
"as:client_id", (context.ClientId == null) ? string.Empty : context.ClientId
},{
"userName", context.UserName
}
});
Retrieve the user identifier in the CreateAsync() method of your IAuthenticationTokenProvider implementation (e.g. "SimpleRefreshTokenProvider" in your case). This would look something like:
public async Task CreateAsync(AuthenticationTokenCreateContext context)
{
var userName = context.Ticket.Properties.Dictionary["userName"];
...
Still in the CreateAsync() method use the user identifier to lookup the existing refresh token. This would look something like:
var existingRefreshToken = await _repo.FindRefreshTokenByUserNameAsync(userName);
Note: You would need to write the above method into your AuthRepository class from the example code. The "FindRefreshTokenByUserNameAsync(userName) implementation might include something like this if you're using Entity Framework and have a "RefreshToken" table that is being used to persist the granted refresh token:
var existingToken = RefreshToken.Where(r => r.UserName == userName).SingleOrDefault();
At this point, you have the existing token and should be able to re-use that refresh token value instead of Guid.NewGuid():
var refreshTokenId = existingToken.Token;
Taking a look at the tutorial's example code, however, indicates that a HashAlgorithm is being used to store the refresh token's value. That could complicate things a bit for you as storing a hash value is better security, but the process of hashing here is meant to be one-way.
If you really want to reuse the original token value when all you have persisted is the hashed token, would need to implement code that captures the non-hashed token value in the ReceiveAsync() method. It would have to temporarily persist the non-hashed value long enough for you to use it in the CreateAsync() method. In other words, you would have to save/persist the "context.Token" in ReceiveAsync(), associate it with your userName (from context.Ticket.Properties.Dictionary["userName"]), and use it later in the CreateAsync() method. It's hacky and I don't like it, but you would do it around this line of code in ReceiveAsync():
string hashedTokenId = Helper.GetHash(context.Token);
When I started out on my project Google allowed to use OpenID where no real registration was needed..
This is now going away, and I have ported the project to the new meths.. BUT
The issue is that the Users will get a new Security token and therefor see the user as a new user.
Has any of you solved that so that I do not need to ask all users to create new accounts, and then for me to link the old accounts with the new accounts?
I decided that the Google email could be my link to the existing users.
If you are using the standard implementation of Identity in ASP.NET/WEBAPI then change: AccountControler.cs - ExternalLoginCallback
What this does is to looked up the email if the Google user did not exist and add a Login to the existing user like this:
// Sign in the user with this external login provider if the user already has a login
var user = await UserManager.FindAsync(loginInfo.Login);
Insert:
//Google Migration. If not existing then check if the Google email exists and if it does the change the LoginProvider key and try to login again
if (user == null && loginInfo.Login.LoginProvider == "Google" && email != "")
{
xxxEntities db = new xxxEntities();
var existingUser = db.user_profiles.Where(e => e.eMail == (string)email).Where(p => p.created_from == "Google");
if (existingUser.Count() > 0)
{
var existingID = existingUser.Single().userFK;
var result = await UserManager.AddLoginAsync(existingID, loginInfo.Login);
if (result.Succeeded)
{
//Now retry the check if the user with this external login provider if the user already has a login
user = await UserManager.FindAsync(loginInfo.Login);
}
}
}
To here:
//Now for the normal Check
if (user != null)
{
Please note that the lookup is some internal Objects I have, it can be done in a OOTB implementation by adding email lookup to the UserManager, or just do like I do and look it up in your own user admin object
I have a RavenDB Service instance set up on localhost:8080 and am using the OAuth plugin to store a simple user document for authentication. I am using a guid for the id and the users email as the name. The following code is functioning properly to store the user
public AccountUserDocument CreateUser(RegisterModel model)
{
using (IDocumentSession session = DataDocumentStore.Instance.OpenSession())
{
Guid userId = Guid.NewGuid();
session.Store(new AccountUserDocument
{
Name = model.Email,
Id = String.Format("Raven/Users/{0}", userId),
AllowedDatabases = new[] { "*" },
Email = model.Email,
FirstName = model.FirstName,
LastName = model.LastName,
FacebookId = 0,
Expires = DateTime.Now.AddMonths(1),
AccessToken = string.Empty
}.SetPassword(model.Password));
session.SaveChanges();
return session.Load<AccountUserDocument>
(String.Format("Raven/Users/{0}", userId));
}
}
and returns a valid user object. However, when i call
return session.Query<AccountUserDocument>()
.Customize(x => x.WaitForNonStaleResults())
.Where(x => x.Name == email)
.SingleOrDefault();
I get nothing. It had been working a week ago but now it just doesn't. If I open up RavenDB studio, I can see the user and the name is exactly how I am entering it (i have even copy and pasted it into the text field).
I have tried stopping and restarting the service hoping that would solve the problem but it did not.
I was wondering if someone could point me in the direction of how I might debug what is going on here. The full code repository can be found here
https://github.com/jamesamuir/MVC_Facebook_Auth
if anyone is inclined to download it.
Thanks in advance.
Well, I see a lot of things wrong with the code sample you provided - such as mismatched versions of client and server. Also, the Authentication Bundle was a 1.0 feature that has been deprecated in 2.0 which is almost final, so you're writing code you will eventually have to replace.
But to answer your question, the specific reason that you can't get results from your query is that you chose to use a document key starting with "Raven/", which is the convention for RavenDB system documents - which don't get indexed.
If you remove "Raven/" from your id, it will work.
Also, you should not be using .WaitForNonStaleResults() - that's only for unit tests, and is dangerous in production. If you really feel like you need to wait, use .WaitForNonStaleResultsAsOfNow() - which is safer because it provides a cutoff.
In my WCF service's business logic, most of the places when I need to locate an entity, I use this syntax:
public void UpdateUser(Guid userId, String notes)
{
using (ProjEntities entities = new ProjEntities())
{
User currUser = entities.SingleOrDefault(us => us.Id == userId);
if (currUser == null)
throw new Exception("User with ID " + userId + " was not found");
}
}
I have recentely discovered that the DbContext has the Find method, and I understand I can now do this:
public void UpdateUser(Guid userId, String notes)
{
using (ProjEntities entities = new ProjEntities())
{
User currUser = entities.Find(userId);
if (currUser == null)
throw new Exception("User with ID " + userId + " was not found");
}
}
Note : the 'userId' property is the primary key for the table.
I read that when using Find method entity framework checks first to see if the entity is already in the local memory, and if so - brings it from there. Otherwise - a trip is made to the database (vs. SingleOrDefault which always makes a trip to the database).
I was wondering if I now will convert all my uses of SingleOrDefault to Find is there any potential of danger?
Is there a chance I could get some old data that has not been updated if I use Find and it fetches the data from memory instead of the database?
What happens if I have the user in memory, and someone changed the user in the database - won't it be a problem if I always use now this 'memory' replica instead of always fetching the latest updated one from the database?
Is there a chance I could get some old data that has not been updated
if I use Find and it fetches the data from memory instead of the
database?
I think you have sort of answered your own question here. Yes, there is a chance that using Find you could end up having an entity returned that is out of sync with your database because your context has a local copy.
There isn't much more anyone can tell you without knowing more about your specific application; do you keep a context alive for a long time or do you open it, do your updates and close it? obviously, the longer you keep your context around the more susceptible you are to retrieving an up to date entity.
I can think of two strategies for dealing with this. The first is outlined above; open your context, do what you need and then dispose of it:
using (var ctx = new MyContext())
{
var entity = ctx.EntitySet.Find(123);
// Do something with your entity here...
ctx.SaveChanges();
}
Secondly, you could retrieve the DbEntityEntry for your entity and use the GetDatabaseValues method to update it with the values from the database. Something like this:
var entity = ctx.EntitySet.Find(123);
// This could be a cached version so ensure it is up to date.
var entry = ctx.Entry(entity);
entry.OriginalValues.SetValues(entry.GetDatabaseValues());