In my Asp .Net Core 3.1 app I'm using EF Core Identity to handle users authentication. As a database provider i use CosmosDB with SQL API.
Creating user with userManager.CreateAsync(...) goes smooth. I see new user data in database.
But when I'm trying to sign in with these credentials using signInManager.PasswordSignInAsync(...) I get exception:
InvalidOperationException: The LINQ expression 'DbSet<IdentityUserRole<string>>
.Join(
outer: DbSet<IdentityRole>,
inner: i => i.RoleId,
outerKeySelector: i0 => i0.Id,
innerKeySelector: (i, i0) => new TransparentIdentifier<IdentityUserRole<string>, IdentityRole>(
Outer = i,
Inner = i0
))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
Linked microsoft document says that this query has to be evaluated on client. So my question is, how can i change query for IdentityRole to achievie that?
Im using default Identity entities - IdentityUser and IdentityRole
Sign up code:
public class DashboardController : Controller
{
private readonly UserManager<IdentityUser> _userManager;
private readonly SignInManager<IdentityUser> _signInManager;
public DashboardController(UserManager<IdentityUser> userManager, SignInManager<IdentityUser> signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
[HttpPost]
public async Task<IActionResult> Register(string username, string password)
{
var user = new IdentityUser
{
UserName = username,
Id = username,
};
var result = await _userManager.CreateAsync(user, password);
if (result.Succeeded)
{
//sign in
var signInResult = await _signInManager.PasswordSignInAsync(user, password, false, false);
// here I also tried to use method with different signature (username, password, ...)
if (signInResult.Succeeded)
{
return RedirectToAction("Index");
}
}
return RedirectToAction("Index");
}
[HttpPost]
public async Task<IActionResult> SignIn(string username, string password)
{
var user = await _userManager.FindByNameAsync(username);
if (user != null)
{
var signInResult = await _signInManager.PasswordSignInAsync(user.UserName, password, false, false);
if (signInResult.Succeeded)
{
return RedirectToAction("Index");
}
}
return RedirectToAction("SignIn");
}
.
.
.
}
Related
I am busy merging two projects and with that there are certain methods that I now have to use. When I am creating a new user it fails complaining about Primary key constrains. The weird thing is that the primary key is in a different table from the one that it is complaining about. So my thinking is that it has to do with EFCore.BulkExtensions.BulkInsertAsync(Third party tool by borisdj) and how it saves and UserManager.CreateAsync and how it saves.
This is the error that I am getting:
Innermost exception Microsoft.Data.SqlClient.SqlException :
Violation of PRIMARY KEY constraint 'PK_dbo.UserDetail'. Cannot insert
duplicate key in object 'dbo.UserDetail'.
I have a method with this if statement:
if (await CreateUser.CreateUserDetailAsync(model))
{
await CreateAccountUser(model.Email, model.Password, model.Role, model.Fullname);
return RedirectToAction("Index", "ShowUser");
}
I get the error in the CreateAccountUser user method but it complains about the table in CreateUser.CreateUserDetailAsync.
Th error complains about my UserDetail here is how this is saved. This part works because even with the error, the data get saved to the database.
private async Task<boolean> CreateUserDetailAsync(_Models.Settings.User user)
{
var userDetail = new UserDetail
{
Code = user.ERPCode,
Email = user.Email,
Name = user.Name,
Surname = user.Surname
};
//Create the
await UserDetail.BulkInsertAsync(new List<UserDetail>{userDetail});
return true;
}
This is my method for bulk inserts using EFCore.BulkExtensions.BulkInsertAsync:
public Task BulkInsertAsync(IEnumerable<T> entities)
{
if (entities == null)
throw new ArgumentNullException("entity");
foreach (var item in entities)
{
//Attempt to set the Create value of the entity
var created = typeof(T).GetProperty("Created");
if (created == null) created = typeof(T).GetProperty("CreatedOn");
created?.SetValue(item, DateTime.Now, null);
//Attempt to set the Modified value of the entity
var modified = typeof(T).GetProperty("Modified");
if (modified == null) modified = typeof(T).GetProperty("ModifiedOn");
modified?.SetValue(item, DateTime.Now, null);
Entities.Add(item);
}
return Throw(Db).BulkInsertAsync(entities.ToList());
}
I am including this code just for clarity:
private static DbContext Throw(IDataContext context) => context as DbContext;
Then when I get to this method, it fails on await UserManager.CreateAsync.
private async Task CreateAccountUser(string email, string password, string role,
string userFullName = null)
{
//Register User with identity
var accountUser = new Core.Data.Models.User();
accountUser.Email = accountUser.UserName = email;
var result = await UserManager.CreateAsync(accountUser, password);
if (result.Succeeded)
{
return true;
}
else
{
return true;
}
}
There are no linking between my User and UserDetail, they are completely separate.
I have created an API that is using EF Core with a repository pattern and I have few questions:
Post method receives an email address and verify whether user exists on not.
If an email address does not exist in the User table, get the guest access details from the AccessManagement table and save in Entitlement table and return the details
If the entry exists, get the user access details and return them
IGeneralRepository:
public interface IGenrealRepository<TEntity> where TEntity : class , new()
{
IQueryable<TEntity> GetAll();
Task<TEntity> AddAsync(TEntity entity);
Task<TEntity[]> AddRangeAsync(TEntity[] entity);
TEntity Update(TEntity entity);
Task<int> CompleteAsync();
}
General repository:
public class GeneralRepository<TEntity> : IGenrealRepository<TEntity> where TEntity : class, new()
{
private MyDbContext _myDbContext;
public GeneralRepository(MyDbContext myDbContext)
{
_myDbContext = myDbContext;
}
public async Task<TEntity> AddAsync(TEntity entity)
{
if (entity == null)
{
throw new ArgumentNullException($"{nameof(AddAsync)} entity must not be null");
}
try
{
await _myDbContext.AddAsync(entity);
return entity;
}
catch (Exception ex)
{
throw new Exception($"{nameof(entity)} could not be saved: {ex.Message}");
}
}
public async Task<TEntity[]> AddRangeAsync(TEntity[] entity)
{
if (entity == null)
{
throw new ArgumentNullException($"{nameof(AddRangeAsync)} entity must not be null");
}
try
{
await _myDbContext.AddRangeAsync(entity);
return entity;
}
catch (Exception ex)
{
throw new Exception($"{nameof(entity)} could not be saved: {ex.Message}");
}
}
public async Task<int> CompleteAsync()
{
return await _myDbContext.SaveChangesAsync();
}
public IQueryable<TEntity> GetAll()
{
try
{
return _myDbContext.Set<TEntity>();
}
catch (Exception ex)
{
throw new Exception($"Couldn't retrieve entities: {ex.Message}");
}
}
public TEntity Update(TEntity entity)
{
try
{
_myDbContext.Update<TEntity>(entity);
return entity;
}
catch (Exception ex)
{
throw new Exception($"{nameof(entity)} could not be updated: {ex.Message}");
}
}
}
IUserService:
public interface IUserService
{
Task<User> CreateUser(string emailId);
Task<int> Complete();
}
UserService implementation:
public class UserService : IUserService
{
private readonly IUserRepository _userRepository;
private readonly IAccessManagementRepository _accessManagementRepository;
public UserService(IUserRepository userRepository, IAccessManagementRepository accessManagementRepository)
{
_userRepository = userRepository;
_accessManagementRepository = accessManagementRepository;
}
public async Task<int> Complete()
{
return await _userRepository.CompleteAsync();
}
public async Task<User> CreateUser(string emailId)
{
var user = _userRepository.GetAll()
.Where(x => x.EmailId.ToUpper() == emailId.ToUpper())
.FirstOrDefault();
if (user == null)
{
var entitlements = await _userAccessRepository.GetAll()
.Where( x => x.Default == true)
.Select( x => new UserEntitlement() {
Id = x.Id,
AccessName = x.AccessName
}).ToListAsync();
//saving User and Entitlement
user = new User()
{
EmailId = emailId,
UserEntitlements = entitlements
};
user = await _userRepository.AddAsync(user);
}
else
{
// Getting current User Entitlement
var entitlements = await _userRepository.GetAllUserEntitilements();
var entitlement = entitlements.Find(x => x.UserId == user.UserId);
user.UserEntitlements = entitlements;
}
return user;
}
}
API call:
[HttpPost]
public async Task<IActionResult> CreateUser([FromBody] User user)
{
var result = await _userService.CreateUser(user.EmailId);
await _userService.Complete();
return CreatedAtAction(nameof(GetUser), new { emailId = result.EmailId }, result);
}
Questions:
Is my method UserService.CreateUser() implementation correct? Any better approach?
Is the below code is the best approach to filter?
var user = _userRepository.GetAll()
.Where(x => x.EmailId.ToUpper() == emailId.ToUpper())
.FirstOrDefault();
How to get data from User and Entitlement table at one stretch? Something like below Include but can not use include because of an error
var user = _userRepository.GetAll()
.Where(x => x.EmailId.ToUpper() == emailId.ToUpper())
.Include<UserEntitlement>()
.FirstOrDefault();
How to do insert to one table and update to another table in a single transaction?
Leo,
I prefer doing the validation of the email outside the CreateUser function
This comes with another function where you could add to IUserService where you can get the user by email GetUserByEmail.
Doing that you can possibly return a proper error or validation message before invoking the CreateUser at the API Call
For example
[HttpPost]
public async Task<IActionResult> CreateUser([FromBody] User user)
{
var user = await _userService.GetUserByEmail(user.EmailId);
// or var userRegistered = await _userService.UserExistsByEmail(user.EmailId) returning a bool
// user registered?
if (user)
{
// The user already exists, return an error or
// You could update the UserEntitlements here or you could
// make an HttpPut where the user is updated do nothing here
}
....
}
An example
var user = _userRepository.GetAll()
.Include(x => x.UserEntitlements)
.Where(x => x.EmailId.ToUpper() == emailId.ToUpper())
.FirstOrDefault();
You can do it using UnitOfWork
Repository Pattern and Unit of Work
I am unsure how to go about searching for the "Code" stored in my Database in order to return the "OriginalUrl".
I know I can search for the ObjectId but I want to be able to search by the "Code" assigned to that ObjectId.
Currently I have a working program that takes a Url as well as a "title" and sends it to the database:
It is assigned an Objectid _id and a randomly generated 12 character "Code":
If it helps this is my Controller class:
namespace ShortenUrls.Controllers
{
[Route("api/codes")]
public class ShortUrlsController : Controller
{
private readonly ShortUrlRepository _repo;
public ShortUrlsController(ShortUrlRepository repo)
{
_repo = repo;
}
[HttpGet("{id}")]
public async Task<IActionResult> Get(string id)
{
var su = await _repo.GetAsync(id);
if (su == null)
return NotFound();
return Ok(su);
}
[HttpPost]
public async Task<IActionResult> Create([FromBody] ShortUrl su)
{
await _repo.CreateAsync(su);
return Ok(su);
}
}
And Repository class:
namespace ShortenUrls.Models.Repository
{
public class ShortUrlRepository
{
private const string alphabet = "23456789bcdfghjkmnpqrstvwxyz-_";
private static readonly Random rand = new Random();
private readonly Database _db;
public ShortUrlRepository(Database db)
{
_db = db;
}
private static string GenerateCode()
{
const int codeLength = 12;
var chars = new char[codeLength];
for (var i = 0; i < codeLength; i++)
{
chars[i] = alphabet[rand.Next(0, alphabet.Length)];
}
return new string(chars);
}
public Task<ShortUrl> GetAsync(string id)
{
var objId = ObjectId.Parse(id);
return _db.Urls.Find(x => x.Id == objId).FirstOrDefaultAsync();
}
public Task CreateAsync(ShortUrl su)
{
su.Code = GenerateCode();
return _db.Urls.InsertOneAsync(su);
}
}
Just use a filter. Doing it this way let's you create a query specifically for the "code".
public async Task<ShortUrl> GetAsync(string code)
{
var filterBuilder = new FilterDefinitionBuilder<ShortUrl>();
var filter = filterBuilder.Eq(s => s.Code, code);
var cursor = await _db.Urls.FindAsync(filter);
return await cursor.FirstOrDefaultAsync();
}
Assuming you already know the code when calling this and that ObjectId is created on InsertOneAsync call. First change your repository to take Code as searchable input.
public Task<ShortUrl> GetAsync(string code)
{
return await _db.Urls.FirstOrDefaultAsync(x => x.Code == code);
}
Then change your controller Get to this:
[HttpGet("{code}")]
public async Task<IActionResult> Get(string code)
{
var su = await _repo.GetAsync(code);
if (su == null)
return NotFound();
return Ok(su);
}
In your controller you can access su.OriginalUrl if you need to only return that after getting the object.
Then in postman you can just call http://localhost:51767/api/codes?code=cmg3fjjr_gtv
Remember only Id works for default url parameters as setup by your default routes in Startup.cs.
app.UseMvc(routes => { /*...*/ })
So this wont work: /api/codes/cmg3fjjr_gtv unless you specifically set up routing or change {code} back to {id}. Readability of your code suffers though.
I am writing WCF Services, I authenticate User who will have access to my WCF Services through ASP.NET Identity without Entity Framework. Now I got an issue on Role Authorization. I am using custom way without Entity Framework so for it to achieve authentication I created User class and UserStore Class. And how could I authorize the role?
[Note:I have Role in Database table (ASPNetRoles and ASPNetUserRoles) that can only access WCF Services and I know I have to decorate the method with principalpermission.]
namespace CalculatorService
{
public class IdentityValidator : UserNamePasswordValidator
{
public override void Validate(string UserName, string Password)
{
using (var userManager = new UserManager<User>(new UserStore("data=source=pcb-sql01;initial catalog=InsitePCB;integrated security=True;MultipleActiveResultSets=True")))
{
var user = userManager.Find(UserName, Password);
if (user == null)
{
var msg = string.Format("Unknown Username {0} or incorrect password {1}", UserName, Password);
Trace.TraceWarning(msg);
throw new FaultException(msg);
// //the client actually will receive MessageSecurityException. But if I throw MessageSecurityException, the runtime will give FaultException to client without clear message.
}
}
}
}
public class RoleAuthorizationManager : ServiceAuthorizationManager
{
protected override bool CheckAccessCore(OperationContext operationContext)
{
using (var userStore = new UserStore("data source=pcb-sql01;initial catalog=InsitePCB;integrated security=True;MultipleActiveResultSets=True"))
{
using (var userManager = new UserManager<User>(userStore))
{
var identity = operationContext.ServiceSecurityContext.PrimaryIdentity;
var user = userManager.FindByName(identity.Name);
if (user == null)
{
var msg = string.Format("Unknown Username {0} .", user.UserName);
Trace.TraceWarning(msg);
throw new FaultException(msg);
}
//Assign roles to the Principal property for runtime to match with PrincipalPermissionAttributes decorated on the service operation.
var roleNames = userManager.GetRoles(user.Id).ToArray();//users without any role assigned should then call operations not decorated by PrincipalPermissionAttributes
operationContext.ServiceSecurityContext.AuthorizationContext.Properties["Principal"] = new GenericPrincipal(operationContext.ServiceSecurityContext.PrimaryIdentity, roleNames);
return true;
}
}
}
}
}
I'm implementing 2fa with IdentityServer3 + Asp.Net Identity (2.2.1). I'm stuck on the 2fa implementation. I've looked at the "AspNetIdentity_2fa" sample, which helped a lot.
I have everything wired up, except for the cookie that indicates the browser has been successfully authenticated. I can set the cookie during the code confirmation, but I cannot get to the cookie in the PostAuthenticateLocalAsync() call to see whether or not to take the 2fa path.
protected override Task<AuthenticateResult> PostAuthenticateLocalAsync(User user, SignInMessage message)
{
if (user.TwoFactorEnabled) // && !TwoFactorCookieSet...
{
return Task.FromResult(new AuthenticateResult("/auth/sendcode", user.Id, user.DisplayName));
}
return base.PostAuthenticateLocalAsync(user, message);
}
I believe I'm taking the correct approach in using the partial logins, but how would I detect that the current browser has already been approved?
More detail: the /auth/sendcode is the standard Asp.Net Identity pages/flow for 2fa, combined with the partial login logic from the sample.
Okay, I found that OwinEnvironmentService can be injected into IdentityServer services. I can get the cookies via OwinEnvironmentService. I'd be interested to hear any opinions on this solution (this isn't meant to be production-ready, it's just a concept):
internal class UserService : AspNetIdentityUserService<User, string>
{
private readonly OwinEnvironmentService _owinEnvironmentService;
public UserService(UserManager userMgr, OwinEnvironmentService owinEnvironmentService) : base(userMgr)
{
_owinEnvironmentService = owinEnvironmentService;
DisplayNameClaimType = IdentityServer3.Core.Constants.ClaimTypes.Name;
}
protected override Task<AuthenticateResult> PostAuthenticateLocalAsync(User user, SignInMessage message)
{
if (user.TwoFactorEnabled)
{
var twoFactorNeeded = false;
object httpContext;
if (_owinEnvironmentService.Environment.TryGetValue("System.Web.HttpContextBase", out httpContext))
{
var cookies = (httpContext as HttpContext)?.Request.Cookies;
if (cookies != null && !cookies.AllKeys.Contains(IdentityConstants.CookieNames.TwoFactorCompleted)) twoFactorNeeded = true;
}
if (twoFactorNeeded)
return Task.FromResult(new AuthenticateResult("/auth/sendcode", user.Id, user.DisplayName));
}
return base.PostAuthenticateLocalAsync(user, message);
}
}
UPDATED
Based on Brock's comment, I think I have a better solution.
// custom User Service
internal class UserService : AspNetIdentityUserService<User, string>
{
private readonly OwinEnvironmentService _owinEnvironmentService;
public UserService(UserManager userMgr, OwinEnvironmentService owinEnvironmentService) : base(userMgr)
{
_owinEnvironmentService = owinEnvironmentService;
DisplayNameClaimType = IdentityServer3.Core.Constants.ClaimTypes.Name;
}
protected override async Task<AuthenticateResult> PostAuthenticateLocalAsync(User user, SignInMessage message)
{
if (user.TwoFactorEnabled)
{
var owinContext = new OwinContext(_owinEnvironmentService.Environment);
var result = await owinContext.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
if(result == null) return new AuthenticateResult("/auth/sendcode", user.Id, user.DisplayName);
}
return await base.PostAuthenticateLocalAsync(user, message);
}
}
// (in MVC controller) generate the 2FA security code and send it
public async Task<ActionResult> SendCode(SendCodeViewModel model)
{
// ...some code removed for brevity...
var token = await UserManager.GenerateTwoFactorTokenAsync(userId, model.SelectedProvider);
var identityResult = await UserManager.NotifyTwoFactorTokenAsync(userId, model.SelectedProvider, token);
if (!identityResult.Succeeded) return View("Error");
return RedirectToAction("VerifyCode", new { Provider = model.SelectedProvider, model.ReturnUrl, model.RememberMe });
}
// (in MVC controller) verify the code and sign in with 2FA
public async Task<ActionResult> VerifyCode(VerifyCodeViewModel model)
{
// ...some code removed for brevity...
var signInManager = new SignInManager<User, string>(UserManager, Request.GetOwinContext().Authentication);
if (await UserManager.VerifyTwoFactorTokenAsync(user.Id, model.Provider, model.Code))
{
await UserManager.ResetAccessFailedCountAsync(user.Id);
await signInManager.SignInAsync(user, model.RememberMe, model.RememberBrowser);
var resumeUrl = await env.GetPartialLoginResumeUrlAsync();
return Redirect(resumeUrl);
}
else
{
await UserManager.AccessFailedAsync(user.Id);
ModelState.AddModelError("", "Invalid code.");
return View(model);
}
}
I implemented the same for remember browser requirement however following statement return always null when we logout and login again.so twofactory step is not skipped..
var result = await owinContext.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);