I use UserManager's GenerateChangeEmailTokenAsync() to generate and send the verification code to the new email. I'd like to check if the code has been used in an unauthenticated context and display a message to the user if the new email was verified already: "Your new email address has already been verified. Please log in".
Is there a method in UserManager to accomplish this?
I'm afraid there is no method in userManager can achieve your requirement, But i think you can pass this success message by yourself. You can refer to this code:
//I use this code to get the currently logged in user
var user = _userManager.GetUserAsync(User).Result;
var result = await _userManager.ChangeEmailAsync(user, newEmail, token);
if (result.Succeeded)
{
TempData["Message"] = "Your new email address has already been verified. Please log in";
return RedirectToAction("ChangeEmail", "Test");
}
else
{
TempData["Message"] = "Email change failed";
return RedirectToAction("ChangeEmail", "Test");
}
Test/ChangeEmail
[HttpGet]
public IActionResult ChangeEmail()
{
//use ViewBag to pass data from controller to view
ViewBag.Message = TempData["Message"];
return View();
}
View
//.....
<h1>#ViewBag.Message</h1>
If change email successfully, it will show message in the view
Related
Currently I'm creating an ASP.NET Core app that uses Identity.
I successfully built Sign In feature, with my code was made briefly:
public async Task<JsonResult> SignIn(string email, string password)
{
var result = new ApiResult();
var user = await userManager.FindByEmailAsync(email);
var signInResult = signInManager.CheckPasswordSignInAsync(user, password, lockOnFailure:true);
result.Success = signInResult.Succeeded;
return Json(result);
}
However, I couldn't use this.User in Controllers, also I can't get currently logged user.
m_currentUser = await userManager.GetUserAsync(User); // This cannot be accomplished
As I know, SignInManager sets automatically authentication data, What's wrong with this code?
I referred bunch of examples but nothing found any different.
SignInManager.CheckPasswordSignInAsync checks whether the given password is valid for the specified user. But it doesn't perform the sign-in process, which ends up creating a user's ClaimsPrincipal and persisting it via a cookie. You should use SignInManager.PasswordSignInAsync instead(this method also check the password use CheckPasswordSignInAsync) :
var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: true);
if (result.Succeeded)
{
_logger.LogInformation("User logged in.");
return LocalRedirect(returnUrl);
}
Or manually complete sign in after CheckPasswordSignInAsync :
await _signInManager.SignInAsync(user, isPersistent:true);
I need to send an email to users from an ASP.NET Core 2 application, following some business rules. However, I need to ensure that the account the email is being sent to actually exists (for some reason, it may be that the account stopped being valid). The customer is using Azure Active Directory, so I need to query AAD somehow so it lets me know whether the account exists or not.
So far I have been looking for Microsoft Graph as a way to do this, however every example I have seen so far requires prior authentication and use a delegate authentication mechanism. I don't want my users having to authenticate nor to prompt the authentication screen.
Given this situation, what would you recommend using? If you can also point me to an example, that would be great. Thanks!
You don't really need to throw/catch exception for every invalid user as you're doing in current code. I have nothing against exception handling in general for other reasons but to see if the user exists or not you can try using Filter.
So your graph query could look like -
https://graph.microsoft.com/v1.0/users?$filter=startswith(userPrincipalName,'someuser#mytenant.onmicrosoft.com')
I have shown startswith here becuase eq didn't work for me in a quick trial. Although I would recommend two things:
Go through Microsoft documentation on Filters here and see what works best for your requirements - Use query parameters to customize responses with Microsoft Graph
Play a little bit with different queries in Microsoft Graph Explorer it's very simple and easy to use.
Here is a modified version for your code.
Note that I'm checking for the collection count to be > 0 and not checking for it to be null, as even in case user is not found the UsersCollectionPage was not null for my test run.
using Microsoft.Identity.Client;
using Microsoft.Graph.Auth;
using Microsoft.Graph;
...
private async Task<bool> ValidateAccounts(string accounts) {
var confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create("clientId here")
.WithTenantId("tokenId here")
.WithClientSecret("secret here")
.Build();
var authProvider = new ClientCredentialProvider(confidentialClientApplication);
var graphClient = new GraphServiceClient(authProvider);
var valid = true;
try {
foreach (var account in accounts.Split(';')) {
var user = await graphClient.Users.Request().Filter("startswith(userPrincipalName, '" + account + "')").GetAsync();
if (user.Count <= 0) {
valid = false;
break;
}
}
} catch (ServiceException ex) {
valid = false;
}
return valid;
}
On a side note, I'm not not sure of your requirements but you could probably get creative by combining multiple user names in single query and then checking for result counts or other propertes. You could use or between multiple criteria or probably use any operator. I haven't really tried this out though.
Finally I came up with something workable. It's not nice, and it uses preview software. First, install Microsoft.Graph and Microsoft.Identity.Client packages. Then install Microsoft.Graph.Auth, which at the time of this writing, is in preview (v1.0.0-preview.1) so you'll need to tick "include prerelease" checkbox in nuget manager.
Then in your AAD, you need to get the ClientId, TenantId and SecretId. In my case, my app was already using AAD authentication so I already had ClientId and TenantId in my appsettings.json file. I only needed to create a new SecretId (in the Certificate & secrets section of my app registration). Then I needed to add permissions (in the API permissions section of my app registration) to include Microsoft.Graph with at least User.Read.All permission.
using Microsoft.Identity.Client;
using Microsoft.Graph.Auth;
using Microsoft.Graph;
...
private async Task<bool> ValidateAccounts(string accounts) {
var confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create("clientId here")
.WithTenantId("tokenId here")
.WithClientSecret("secret here")
.Build();
var authProvider = new ClientCredentialProvider(confidentialClientApplication);
var graphClient = new GraphServiceClient(authProvider);
var valid = true;
try {
foreach (var account in accounts.Split(';')) {
var user = await graphClient.Users[account]
.Request()
.GetAsync();
if (user == null) {
valid = false;
break;
}
}
} catch (ServiceException ex) {
valid = false;
}
return valid;
}
Here, the function takes a semicolon-separated string for each account. The GetAsync method will throw a ServiceException if the user does not exist. I don't like that, but couldn't find another way. So that's about it. Hope this helps someone else, and hope someone could come up with a better solution eventually.
Import following namespaces (You needs to install relevant packages using nuget):
using Microsoft.Graph;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
Set your Azure AD app values:
private string _tenant => "your_tenant_id";
private string _appId => "your_ad_app_client_id";
private string _appSecret => "your_app_client_secret";
Create Graph Service Client using this:
public static GraphServiceClient CreateGraphServiceClient()
{
var clientCredential = new ClientCredential(_appId, _appSecret);
var authenticationContext = new AuthenticationContext("https://login.microsoftonline.com/{_tenant}");
var authenticationResult = authenticationContext.AcquireTokenAsync("https://graph.microsoft.com", clientCredential).Result;
var delegateAuthProvider = new DelegateAuthenticationProvider((requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", authenticationResult.AccessToken);
return Task.FromResult(0);
});
return new GraphServiceClient(delegateAuthProvider);
}
var graphServiceClient = GraphServiceClientHelper.CreateGraphServiceClient();
Then call graph api and filter users by Email Address as follows:
var user = await graphServiceClient.Users.Request().Filter("mail eq '" + UserEmailAddress + "'").GetAsync();
if (user.Count == 0) {
//user not exist
}
Below code worked for me.
using Microsoft.Graph;
using Microsoft.Graph.Auth;
using Microsoft.Identity.Client;
private static async Task<bool> ValidateAccounts(string accounts)
{
var confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create("client id")
.WithTenantId("tenant id")
.WithClientSecret("client secret")
.Build();
var authProvider = new ClientCredentialProvider(confidentialClientApplication);
var graphClient = new GraphServiceClient(authProvider);
var valid = true;
try
{
foreach (var account in accounts.Split(';'))
{
var user = await
graphClient.Users.Request().Filter($"identities/any(c:c/issuerAssignedId eq
'{account}' and c/issuer eq 'xyz.onmicrosoft.com')").GetAsync();
if (user.Count <= 0)
{
valid = false;
break;
}
}
}
catch(Exception ex)
{
valid = false;
}
return valid;
}
I am using Microsoft.AspNetCore.Identity to implement basic system Login/Logout.
Here is a part of my code:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginVM login, string returnUrl=null)
{
if (!ModelState.IsValid)
return View(login);
var foundUser = await _userManager.FindByEmailAsync(login.EmailAddress);
if (foundUser != null)
{
if (!await _userManager.IsEmailConfirmedAsync(foundUser))
{
ViewBag.Message = "Your credentials are wrong or your account is not activated.";
return View(login);
}
}
else return RedirectToAction("Register", "Account");
var result = await _signinManager.PasswordSignInAsync(
login.EmailAddress,
login.Password,
login.RememberMe,
false);
if (!result.Succeeded)
{
ModelState.AddModelError("Login error - please try again or contact support team.", "");
return View(login);
}
if (string.IsNullOrWhiteSpace(returnUrl))
return RedirectToAction("Index", "Home");
return Redirect(returnUrl);
}
It works pretty good although, I can see my passwords on Headers through Network tab(Google Chrome developer tools).
Is there a way to avoid that? Can those credentials been hidden somehow?
Any help is welcome
When submitting a form, the input data will be visible to the user entering it. However, it will be encrypted in transit if you use https.
Technichally you can use javascript to obfuscate it, but it will not increase security.
I have an API implemented by asp.net core.
I've used OpenIddict to generate access token and refresh token for users who registered to my api by email and password.
I've added Google middleware (.UseGoogleAuthentication ... ) to my API and I can successfully log in user with Google.
My client is UWP and I use WebAuthenticationBroker to get redirected to google after sending a reuest to localhost/Account/ExternalLogin/Google.
when the users is logged In with google he is redirected to Account/ExternalLoginConfirmation which is trivial to this point now before it finishes with ExternalLoginConfirmation I Want to generate and send back an Access Token and a refresh token for the user cause if the WebAuthenticationBroker get's closed I have no other way to get tokens for this user(cause he has no password and the username will be unknown to me).
Itried this :
//
// POST: /Account/
[HttpPost("ExternalLoginConfirmation")]
[AllowAnonymous]
//[ValidateAntiForgeryToken]
public async Task<IActionResult> ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model,
string returnUrl = null)
{
if (ModelState.IsValid)
{
// Get the information about the user from the external login provider
var info = await SignInManager.GetExternalLoginInfoAsync();
if (info == null)
return View("ExternalLoginFailure");
var user = new UserInfo { UserName = model.Email, Email = model.Email };
var result = await UserManager.CreateAsync(user);
if (result.Succeeded)
{
result = await UserManager.AddLoginAsync(user, info);
if (result.Succeeded)
{
await SignInManager.SignInAsync(user, false);
Logger.LogInformation(6, "User created an account using {Name} provider.", info.LoginProvider);
var identity = new ClaimsIdentity(
OpenIdConnectServerDefaults.AuthenticationScheme,
OpenIdConnectConstants.Claims.Name, null);
// Add a "sub" claim containing the user identifier, and attach
// the "access_token" destination to allow OpenIddict to store it
// in the access token, so it can be retrieved from your controllers.
identity.AddClaim(OpenIdConnectConstants.Claims.Subject,
user.Id,
OpenIdConnectConstants.Destinations.AccessToken);
identity.AddClaim(OpenIdConnectConstants.Claims.Name, user.UserName,
OpenIdConnectConstants.Destinations.AccessToken);
// ... add other claims, if necessary.
var principal = new ClaimsPrincipal(identity);
var authenticateInfo = await HttpContext.Authentication.GetAuthenticateInfoAsync(info.LoginProvider);
var ticket = CreateTicketAsync(principal, authenticateInfo.Properties);
// Ask OpenIddict to generate a new token and return an OAuth2 token response.
return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
//return RedirectToLocal(returnUrl);
}
}
AddErrors(result);
}
//ViewData["ReturnUrl"] = returnUrl;
return BadRequest();
}
#region _helpers
private AuthenticationTicket CreateTicketAsync(ClaimsPrincipal principal,
AuthenticationProperties properties = null)
{
// Create a new authentication ticket holding the user identity.
var ticket = new AuthenticationTicket(principal, properties,
OpenIdConnectServerDefaults.AuthenticationScheme);
ticket.SetScopes(new[]
{
/* openid: */ OpenIdConnectConstants.Scopes.OpenId,
/* email: */ OpenIdConnectConstants.Scopes.Email,
/* profile: */ OpenIdConnectConstants.Scopes.Profile,
/* offline_access: */ OpenIdConnectConstants.Scopes.OfflineAccess,
/* roles: */ OpenIddictConstants.Scopes.Roles
});
ticket.SetAudiences(Configuration["Authentication:OpenIddict:Audience"]);
return ticket;
}
private void AddErrors(IdentityResult result)
{
foreach (var error in result.Errors)
ModelState.AddModelError(string.Empty, error.Description);
}
private IActionResult RedirectToLocal(string returnUrl)
{
if (Url.IsLocalUrl(returnUrl))
return Redirect(returnUrl);
return BadRequest();
}
#endregion
but this fails and throws exception :
an authorization or token response cannot be returned from this controller
now how do I generate these Tokens for the user?
now how do I generate these Tokens for the user?
OpenIddict deliberately prevents you from returning OIDC responses from non-OIDC endpoints (for obvious security reasons).
To make your scenario work, you must redirect your users back to the authorization endpoint with all the OpenID Connect parameters.
Concretely, you should revert all the changes added to ExternalLoginConfirmation() (that should return RedirectToLocal(returnUrl);) and move the SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme); part back to your authorization controller.
I have an API which dose not any related View and it's for authentication's methods, my change password has been doing something wrong, I don't know what's the exact problem then I have to trace it
My change password is :
[Route("password")]
[BasicAuthenticationFilter]
[HttpPost]
public async Task<UpdateResult<bool>> ChangePassword([FromBody]ChangePasswordViewModel password)
{
try
{
var result = await _userManagerService.ChangePassword(Thread.CurrentPrincipal.Identity.Name, password.NewPassword, password.OldPassword);
return UpdateResult<bool>.Success(result);
var ChangePasswordViewModel = new ChangePasswordViewModel { OldPassword = "", NewPassword = "" };
}
catch (Exception ex)
{
return UpdateResult<bool>.Fail(ex.Message, 1);
}
}
Because it's httpPost I'm using PostMan to trace it and my url is like this :
http://localhost:2511/v1/auth/password?password=new ChangePasswordViewModel{ OldPassword = "13532548", NewPassword = "123456" }
But it send my password parameter null, What's my problem?
You specified that your parameters should be passed in your request body by using [FromBody] attribute like this [FromBody]ChangePasswordViewModel password then this URL
http://localhost:2511/v1/auth/password?password=new ChangePasswordViewModel{ OldPassword = "13532548", NewPassword = "123456" }
is incorrect for passing data to your action. With Postman, you should do this if you want to reach your action:
Second just because you're using basic authentication then make sure to give the authentication data to Postamn too like this: