.netCore email verify before seding - asp.net-core

I'm looking for a method to validate the email address before sending. On email dns server.
I do NOT need regex or form email validation.
If user inputs test#google.com I want to know if there is an address like this on google server. I know there are some products that do the email validation. But we do not have money for that.
I'm using .netCore mailkit as smptClient. I've saw that there is a verify method but it somehow does the call but never returns.
Can you help please?

The only way to validate an email address is to send an email to that address with a link the user must click. That link should have a token uniquely generated for the user. Your site, then, looks up the token from the user when the user visits your site with that link and marks the associated user's email as verified.
If you're using Identity, support for this is baked in. There's a tutorial in the Microsoft docs. Essentially, it involves just adding a line in your AddIdentity config in Startup.cs:
config.SignIn.RequireConfirmedEmail = true;
Then, in your register action, you'd use the following to generate the URL for the link in the confirmation email:
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
Next, just send the email to the user in any way you like. In your email confirmation action, the following confirms the email based on the code in the URL:
var result = await _userManager.ConfirmEmailAsync(user, code);
EDIT
The Url.EmailConfirmationLink method actually comes from an extension added in the generated code when you add individual auth to a new project. Here's the code for that, for reference:
public static string EmailConfirmationLink(this IUrlHelper urlHelper, string userId, string code, string scheme)
{
return urlHelper.Action(
action: nameof(AccountController.ConfirmEmail),
controller: "Account",
values: new { userId, code },
protocol: scheme);
}

Related

Amplify "Unable to verify secret hash for client"

We have been using Amplify and Cognito to register our users for an Angular6 application deployed to Lambda. The client wanted to transition from email to username as primary user identification. So we created a new user pool / client. I don't have visibility into the configuration settings, I was simply given new user pool, identity pool, and client id's. Then I changed the code for application signup to look like this:
return from(Auth.signUp({
'username': username, // was email
'password': password,
attributes: { // added these
'email': email,
'phone_number': phone_number,
'family_name': name,
'birthdate': DOB,
'custom:last_4_ssn': SSN // custom attribute
}}));
The response I'm getting with no other changes made is: Unable to verify secret hash for client. Google claims the problem is that secretAccess is currently an unsupported configuration, but the guy who has access to these services swears to me that nowhere is secretAccess configured in our setup.
I apologize for not having access to the configuration, but is there any other possible reason to receive this error?
That error is probably originating from the fact that the app client you are connected to has an associated secret key. When you create a user pool app client, it generates a secret by default:
Right now, with React-Native Amplify you have to use an app client that does not have a secret key generated. So when you create a new app client with your desired attributes, make sure the "Generate client secret" box is unchecked.
The solution is to pass secret_hash along with the adminAuthInitiate Request. And to calculate the secret hash you can use the following method:
public static String calculateSecretHash(String userPoolClientId, String userPoolClientSecret, String userName) {
final String HMAC_SHA256_ALGORITHM = "HmacSHA256";
SecretKeySpec signingKey = new SecretKeySpec(
userPoolClientSecret.getBytes(StandardCharsets.UTF_8),
HMAC_SHA256_ALGORITHM);
try {
Mac mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
mac.init(signingKey);
mac.update(userName.getBytes(StandardCharsets.UTF_8));
byte[] rawHmac = mac.doFinal(userPoolClientId.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(rawHmac);
} catch (Exception e) {
throw new RuntimeException("Error while calculating ");
}
}
How to Pass Secret_Hash
Map<String, String> authParams = new HashMap<>(2);
authParams.put("USERNAME", <username>);
authParams.put("PASSWORD", <password>);
authParams.put("SECRET_HASH", calculateSecretHash(cognitoClientId, cognitoClientSecret, <username>));
AdminInitiateAuthRequest authRequest = new AdminInitiateAuthRequest()
.withClientId(userPool.getClientId()).withUserPoolId(userPool.getUserPoolId())
.withAuthFlow(AuthFlowType.ADMIN_NO_SRP_AUTH).withAuthParameters(authParams);
AdminInitiateAuthResult result = cognito.adminInitiateAuth(authRequest);
auth = result.getAuthenticationResult();

How can I get a user password using the Auth0 Management API nuget package?

We are currently trying to make a change to our website so that it uses Auth0 to authenticate. As part of that, I am rewriting a "config" website that we have for managing the users. The users will now be stored in Auth0 and the config website will therefore have to be able to add and edit Auth0 users in my tenant.
The config website uses the Auth0 Management API nuget package: https://github.com/auth0/auth0.net
But I have run into a problem. I can get a list of users and I can create a user. I can get the user's details and present them in an edit form onscreen, but I can't save the changes made, because when I try to do this I get an error that I need to supply a password in the UserUpdateRequest.
But when I get the user's details (client.Users.GetAsync(id)), it doesn't give me back a password property. If I could get the password from the call to GetAsync(id) then I could add it to the UserUpdateRequest. But if I can't get the password from GetAsync, how can I put the password in the UserUpdateRequest? How am I supposed to ever save a user?
I guess my ultimate question is: how can I get the user's password using the Management API...so that I can supply it later on to the UserUpdateRequest model when calling Users.UpdateAsync. Or if I can't get the user's password, can I somehow update the user without knowing their password?
It looks like the Nuget Management API was expecting this method to be used by the user themselves (and they could therefore put in their password to change their details), not an admin user operating through a config/admin website that wouldn't know the users password.
C# User/Edit [HttpGet] action method to get users and display them:
var token = GetAccessToken();
var apiClient = new ManagementApiClient(token, new Uri("https://MY_TENANT_ID.au.auth0.com/api/v2"));
var user = await apiClient.Users.GetAsync(id);
var userModel = MapUserToUserModel(user);
return View(userModel);
C# User/Edit [HttpPost] action method to save the changes to user's details:
var token = GetAccessToken();
var apiClient = new ManagementApiClient(token, new Uri("https://MY_TENANT_ID.au.auth0.com/api/v2"));
var updateReq = new UserUpdateRequest()
{
UserName = model.UserId,
Email = model.Email,
Password = model.Password,
EmailVerified = model.EmailVerified,
AppMetadata = model.AppMetadata,
UserMetadata = model.UserMetadata
};
var user = await apiClient.Users.UpdateAsync(model.UserId, updateReq);

Keycloak API: Getting specific message when user action is required

Im calling this API Keycloak endpoint
/auth/realms/master/protocol/openid-connect/token
to obtain the user token. But the response is
{
"error":"invalid_grant",
"error_description":"Account is not fully set up"
}
and status code 400.
This response is too ambiguous. How can I get more detailed response to know that i have to redirect the user to my custom "change password" page and how can i get the user token?
Login to Keycloak and check if there are any Required User Actions pending for the user
like (Update Password, Verify email, etc)
You can check Keycloak logs for more detail about the error
I had the same problem, so I used workaround. Workaround is Authentication SPI. I added new step into authentication flow like this:
#Override
public void authenticate(AuthenticationFlowContext context) {
var user = context.getUser();
var foundUpdatePasswordRequiredAction = user.getRequiredActionsStream()
.anyMatch(actionName -> UserModel.RequiredAction.UPDATE_PASSWORD.name().equals(actionName));
if (foundUpdatePasswordRequiredAction) {
context.getEvent().user(user);
context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
Response challengeResponse = errorResponse(Response.Status.UNAUTHORIZED.getStatusCode(), "temporary_password", "Constant password is not set up");
context.failure(AuthenticationFlowError.INVALID_USER, challengeResponse);
return;
}
context.success();
}
It means if user has required action and the action equals "UPDATE_PASSWORD" return my error code and my error message. It works good for me.

AspNet Core External Authentication with Both Google and Facebook

I am trying to implement the Form-Authentication in ASP.Net Core with Both Google and Facebook Authentications. I followed some tutorials and after some struggles, I managed to make it work both.
However, the problem is that I cannot use both authentications for the same email.
For example, my email is 'ttcg#gmail.com'.
I used Facebook authentication to log in first... Registered my email and it worked successfully and put my record into 'dbo.ASPNetUsers' table.
Then I logged out, clicked on Google Authentication to log in. It authenticated successfully, but when I tried to register it keeps saying that my email is already taken.
I tried to do the same thing for other online websites (Eg, Stackoverflow). I used the same email for both Google and Facebook and the website knows, I am the same person and both my login / claims are linked even though they come from different places (Google & Facebook).
I would like to have that feature in my website and could you please let me know how could I achieve that.
In theory, it should put another line in 'dbo.AspNetUserLogins' and should link the same UserId with multiple logins.
Do I need to implement my own SignInManager.SignInAsync method to achieve that feature? Or am I missing any configuration?
You need to link your Facebook external login to your Google external login with your email by using UserManager.AddLoginAsync, you cannot register twice using the same adresse if you use the adresse as login.
Check out the Identity sample on Identity github repo.
https://github.com/aspnet/Identity/blob/dev/samples/IdentitySample.Mvc/Controllers/ManageController.cs
To link external login to a user, the Manae controller expose methods LinkLogin and LinkLoginCallback
LinkLogin requests a redirect to the external login provider to link a login for the current user
LinkLoginCallback processes the provider response
//
// POST: /Manage/LinkLogin
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult LinkLogin(string provider)
{
// Request a redirect to the external login provider to link a login for the current user
var redirectUrl = Url.Action("LinkLoginCallback", "Manage");
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, _userManager.GetUserId(User));
return Challenge(properties, provider);
}
//
// GET: /Manage/LinkLoginCallback
[HttpGet]
public async Task<ActionResult> LinkLoginCallback()
{
var user = await GetCurrentUserAsync();
if (user == null)
{
return View("Error");
}
var info = await _signInManager.GetExternalLoginInfoAsync(await _userManager.GetUserIdAsync(user));
if (info == null)
{
return RedirectToAction(nameof(ManageLogins), new { Message = ManageMessageId.Error });
}
var result = await _userManager.AddLoginAsync(user, info);
var message = result.Succeeded ? ManageMessageId.AddLoginSuccess : ManageMessageId.Error;
return RedirectToAction(nameof(ManageLogins), new { Message = message });
}

How to get username from Exchange identity token?

I'm writing an Add-in for Office365/Outlook. The Add-in runs on a web-server that presents information from a third-party system. I need to make sure it only presents information related to the username (or email address) logged in. I've successfully sent and validated the Exchange identity token on my server, using the PHP example code provided by Microsoft:
https://dev.office.com/docs/add-ins/outlook/use-php-to-validate-an-identity-token
My problem is that the identity token does not contain any username or email adress, the closest I get is "msexchuid", but I can't make any sense out of that numeric user identifier in the third-party system.
On the client side the Add-in javascript can get a username and email via "Office.context.mailbox.userProfile", however I don't just want to forward that to my web server as it could be faked.
Is there a way to make the Identity token contain the username/email (that would be great!), or is it possible from my web server's server side PHP script lookup further user details based on the identity token?
The id token is used to intend to integrate with third-party application for SSO. As you mentioned that it only include a unique id of Exchange.
As a workaround, we can get from the callback token via the getCallbackTokenAsync method which include the SMTP address directly. And to validate the callback token, we can verify whether we can get the item info with EWS.
For example, there is an ‘parentItemId’ in the callback token. It is same that retrieve the claims from the callback token as id token since there are is JWT token. You can refer to here for more detail.
Then we can use the code below to get the item information from EWS:
public bool Post([FromBody]EWSRequest request)
{
ExchangeService service = new ExchangeService();
service.Credentials = new OAuthCredentials(request.token);
service.Url = new Uri(request.ewsURL);
//get item id from callback token
var itemId = "";
Item item = Item.Bind(service, itemId);
var subject = item.Subject;
return subject.Length>0;
}
public class EWSRequest
{
public string token;
public string ewsURL;
}
JavarScript :
Office.context.mailbox.getCallbackTokenAsync(getCallbackTokenCallback)
function getCallbackTokenCallback(asyncResult) {
var _token = asyncResult.value;
var _ewsURL = Office.context.mailbox.ewsUrl;
var serviceEndpoint = "https://localhost:44300/API/token/"
var postData={ token: _token, ewsURL: _ewsURL }
$.ajax({
url: serviceEndpoint,
type: "post",
contentType: "application/json",
data: JSON.stringify(postData),
success: function (result) {
var ret = result;
}
})
}