Email Confirm in MVC 5 - c#

I am trying to send a confirmation email from my API. The mail is sent without problems.
When I load the url from MVC5, I have this error:
I tried:
Asp.NET Identity 2 giving "Invalid Token" error
http://www.gunaatita.com/Blog/Invalid-Token-Error-on-Email-Confirmation-in-Aspnet-Identity/1056 --> My api and my MVC are two projects hosted on two servers, for this reason I try using machineKey validationKey.
The code I use is below:
Web API
if (!await db.Users.AnyAsync(c => c.Email == userRequest.Email)) return StatusCode(HttpStatusCode.NotFound);
var userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(userContext));
userManager.UserTokenProvider = new TotpSecurityStampBasedTokenProvider<ApplicationUser, string>();
var user = await userManager.FindByNameAsync(userRequest.Email);
var userId = user.Id;
var code = await userManager.GenerateEmailConfirmationTokenAsync(user.Id);
var url = "MyUrl" + "/Account/ConfirmEmail?userId=" + userId + "&code=" + code;
MVC5
if (userId == null || code == null)
{
return View("Error");
}
var result = await UserManager.ConfirmEmailAsync(userId, code);
return View(result.Succeeded ? "ConfirmEmail" : "Error");

For the token confirmation to work, the token needs to be saved in the Users table AspNetUsers.
Web API
string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
user.ConfirmationToken = code;
UserManager.Update(user);
MVC5
var result = await UserManager.ConfirmEmailAsync(userId, code);
switch (result.Succeeded)
{
case true:
// Your code
case false:
//
default:
//
}

I think it throws 'invalid token' because the code parameter is too complex for querystring (contains special characters). So it is not redirecting to the page properly. To solve this problem:
string val = HttpServerUtility.UrlTokenEncode(Encoding.ASCII.GetBytes(code));
You can change to 'code' parameter with this.

Related

Email confirmation in Web API

I have Web API application and I want to implement email confirmation.
Now I have method that takes address -- the client host which will be in callback url uriBuilder and will be opened by the user from mail:
public async Task<IdentityResult> RegisterAsync(string email, string userName, string password, string address)
{
var user = new ApplicationUser { Email = email, UserName = userName };
var result = await _userManager.CreateAsync(user, password);
if (result.Succeeded)
{
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var uriBuilder = new UriBuilder(address) { Port = -1 };
var query = HttpUtility.ParseQueryString(uriBuilder.Query);
query["userId"] = user.Id;
query["code"] = code;
uriBuilder.Query = query.ToString();
await _emailService.SendEmailAsync
(
user.Email,
"Email confirmation",
$"Confirm the registration by clicking on the <a href='{uriBuilder}'>link</a>."
);
}
return result;
}
Then on the client side will be POST call to the API:
public async Task<bool> ConfirmEmailAsync(string userId, string code)
{
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
{
throw new UserNotFoundException();
}
var result = await _userManager.ConfirmEmailAsync(user, code);
return result.Succeeded;
}
This is so that user do not interact with API directly.
Is it ok to pass the host address in the request? If not, what should I do if there are several clients? In case of one client I can move it to config.
Yeap, it is ok to pass the host as parameter in the request.
I suppose a user should be in your scenario associated with specific host/address. You can store in the Db the host associated with the user and get that on the RegisterAsync method.

My service doesn't get my Post entity (PostAsJsonAsync)

Hi i'm new on programming and i'm working on some code. When i want to post a created entity my service return a null entity and go on error 400 BadRequest and i can't understand why
var action = _url + "/NewUser";
EntityUser newUser = new EntityUser();
try
{
newUser.Id = 1;
newUser.Name = "Jack";
newUser.Surname = "Black";
HttpResponseMessage responseMessage = await _client.PostAsJsonAsync<EntityUser>(action, newUser);
}
this is the other part of the code on my service where my User is null
public async Task<long> NewUser(EntityUser user)
{
return await Task.Factory.StartNew(() => new DtoUser().NewUser(user));
}
I'm sure my user isn't null when i post it so i don't understand why he doens't get it. It should create another user from the data i gave him.

Get User From Usermanger After Login

I'm trying to get a user from the usermanger after External Login, using .Net-Core and IdentityServer4
public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
{
if (remoteError != null)
{
ErrorMessage = $"Error from external provider: {remoteError}";
return RedirectToAction(nameof(Login));
}
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
return RedirectToAction(nameof(Login));
}
// Sign in the user with this external login provider if the user already has a login.
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
if (result.Succeeded)
{
var user = await _userManager.GetUserAsync(HttpContext.User);
var serviceProvider = HttpContext.RequestServices;
var context = serviceProvider.GetRequiredService<PublicAstootContext>();
var coinbaseRule = new CoinbaseRule();
await coinbaseRule.UpdateCoinbaseAccountInfo(context, user.Id, info);
//...
}
}
However, even after the login succeeds, when I attempt to get the user from the usermanger, the user is always null.
How can I get the user from the usermanger after my external login?
The call to ExternalLoginSignInAsync does not populate HttpContext.User - it ends up writing an authentication cookie that is read in subsequent requests when attempting to populate HttpContext.User, but not before. In your example, the call to ExternalLoginSignInAsync occurs within the same request as the call to GetUserAsync, which means that HttpContext.User will not represent an authenticated user and so no match will be found.
Instead, you can use UserManager.FindByLoginAsync to get the correct value for user:
var user = await _userManager.FindByLoginAsync(
info.LoginProvider, info.ProviderKey);

Why new fb api 2.4 returns null email on MVC 5 with Identity and oauth 2?

Everything used to work perfect until fb upgraded it's api to 2.4 (I had 2.3 in my previous project).
Today when I add a new application on fb developers I get it with api 2.4.
The problem: Now I get null email from fb (loginInfo.email = null).
Of course I checked that the user email is in public status on fb profile,
and I went over the loginInfo object but didn't find any other email address.
and I google that but didn't find any answer.
please any help.. I 'm kind of lost..
Thanks,
My original code (which worked on 2.3 api):
In the AccountController.cs:
//
// GET: /Account/ExternalLoginCallback
[AllowAnonymous]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
return RedirectToAction("Login");
}
//A way to get fb details about the log-in user:
//var firstNameClaim = loginInfo.ExternalIdentity.Claims.First(c => c.Type == "urn:facebook:first_name"); <--worked only on 2.3
//var firstNameClaim = loginInfo.ExternalIdentity.Claims.First(c => c.Type == "urn:facebook:name"); <--works on 2.4 api
// Sign in the user with this external login provider if the user already has a login
var result = await SignInManager.ExternalSignInAsync(loginInfo, isPersistent: false);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = false });
case SignInStatus.Failure:
default:
// If the user does not have an account, then prompt the user to create an account
ViewBag.ReturnUrl = returnUrl;
ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = loginInfo.Email }); //<---DOESN'T WORK. loginInfo.Email IS NULL
}
}
In the Startup.Auth.cs:
Microsoft.Owin.Security.Facebook.FacebookAuthenticationOptions fbOptions = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationOptions()
{
AppId = System.Configuration.ConfigurationManager.AppSettings.Get("FacebookAppId"),
AppSecret = System.Configuration.ConfigurationManager.AppSettings.Get("FacebookAppSecret"),
};
fbOptions.Scope.Add("email");
fbOptions.Provider = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationProvider()
{
OnAuthenticated = (context) =>
{
context.Identity.AddClaim(new System.Security.Claims.Claim("FacebookAccessToken", context.AccessToken));
foreach (var claim in context.User)
{
var claimType = string.Format("urn:facebook:{0}", claim.Key);
string claimValue = claim.Value.ToString();
if (!context.Identity.HasClaim(claimType, claimValue))
context.Identity.AddClaim(new System.Security.Claims.Claim(claimType, claimValue, "XmlSchemaString", "Facebook"));
}
return System.Threading.Tasks.Task.FromResult(0);
}
};
fbOptions.SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie;
app.UseFacebookAuthentication(fbOptions);
Taken from a Katana Thread I devised the following:
Change the FacebookAuthenticationOptions to include BackchannelHttpHandler and UserInformationEndpoint as seen below. Make sure to include the names of the fields you want and need for your implementation.
var facebookOptions = new FacebookAuthenticationOptions()
{
AppId = "*",
AppSecret = "*",
BackchannelHttpHandler = new FacebookBackChannelHandler(),
UserInformationEndpoint = "https://graph.facebook.com/v2.4/me?fields=id,name,email,first_name,last_name"
}
Then create a custom FacebookBackChannelHandler that will intercept the requests to Facebook and fix the malformed url as needed.
UPDATE: The FacebookBackChannelHandler is updated based on a 27 Mar 2017 update to the FB api.
public class FacebookBackChannelHandler : HttpClientHandler
{
protected override async System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
if (!request.RequestUri.AbsolutePath.Contains("/oauth"))
{
request.RequestUri = new Uri(request.RequestUri.AbsoluteUri.Replace("?access_token", "&access_token"));
}
var result = await base.SendAsync(request, cancellationToken);
if (!request.RequestUri.AbsolutePath.Contains("/oauth"))
{
return result;
}
var content = await result.Content.ReadAsStringAsync();
var facebookOauthResponse = JsonConvert.DeserializeObject<FacebookOauthResponse>(content);
var outgoingQueryString = HttpUtility.ParseQueryString(string.Empty);
outgoingQueryString.Add("access_token", facebookOauthResponse.access_token);
outgoingQueryString.Add("expires_in", facebookOauthResponse.expires_in + string.Empty);
outgoingQueryString.Add("token_type", facebookOauthResponse.token_type);
var postdata = outgoingQueryString.ToString();
var modifiedResult = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(postdata)
};
return modifiedResult;
}
}
public class FacebookOauthResponse
{
public string access_token { get; set; }
public string token_type { get; set; }
public int expires_in { get; set; }
}
One useful addition would be to check for the version 3.0.1 of the library and throw an exception if and when it changes. That way you'll know if someone upgrades or updates the NuGet package after a fix for this problem has been released.
(Updated to build, work in C# 5 without new nameof feature)
For me this Issue was solved by upgrading to Microsoft.Owin.Security.Facebook 3.1.0 and adding 'email' to the Fields collection:
var options = new FacebookAuthenticationOptions
{
AppId = "-------",
AppSecret = "------",
};
options.Scope.Add("public_profile");
options.Scope.Add("email");
//add this for facebook to actually return the email and name
options.Fields.Add("email");
options.Fields.Add("name");
app.UseFacebookAuthentication(options);
To resolve this you need to install Facebook SDK from NuGet packages.
In StartUp File
app.UseFacebookAuthentication(new FacebookAuthenticationOptions
{
AppId = "XXXXXXXXXX",
AppSecret = "XXXXXXXXXX",
Scope = { "email" },
Provider = new FacebookAuthenticationProvider
{
OnAuthenticated = context =>
{
context.Identity.AddClaim(new System.Security.Claims.Claim("FacebookAccessToken", context.AccessToken));
return Task.FromResult(true);
}
}
});
In Controller or Helper
var identity = AuthenticationManager.GetExternalIdentity(DefaultAuthenticationTypes.ExternalCookie);
var accessToken = identity.FindFirstValue("FacebookAccessToken");
var fb = new FacebookClient(accessToken);
dynamic myInfo = fb.Get("/me?fields=email,first_name,last_name,gender"); // specify the email field
With this you can get the EmailId,First-last Name, Gender.
You can also add your additional required properties in that query string.
Hope this will help someone.
Just want to add on Mike's answer that this line
facebookOptions.Scope.Add("email");
still needs to be added after
var facebookOptions = new FacebookAuthenticationOptions()
{
AppId = "*",
AppSecret = "*",
BackchannelHttpHandler = new FacebookBackChannelHandler(),
UserInformationEndpoint = "https://graph.facebook.com/v2.4/me?fields=id,name,email,first_name,last_name,location"
}
And if you already registered your facebook account to your dev website with no "email permission". After changing the code and trying again, you will still not get the email because the email permission isn't granted to your dev website. The way I do is go to https://www.facebook.com/settings?tab=applications, remove my facebook app, and redo the process again.
Upgrade Microsoft.Owin to 3.0.1 ( Install-Package Microsoft.Owin.Security.OAuth )
in the Startup.Auth.cs add facebookOptions.UserInformationEndpoint = "https://graph.facebook.com/v2.4/me?fields=id,name,email";
Read the changelog, this is by design. You need to explicitly request the fields and edges you want retuned in the response:
Declarative Fields
To try to improve performance on mobile networks,
Nodes and Edges in v2.4 requires that you explicitly request the
field(s) you need for your GET requests. For example, GET
/v2.4/me/feed no longer includes likes and comments by default, but
GET /v2.4/me/feed?fields=comments,likes will return the data. For more
details see the docs on how to request specific fields.

"IUserTokenProvider not found" error when calling UserManager.GeneratePasswordResetTokenAsync

Can someone help me with the following code? I'm getting an error on this line and don't understand why:
string code = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
Full code:
var user = await UserManager.FindByEmailAsync(model.Email);//Find user by email entered
if (user == null)
{
return View("ForgotPasswordConfirmation");
}
string code = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
var callbackUrl = Url.Action("ResetPassword", "Login", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
The GeneratePasswordResetTokenAsync method requires a UserTokenProvider to be set in your UserManager.
You are receiving the error because of a null check in the GenerateUserTokenAsync called by your GeneratePasswordResetTokenAsync method.
A complementary to Scott Brady's answer. You should create the UserTokenProvider manually:
// db is of type DbContext or IdentityDbContext
var userManager = new UserManager(new UserStore(db));
var dataProtectionProvider = new DpapiDataProtectionProvider("Test");
userManager.UserTokenProvider = new DataProtectorTokenProvider<User, Guid>(dataProtectionProvider.Create("ASP.NET Identity"));
var user = await UserManager.FindByEmailAsync(model.Email);//Find user by email entered
//rest of the code

Categories

Resources