I run into another problem. I have a UserRole domain class which takes UserID and RoleID
public class User_Role
{
public Guid Id { get; set; }
public Guid UserId { get; set; }
//nav prop
public User User { get; set; }
public Guid RoleId { get; set; }
//nav prop
public Role Role { get; set; }
}
Roles have 2 types Reader and Admin. I need them for authentication which I will implement later.
The problem is when I added a "Reader" I can add it again and again. I understand that I need to validate user_role. This is my User_role controller
[Route("api/[controller]")]
[ApiController]
public class UserRoleController : ControllerBase
{
public readonly IUserRepository _userRepository;
public readonly IRoleRepository _roleRepository;
public readonly IUserRoleRepository _userRoleRepository;
public UserRoleController(IUserRepository userRepository, IRoleRepository IRoleRepository,
IUserRoleRepository userRoleRepository)
{
_userRepository = userRepository;
_roleRepository = IRoleRepository;
_userRoleRepository = userRoleRepository;
}
[HttpGet]
public async Task<IActionResult> GetAllUserRoles()
{
var userRoleDomain = await _userRoleRepository.GetAllAsync();
return Ok(userRoleDomain);
}
[HttpGet]
[Route("{userRoleId:guid}")]
[ActionName("GetUserRoleById")]
public async Task<IActionResult> GetUserRoleById(Guid userRoleId)
{
var userRoleDomain = await _userRoleRepository.GetUserRoleByIdAsync(userRoleId);
if(userRoleDomain == null)
{
return NotFound();
}
var UserRoleDto = userRoleDomain.ConvertToDto();
return Ok(UserRoleDto);
}
[HttpPost]
public async Task<IActionResult> AddUserRole([FromBody]AddUserRoleRequest addUserRoleRequest)
{
if(!(await ValidateAddUserRoleAsync(addUserRoleRequest)))
{
return BadRequest(ModelState);
}
//CheckIfRoleExist
var userRoleDomain = new User_Role
{
RoleId = addUserRoleRequest.RoleId,
UserId = addUserRoleRequest.UserId
};
userRoleDomain = await _userRoleRepository.AddUserRoleAsync(userRoleDomain);
var userRoleDto = userRoleDomain.ConvertToDto();
return CreatedAtAction(nameof(GetUserRoleById), new { userRoleId = userRoleDto.Id}, userRoleDto);
}
[HttpDelete]
[Route("{userRoleId:guid}")]
public async Task<IActionResult> DeleteUserRole(Guid userRoleId)
{
var userRoleDomain = await _userRoleRepository.DeleteAsync(userRoleId);
if(userRoleDomain == null)
{
return NotFound();
}
var userRoleDto = userRoleDomain.ConvertToDto();
return Ok(userRoleDto);
}
[HttpPut]
[Route("{userRoleId:guid}")]
public async Task<IActionResult> UpdateUserRole([FromRoute]Guid userRoleId,
[FromBody]UpdateUserRoleRequest updateUserRoleRequest)
{
if(!(await ValidateUpdateUserRoleAsync(updateUserRoleRequest)))
{
return BadRequest(ModelState);
}
var userRoleDomain = new User_Role()
{
Id = userRoleId,
UserId = updateUserRoleRequest.UserId,
RoleId = updateUserRoleRequest.RoleId
};
userRoleDomain = await _userRoleRepository.UpddateAsync(userRoleId, userRoleDomain);
if(userRoleDomain == null)
{
return NotFound();
}
var userRoleDto = userRoleDomain.ConvertToDto();
return Ok(userRoleDto);
}
#region Validation methods
private async Task<bool> ValidateAddUserRoleAsync(AddUserRoleRequest addUserRoleRequest)
{
var user = await _userRepository.GetAsyncByIdWithRoles(addUserRoleRequest.UserId);
if (user == null)
{
ModelState.AddModelError(nameof(addUserRoleRequest.UserId),
$"{nameof(addUserRoleRequest.UserId)} UserId is invalid");
}
var role = await _roleRepository.GetAsync(addUserRoleRequest.RoleId);
if(role == null)
{
ModelState.AddModelError(nameof(addUserRoleRequest.RoleId),
$"{nameof(addUserRoleRequest.RoleId)} RoleId is invalid");
}
if(ModelState.ErrorCount > 0)
{
return false;
}
return true;
}
private async Task<bool> ValidateUpdateUserRoleAsync(UpdateUserRoleRequest updateUserRoleRequest)
{
var user = await _userRepository.GetUserByIdASync(updateUserRoleRequest.UserId);
if(user == null)
{
ModelState.AddModelError(nameof(updateUserRoleRequest.UserId),
$"{nameof(updateUserRoleRequest.UserId)} UserId is invalid");
}
var role = await _roleRepository.GetAsync(updateUserRoleRequest.RoleId);
if(role == null)
{
ModelState.AddModelError(nameof(updateUserRoleRequest.RoleId),
$"{nameof(updateUserRoleRequest.RoleId)} RoleId is invalid");
}
if(ModelState.ErrorCount > 0)
{
return false;
}
return true;
}
private async Task<bool> CheckIfRoleExist(AddUserRoleRequest addUserRoleRequest)
{
}
#endregion
}
I am thinking to validate this in my CheckIfRoleExist function. How do I check if this type of role is already added with a specific userId?
Adding call to repository
public async Task<bool> CheckIfAlreadyExistAsync(User_Role userRole)
{
var userRoleExist = await _webApiDbContext.User_Roles.AnyAsync(u => u.RoleId == userRole.RoleId && u.UserId == userRole.UserId);
if(userRoleExist != null)
{
return true;
}
return false;
}
Figured out something like this
private async Task<bool> CheckIfRoleExist(AddUserRoleRequest addUserRoleRequest)
{
var user = await _userRepository.GetAsyncByIdWithRoles(addUserRoleRequest.UserId);
if(user == null)
{
return false;
}
foreach(var roleAdded in user.UserRoles)
{
if(roleAdded.RoleId == addUserRoleRequest.RoleId)
{
ModelState.AddModelError(nameof(addUserRoleRequest.RoleId),
$"{nameof(addUserRoleRequest.RoleId)} user already have this role");
}
}
if (ModelState.ErrorCount > 0)
{
return false;
}
return true;
}
If you have cleaner answer dont be afraid to post it:)
Related
I use an ASP.NET Web API. How do I check my login with a Post request? I want to check if the login already exists in the database, and if so, throw an error.
UsersController:
[HttpPost]
public async Task<ActionResult<User>> PostUser(User user)
{
if (_context.Users == null)
{
return Problem("Entity set 'ShopContext.Users' is null.");
}
var role = await _context.Roles.FindAsync(user.IdRole);
if (role == null)
{
return NotFound();
}
for (int i = 0; i < user.Id; i++)
{
if (i == user.UserName.Length)
{
return Problem("User already registered");
}
}
user.Role = role;
_context.Users.Add(user);
await _context.SaveChangesAsync();
return CreatedAtAction("GetUser", new { id = user.Id }, user);
}
User model:
namespace ShopAPI.Models
{
public class User
{
public int Id { get; set; }
public string? Password { get; set; }
public string? UserName { get; set; }
}
}
[HttpPost]
public async Task<ActionResult<User>> PostUser(User user)
{
var userWithIdAlreadyExists = _context.Users.Find(user.Id) != null;
if(userAlreadyExists)
{
return Problem ("user with id already exists");
}
var userNameAlreadyExists = _context.users
.FirstOrDefault(u => u.UserName == user.UserName) == null;
if(userNameAlreadyExists)
{
return Problem("User already exists with that name");
}
//Write the code you want to execute when the user has a valid id and username
}
I have a problem concerning entities in ASP.NET Core.
I use Entity Framework Core as data access library.
The issue I've come across happens when I'm trying to update an entity. After I modify the properties and call SaveChanges, the entity gets deleted and I don't understand why.
Here's the entity:
public class Contract
{
public int Id { get; set; }
[Required]
public DateTime ExpiryDate { get; set; }
[Required]
[Range(0, float.MaxValue)]
public float MonthlyFee { get; set; }
[Required]
public string UserId { get; set; }
[Required]
public int CarId { get; set; }
public User User { get; set; }
public Car Car { get; set; }
}
Here's the related entities for reference:
public class User : IdentityUser
{
[Required]
[PersonalData]
public string Name { get; set; }
[Required]
[PersonalData]
public string Surname { get; set; }
[Required]
[PersonalData]
public string TaxCode { get; set; }
[Required]
[PersonalData]
[DataType(DataType.Date)]
public DateTime DateOfBirth { get; set; }
public string ProfilePictureUrl { get; set; }
public Contract Contract { get; set; }
public ICollection<CarAccident> CarAccidents { get; set; }
}
public class Car
{
public int Id { get; set; }
[Required]
[RegularExpression("[A-Z][A-Z][0-9][0-9][0-9][A-Z][A-Z]")]
public string LicensePlate { get; set; }
public int CarModelId { get; set; }
public string FittingDescription { get; set; }
public Contract Contract { get; set; }
public ICollection<CarAccident> CarAccidents { get; set; }
public CarModel CarModel { get; set; }
}
Here's my update method in repository:
public async Task<Contract> Update(Contract entity)
{
var dbContract = await GetById(entity.Id);
if (dbContract == null)
return null;
var dbUser = await _userRepository.GetById(entity.UserId);
if (dbUser == null)
return null;
var dbCar = await _carRepository.GetById(entity.CarId);
if (dbCar == null)
return null;
dbContract.ExpiryDate = entity.ExpiryDate;
dbContract.User = entity.User;
dbContract.Car = dbCar;
dbContract.User = dbUser;
//_context.Contracts.FromSqlInterpolated($"UPDATE dbo.Contracts SET ExpiryDate={entity.ExpiryDate}, MonthlyFee={entity.MonthlyFee} WHERE Id={entity.Id}");
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException)
{
return null;
}
return await GetById(entity.Id);
}
Has anyone got any idea how to solve this?
UPDATE:
This is the new Update method:
public async Task<Contract> Update(Contract entity)
{
var dbContract = await GetById(entity.Id);
if (dbContract == null)
return null;
var dbUser = await _userRepository.GetById(entity.UserId);
if (dbUser == null)
return null;
var dbCar = await _carRepository.GetById(entity.CarId);
if (dbCar == null)
return null;
dbContract.ExpiryDate = entity.ExpiryDate;
dbContract.Car = dbCar;
dbContract.User = dbUser;
//_context.Contracts.FromSqlInterpolated($"UPDATE dbo.Contracts SET ExpiryDate={entity.ExpiryDate}, MonthlyFee={entity.MonthlyFee} WHERE Id={entity.Id}");
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException)
{
return null;
}
return await GetById(entity.Id);
}
Here's the Fluent API configuration:
private void _configureUsers(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasOne(u => u.Contract)
.WithOne(c => c.User)
.HasForeignKey<Contract>(c => c.UserId);
}
private void _configureCars(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Car>()
.HasAlternateKey(c => c.LicensePlate);
modelBuilder.Entity<Car>()
.HasOne(c => c.Contract)
.WithOne(c => c.Car)
.HasForeignKey<Contract>(c => c.CarId);
}
Both this methods get called in the OnModelCreating method of the context.
I've finally managed to solve my issue.
I was already tracking the entity in my api controller like that:
[HttpPut("{id}")]
[Authorize(Roles = "Backoffice")]
public async Task<ActionResult<ContractDTO>> PutContract(int id, [FromBody] PutContractViewModel viewModel)
{
if (viewModel == null || !ModelState.IsValid)
return BadRequest(new { message = "Your model is wrong" });
var contract = await _contractService.GetContractDTO(id);
if (contract == null)
return NotFound();
var modifiedContract = await _contractService.UpdateContract(viewModel);
if (modifiedContract == null)
return BadRequest(new { message = "User or car may be busy in another contract" });
return Ok(modifiedContract);
}
This type of approach works in one to many relationships, but evidently when you have one to one relationship and you have to objects that rapresent the same entity the ChangeTracker cannot track the changes correctly.
I post my new controller and repository code if someone will burst into my same problem.
Controller:
[HttpPut("{id}")]
[Authorize(Roles = "Backoffice")]
public async Task<ActionResult<ContractDTO>> PutContract(int id, [FromBody] PutContractViewModel viewModel)
{
if (viewModel == null || !ModelState.IsValid)
return BadRequest(new { message = "Your model is wrong" });
ContractDTO modifiedContract;
try
{
modifiedContract = await _contractService.UpdateContract(viewModel);
}
catch (EntityNotFoundException)
{
return NotFound();
}
if (modifiedContract == null)
return BadRequest(new { message = "User or car may be busy in another contract" });
return Ok(modifiedContract);
Service:
public async Task<ContractDTO> UpdateContract(PutContractViewModel viewModel)
{
try
{
return await ParseContractToContractDTO(await _contractRepository.Update(ParsePutContractViewModelToContract(viewModel)));
}
catch(EntityNotFoundException)
{
throw;
}
}
Repository:
public async Task<Contract> Update(Contract entity)
{
var dbContract = await _context.Contracts.Include(c => c.User).Include(c => c.Car).FirstOrDefaultAsync(c => c.Id == entity.Id);
if (dbContract == null)
{
throw new EntityNotFoundException();
}
var dbUser = await _context.Users.Include(u => u.Contract).FirstOrDefaultAsync(u => u.Id == entity.UserId);
if (dbUser == null)
return null;
var dbCar = await _context.Cars.Include(c => c.Contract).FirstOrDefaultAsync(c => c.Id == entity.CarId);
if (dbCar == null)
return null;
dbContract.ExpiryDate = entity.ExpiryDate;
dbContract.MonthlyFee = entity.MonthlyFee;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException)
{
return null;
}
return await GetById(entity.Id);
}
I want to thank you all, you've been very helpful and patient with me.
I am using the following code for login procedure.I kept the breakpoint at Login Controller and validateUser function of service layer to check where did I go wrong and I found out that I am getting the null value in dbUser object at ValidateUser function of service layer and it is showing invalid login attempt. Where did I go wrong? Am I missing something?
Controller View
public ActionResult Login(UserLoginDTO userLoginDTO)
{
try
{
if (ModelState.IsValid)
{
userLoginDTO.Password = Helper.EncodePasswordToBase64(userLoginDTO.Password);
UserLoginDTO user = userLoginService.ValidateUser(userLoginDTO);
if (user != null)
{
this.SignInUser(user, false);
return RedirectToAction("Index", "Home");
}
ModelState.AddModelError("", "Invalid login attempt.");
}
}
catch (Exception ex)
{
Log.Error(ex);
//throw;
}
return View(userLoginDTO);
}
Model View
public class UserLoginDTO
{
public int Id { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
}
Service Layer
public interface IUserLoginService
{
UserLoginDTO ValidateUser(UserLoginDTO userLoginModel);
}
public class UserLoginService : IUserLoginService
{
private IUnitOfWork unitOfWork;
public UserLoginService(IUnitOfWork _unitOfWork)
{
unitOfWork = _unitOfWork ?? new UnitOfWork();
}
public UserLoginDTO ValidateUser(UserLoginDTO userLoginModel)
{
UserLogin dbUser = unitOfWork.UserLoginRepository.All()
.Where(x => x.UserName == userLoginModel.UserName
&& x.Password == userLoginModel.Password)
.FirstOrDefault();
if (dbUser != null)
{
UserLoginDTO userDTO = dbUser.Convert();
return userDTO;
}
return null;
}
}
Repository Layer
public interface IUnitOfWork
{
int Save();
Task<int> SaveAsync();
IRepository<UserLogin> UserLoginRepository { get; }
}
private IRepository<UserLogin> _userLoginRepository;
public IRepository<UserLogin> UserLoginRepository
{
get
{
return _userLoginRepository ??
(_userLoginRepository = new RepositoryBase<UserLogin>(_context));
}
}
I used this code for admin panel to changed password of customer by manager,I did not get any Error. but password not changed,
i got my coed from this
[HttpPost]
[ValidateAntiForgeryToken]
public virtual ActionResult ChangeUserPassword(SetPasswordViewModel model,string userId)
{
if (ModelState.IsValid)
{
//var result = await UserManager.AddPasswordAsync(User.Identity.GetUserId(), model.NewPassword);
ApplicationUser appUser = db.Users.Find(userId);
var result = UserManager.ChangePasswordAsync(appUser, model.NewPassword);
if (result.IsCompleted)
{
var user = UserManager.FindById(User.Identity.GetUserId());
//var user = db.Users.Find(userId);
if (user != null)
{
//await SignInManager<,>.SignInAsync(user, isPersistent: false, rememberBrowser: false);
}
return RedirectToAction("Index", new { Message = ManageController.ManageMessageId.SetPasswordSuccess });
}
// AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
}
and in controller for change use this
public async Task<IdentityResult> ChangePasswordAsync(ApplicationUser Appuser, string newPassword)
{
var store = this.Store as Microsoft.AspNet.Identity.IUserPasswordStore<ApplicationUser,string>;
if (store == null)
{
var errors = new string[]
{
"Current UserStore doesn't implement IUserPasswordStore"
};
return IdentityResult.Failed(errors);
// return Task.FromResult(new IdentityResult(errors) { Succeeded = false });
}
var newPasswordHash = this.PasswordHasher.HashPassword(newPassword);
await store.SetPasswordHashAsync(Appuser, newPasswordHash);
await store.UpdateAsync(Appuser);
//return await Task.FromResult<IdentityResult>(IdentityResult.Success);
return IdentityResult.Success;
}
}
whats my mistake?
after update answer i use this method instead
[HttpPost]
public async Task<IdentityResult> ChangePasswordAsync(ApplicationUser appuserId, string newPassword)
{
ApplicationDbContext db = new ApplicationDbContext();
//var userr =await db.Users.FirstOrDefaultAsync(x => x.Id == appuserId.Id);
var newPasswordHash = this.PasswordHasher.HashPassword(newPassword);
db.Entry(Users).State = EntityState.Modified;
if (appuserId != null) appuserId.PasswordHash = newPasswordHash;
db.SaveChanges();
return IdentityResult.Success;
}
but had the same problem again
in IdentityModels.cs i had
public DbSet<CommonAction> CommonActions { get; set; }
public DbSet<PublicContent> PublicContents { get; set; }
public DbSet<MainSubject> MainSubjects { get; set; }
public DbSet<EducationLevel> EducationLevels { get; set; }
public DbSet<TimePeriod> TimePeriods { get; set; }
public DbSet<HelpType> HelpTypes { get; set; }
public DbSet<FinancialSupport> FinancialSupports { get; set; }
public DbSet<LinksStatistic> LinksStatistics { get; set; }
public DbSet<SlideOfCommonAction> SlideOfCommonActions { get; set; }
in normal model IdentityModel User is not register as DbSet
()
You're not awaiting ChangePasswordAsync, you're just checking if it's completed or not. Which may result in the View being returned before the password has been changed.
Then you should try to use async/await all the way, if possible. That means that your action should be async as well.
[HttpPost]
[ValidateAntiForgeryToken]
public virtual async Task<ActionResult> ChangeUserPassword(SetPasswordViewModel model,string userId)
{
if (ModelState.IsValid)
{
//var result = await UserManager.AddPasswordAsync(User.Identity.GetUserId(), model.NewPassword);
ApplicationUser appUser = db.Users.Find(userId);
// await the result.
var result = await UserManager.ChangePasswordAsync(appUser, model.NewPassword);
if (result.Succeeded) // Or whatever you're expecting.
{
var user = UserManager.FindById(User.Identity.GetUserId());
//var user = db.Users.Find(userId);
if (user != null)
{
//await SignInManager<,>.SignInAsync(user, isPersistent: false, rememberBrowser: false);
}
return RedirectToAction("Index", new { Message = ManageController.ManageMessageId.SetPasswordSuccess });
}
// AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
}
Update after comments:
The entity type DbSet`1 is not part of the model for the current context
db.Entry(Users).State = EntityState.Modified;
This is when EF cannot connect your model to an entity in the context. Without seeing the code, it's hard to say exactly why. But it could be that your're missing a DbSet in your context.
Have you configured your UserManager correctly? Does the other UserManager actions work?
Try debugging and see where it crashes.
I followed the following tutorial to allow a user to login with Google and Facebook. How ever I have implemented It in a WEB API project as I am using a Node JS front end and Access by Mobile Applications. (http://www.asp.net/mvc/tutorials/mvc-5/create-an-aspnet-mvc-5-app-with-facebook-and-google-oauth2-and-openid-sign-on)
So what I would like to ask is how do I use the OAuth?
There is the default Account Controller with all the needed methods in.
1: How do I login to the WEB API From:
A: Web Site (Java Script)
B: Android Application?
From My understanding of it:
My Android application will have to call one of the Account Action Methods with the appropriate data.
Which Action Method?
What Data do I need to send it?
Here is the Account Controller: (Sorry Its quite long):
[Authorize]
[RoutePrefix("api/Account")]
public class AccountController : ApiController
{
private const string LocalLoginProvider = "Local";
public AccountController()
: this(Startup.UserManagerFactory(), Startup.OAuthOptions.AccessTokenFormat)
{
}
public AccountController(UserManager<IdentityUser> userManager,
ISecureDataFormat<AuthenticationTicket> accessTokenFormat)
{
UserManager = userManager;
AccessTokenFormat = accessTokenFormat;
}
public UserManager<IdentityUser> UserManager { get; private set; }
public ISecureDataFormat<AuthenticationTicket> AccessTokenFormat { get; private set; }
// GET api/Account/UserInfo
[HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
[Route("UserInfo")]
public UserInfoViewModel GetUserInfo()
{
ExternalLoginData externalLogin = ExternalLoginData.FromIdentity(User.Identity as ClaimsIdentity);
return new UserInfoViewModel
{
UserName = User.Identity.GetUserName(),
HasRegistered = externalLogin == null,
LoginProvider = externalLogin != null ? externalLogin.LoginProvider : null
};
}
// POST api/Account/Logout
[Route("Logout")]
public IHttpActionResult Logout()
{
Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType);
return Ok();
}
// GET api/Account/ManageInfo?returnUrl=%2F&generateState=true
[Route("ManageInfo")]
public async Task<ManageInfoViewModel> GetManageInfo(string returnUrl, bool generateState = false)
{
IdentityUser user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
if (user == null)
{
return null;
}
List<UserLoginInfoViewModel> logins = new List<UserLoginInfoViewModel>();
foreach (IdentityUserLogin linkedAccount in user.Logins)
{
logins.Add(new UserLoginInfoViewModel
{
LoginProvider = linkedAccount.LoginProvider,
ProviderKey = linkedAccount.ProviderKey
});
}
if (user.PasswordHash != null)
{
logins.Add(new UserLoginInfoViewModel
{
LoginProvider = LocalLoginProvider,
ProviderKey = user.UserName,
});
}
return new ManageInfoViewModel
{
LocalLoginProvider = LocalLoginProvider,
UserName = user.UserName,
Logins = logins,
ExternalLoginProviders = GetExternalLogins(returnUrl, generateState)
};
}
// POST api/Account/ChangePassword
[Route("ChangePassword")]
public async Task<IHttpActionResult> ChangePassword(ChangePasswordBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
IdentityResult result = await UserManager.ChangePasswordAsync(User.Identity.GetUserId(), model.OldPassword,
model.NewPassword);
IHttpActionResult errorResult = GetErrorResult(result);
if (errorResult != null)
{
return errorResult;
}
return Ok();
}
// POST api/Account/SetPassword
[Route("SetPassword")]
public async Task<IHttpActionResult> SetPassword(SetPasswordBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
IdentityResult result = await UserManager.AddPasswordAsync(User.Identity.GetUserId(), model.NewPassword);
IHttpActionResult errorResult = GetErrorResult(result);
if (errorResult != null)
{
return errorResult;
}
return Ok();
}
// POST api/Account/AddExternalLogin
[Route("AddExternalLogin")]
public async Task<IHttpActionResult> AddExternalLogin(AddExternalLoginBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
AuthenticationTicket ticket = AccessTokenFormat.Unprotect(model.ExternalAccessToken);
if (ticket == null || ticket.Identity == null || (ticket.Properties != null
&& ticket.Properties.ExpiresUtc.HasValue
&& ticket.Properties.ExpiresUtc.Value < DateTimeOffset.UtcNow))
{
return BadRequest("External login failure.");
}
ExternalLoginData externalData = ExternalLoginData.FromIdentity(ticket.Identity);
if (externalData == null)
{
return BadRequest("The external login is already associated with an account.");
}
IdentityResult result = await UserManager.AddLoginAsync(User.Identity.GetUserId(),
new UserLoginInfo(externalData.LoginProvider, externalData.ProviderKey));
IHttpActionResult errorResult = GetErrorResult(result);
if (errorResult != null)
{
return errorResult;
}
return Ok();
}
// POST api/Account/RemoveLogin
[Route("RemoveLogin")]
public async Task<IHttpActionResult> RemoveLogin(RemoveLoginBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
IdentityResult result;
if (model.LoginProvider == LocalLoginProvider)
{
result = await UserManager.RemovePasswordAsync(User.Identity.GetUserId());
}
else
{
result = await UserManager.RemoveLoginAsync(User.Identity.GetUserId(),
new UserLoginInfo(model.LoginProvider, model.ProviderKey));
}
IHttpActionResult errorResult = GetErrorResult(result);
if (errorResult != null)
{
return errorResult;
}
return Ok();
}
// GET api/Account/ExternalLogin
[OverrideAuthentication]
[HostAuthentication(DefaultAuthenticationTypes.ExternalCookie)]
[AllowAnonymous]
[Route("ExternalLogin", Name = "ExternalLogin")]
public async Task<IHttpActionResult> GetExternalLogin(string provider, string error = null)
{
if (error != null)
{
return Redirect(Url.Content("~/") + "#error=" + Uri.EscapeDataString(error));
}
if (!User.Identity.IsAuthenticated)
{
return new ChallengeResult(provider, this);
}
ExternalLoginData externalLogin = ExternalLoginData.FromIdentity(User.Identity as ClaimsIdentity);
if (externalLogin == null)
{
return InternalServerError();
}
if (externalLogin.LoginProvider != provider)
{
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
return new ChallengeResult(provider, this);
}
IdentityUser user = await UserManager.FindAsync(new UserLoginInfo(externalLogin.LoginProvider,
externalLogin.ProviderKey));
bool hasRegistered = user != null;
if (hasRegistered)
{
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
ClaimsIdentity oAuthIdentity = await UserManager.CreateIdentityAsync(user,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookieIdentity = await UserManager.CreateIdentityAsync(user,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = ApplicationOAuthProvider.CreateProperties(user.UserName);
Authentication.SignIn(properties, oAuthIdentity, cookieIdentity);
}
else
{
IEnumerable<Claim> claims = externalLogin.GetClaims();
ClaimsIdentity identity = new ClaimsIdentity(claims, OAuthDefaults.AuthenticationType);
Authentication.SignIn(identity);
}
return Ok();
}
// GET api/Account/ExternalLogins?returnUrl=%2F&generateState=true
[AllowAnonymous]
[Route("ExternalLogins")]
public IEnumerable<ExternalLoginViewModel> GetExternalLogins(string returnUrl, bool generateState = false)
{
IEnumerable<AuthenticationDescription> descriptions = Authentication.GetExternalAuthenticationTypes();
List<ExternalLoginViewModel> logins = new List<ExternalLoginViewModel>();
string state;
if (generateState)
{
const int strengthInBits = 256;
state = RandomOAuthStateGenerator.Generate(strengthInBits);
}
else
{
state = null;
}
foreach (AuthenticationDescription description in descriptions)
{
ExternalLoginViewModel login = new ExternalLoginViewModel
{
Name = description.Caption,
Url = Url.Route("ExternalLogin", new
{
provider = description.AuthenticationType,
response_type = "token",
client_id = Startup.PublicClientId,
redirect_uri = new Uri(Request.RequestUri, returnUrl).AbsoluteUri,
state = state
}),
State = state
};
logins.Add(login);
}
return logins;
}
// POST api/Account/Register
[AllowAnonymous]
[Route("Register")]
public async Task<IHttpActionResult> Register(RegisterBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
IdentityUser user = new IdentityUser
{
UserName = model.UserName
};
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
IHttpActionResult errorResult = GetErrorResult(result);
if (errorResult != null)
{
return errorResult;
}
return Ok();
}
// POST api/Account/RegisterExternal
[OverrideAuthentication]
[HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
[Route("RegisterExternal")]
public async Task<IHttpActionResult> RegisterExternal(RegisterExternalBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
ExternalLoginData externalLogin = ExternalLoginData.FromIdentity(User.Identity as ClaimsIdentity);
if (externalLogin == null)
{
return InternalServerError();
}
IdentityUser user = new IdentityUser
{
UserName = model.UserName
};
user.Logins.Add(new IdentityUserLogin
{
LoginProvider = externalLogin.LoginProvider,
ProviderKey = externalLogin.ProviderKey
});
IdentityResult result = await UserManager.CreateAsync(user);
IHttpActionResult errorResult = GetErrorResult(result);
if (errorResult != null)
{
return errorResult;
}
return Ok();
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
UserManager.Dispose();
}
base.Dispose(disposing);
}
#region Helpers
private IAuthenticationManager Authentication
{
get { return Request.GetOwinContext().Authentication; }
}
private IHttpActionResult GetErrorResult(IdentityResult result)
{
if (result == null)
{
return InternalServerError();
}
if (!result.Succeeded)
{
if (result.Errors != null)
{
foreach (string error in result.Errors)
{
ModelState.AddModelError("", error);
}
}
if (ModelState.IsValid)
{
// No ModelState errors are available to send, so just return an empty BadRequest.
return BadRequest();
}
return BadRequest(ModelState);
}
return null;
}
private class ExternalLoginData
{
public string LoginProvider { get; set; }
public string ProviderKey { get; set; }
public string UserName { get; set; }
public IList<Claim> GetClaims()
{
IList<Claim> claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.NameIdentifier, ProviderKey, null, LoginProvider));
if (UserName != null)
{
claims.Add(new Claim(ClaimTypes.Name, UserName, null, LoginProvider));
}
return claims;
}
public static ExternalLoginData FromIdentity(ClaimsIdentity identity)
{
if (identity == null)
{
return null;
}
Claim providerKeyClaim = identity.FindFirst(ClaimTypes.NameIdentifier);
if (providerKeyClaim == null || String.IsNullOrEmpty(providerKeyClaim.Issuer)
|| String.IsNullOrEmpty(providerKeyClaim.Value))
{
return null;
}
if (providerKeyClaim.Issuer == ClaimsIdentity.DefaultIssuer)
{
return null;
}
return new ExternalLoginData
{
LoginProvider = providerKeyClaim.Issuer,
ProviderKey = providerKeyClaim.Value,
UserName = identity.FindFirstValue(ClaimTypes.Name)
};
}
}
private static class RandomOAuthStateGenerator
{
private static RandomNumberGenerator _random = new RNGCryptoServiceProvider();
public static string Generate(int strengthInBits)
{
const int bitsPerByte = 8;
if (strengthInBits % bitsPerByte != 0)
{
throw new ArgumentException("strengthInBits must be evenly divisible by 8.", "strengthInBits");
}
int strengthInBytes = strengthInBits / bitsPerByte;
byte[] data = new byte[strengthInBytes];
_random.GetBytes(data);
return HttpServerUtility.UrlTokenEncode(data);
}
}
#endregion
}
}