Asp.net Core Email confirmation sometimes says InvalidToken - c#

I am using asp.net core identity 2.1 and i am having a random issue with email confirmation, which while email confirmation sometimes says result.Error = InvalidToken. The token is also not expired.
Note: We are using multiple servers, and we have also stored our keys in one place so that all the servers use the same keys.
Code snippet for email confirmation.
Email Confirmation
var confCode = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.Action("ConfirmEmail", "Account", new
{
userId = user.Id,
code = WebUtility.UrlEncode(confCode)
}, protocol: HttpContext.Request.Scheme);
string confirmationEmailBody = string.Format(GetTranslatedResourceString("ConfirmationEmailBody"), "<a href='" + callbackUrl + "'>") + "</a>";
Verification of token
public async Task<bool> ConfirmEmailAsync(string userId, string code)
{
if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(code))
return false;
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
return false;
var result = await _userManager.ConfirmEmailAsync(user, code).ConfigureAwait(false);
if (!result.Succeeded)
result = await _userManager.ConfirmEmailAsync(user, WebUtility.UrlDecode(code)).ConfigureAwait(false);
return result.Succeeded;
}
Invalid Token
The below token is encoded twice but we handle that situation
CfDJ8HYrrpCgcr5GvrItPOWapXRy8WF8odd%252BVKuDup7buRsl1x4agRpfgQlEIPWiBqM0Wuilu9tCv5l%252B3lNaAb89%252Fi%252B4k0y%252FH0jdXAbabz0%252FXDGA0eUrmcKdIsDFNuXeyP5ezTVmTx8t0ky9xCTXaKLAfvTsCJviETk5Ag9JbUs3l3%252BnUon6fyYOHsslJI5VKLqhMM0Sm%252BW1EE%252B%252FPEJ%252BXcn%252FPS1My%252BI1lExuF1R1hFEZScEsUCG%252Bx%252BVIFB9bzs1IoLC%252Baw%253D%253D
Any help will be appreciated, Thank you!

This problem seems to be basic Query String Related issue.
There are no pointers in your question about sample expected value and sample actual value. Hence I will not be able to provide you exact answer here. But below two are the pointers which will certainly resolve this issue.
There can be two issues:
Issue 1: Original Base-64 is not restored after HtmlDecode/UrlDecode
These tokens are encoded as base 64 strings which may contain characters like '+'.
They are sent to server.
Then server tries to perform HtmlDecode operation on this string, to remove the characters which were actually present in original base 64 token.
E.g. '+' is replaced by empty string.
So, the token generated after WebUtility.HtmlDecode is invalid. That's why you get the invalid token error
How to check this ? You can debug and see what is the value after HtmlDecode and what is expected value. If they are differing then this is root cause.
Issue 2: Query string not correctly formed
Multiple key value pairs in query strings are joined using '&' character.
e.g. key1=value1&key2=value2
But some times instead of & , its encoded version & comes in the query string.
e.g. key1=value1&key2=value2
The .Net server would not be able to parse query string correctly if this is the case.
How to check this ? You can use directly read raw query string from HttpContext or HttpRequest using QueryString property and check if this is the case. If it is then you can either change your client to send appropriate query string (more logical and maintainable) or write some code to correct it on server side.
These pointers should help you to resolve the issue.

your request for email conformation will gives you response as userId and Your Private key but your token method should be different function like token refresh, you need to add some conditions like if token has expired then refresh token

This may not be the perfect answer, but if you just need an urgent fix and less headache, just generate a shorter disaster free token/code.
services.AddIdentity<ApplicationUser, ApplicationRole>(options => {
options.Tokens.PasswordResetTokenProvider = TokenOptions.DefaultEmailProvider;
options.Tokens.EmailConfirmationTokenProvider = TokenOptions.DefaultEmailProvider;
}).AddDefaultTokenProviders()
.AddEntityFrameworkStores<YourDbContext>();

You should decode the confirmation code prior to passing it into the _userManager.ConfirmEmailAsync(user, code).ConfigureAwait(false) method.
You have URL encoded the confirmation code that you use in the callBackUrl; you should use WebUtility.UrlDecode(code) to decode it before attempting to use it.

I think the user creation process is taking too much time
Before you are clicking confirm
Make sure you check database that user is created
Check confirm email is false in sql table

You do not need to solve this problem using identity server.
When a user register add two column in your user table. One for verification and others for IsVerified. In verification token column add a Guid. Then generate a link with this Guid. When a user clicks this link then you get the Guid inside a controller. Then get this user using this column, then set IsVerified column to true and delete your Guid column. And your user is now successfully verified.

If your encoded code contains '==' in the end. i.e after urlencode or base64 encode if the code comes out like this "cm9vdA==".
decoding this will not give you the exact encoded string and will result in an invalid code.
So while generating the token check if the encoded value ends with "==". If it does generate another token and the problem will be solved.

Related

Invalid token when password resetting on Microsoft.AspNetCore.Identity but ONLY on server side, not localhost

I'm currently a beginner developer and I'm coding a password reset on my WebApp.
So as you can see everything is good in localhost but in live I got an invalid token and I don't know why:
public async Task<IActionResult> ForgotPassword(ForgotPasswordViewModel model)
{
if (ModelState.IsValid)
{
var user = await _userManager.FindByEmailAsync(model.Email);
if (user == null)
{
// Don't reveal that the user does not exist or is not confirmed
return RedirectToAction(nameof(ForgotPasswordConfirmation));
}
var code = await _userManager.GeneratePasswordResetTokenAsync(user);
var callbackUrl = Url.ResetPasswordCallbackLink(user.Id, code, Request.Scheme);
// Go to the change password page
Response.Redirect(callbackUrl);
}
// If we got this far, something failed, redisplay form
return View(model);
}
I have checked the files in my FTP and everything is the same in AccountController.cs.
My token is supposed to be built with some special characters as "+" and I know my url will be like http://localhost:5000/Account/ResetPassword?userId=00c2a3ad-64c8-49d3-95e5-49a120831b85&code=CfDJ8MVJi%2BoeOmp....
I have figured my callbackUrl has a different behavior onlineat the %25
Do I have decoded the URL and I found %25 means "%" in UTF-8 and I think it's an encoding/decoding problem so I trued to encode like this code = HttpUtility.UrlEecode(code) and decoding it but it's even worse because my token is getting invalid so maybe I got completely my theory is wrong.
Have you any clues? any soution to my problem? Glad to disscuss with.
Thanks for paying attention to my issue,
BR X-MAS Greetings,
Lap1
EDIT:
Okay first of all thanks for so I checked my versions on the both environnemnets by using dotnet -- info and here are my versions. and . So I know I installed a newer version of .NET Framework on my PC. But there is remaining the 2.0.0 version and it's used for every debugging. I don't know if the problem is here so If you have any clues I'm here to exploit this.
EDIT 2: so I uninstalled the newer versions (not a good idea buut I did just in case) to keep the same versions in 2.2.202 and my code still running
EDIT3: ok so I will keep updating my thoughts about my issue. I think it's because the code is encoded once before I see the URL. e.g. + becomes %2F and it becomes %252F in the URL. I never encoded the code string but there is somewhere in the server some options...

Query String Parameter Being Lost on Request

i'm developing an MVC 4 web application.
I'm trying to make an url that changes in an authorized/unauthorized context.
I'm generating the following url for unauthorized user:
http://localhost/vendas-web/Login?ReturnUrl=%2Fvendas-web%2FClienteNovo%2FIndex%299999
The first time I've tested, it worked just fine.
But.. the second time I've tried, the query string got lost.. and the url turned into:
http://localhost/vendas-web/Login
When i test it against chrome on anonymous tab, it works FINE.
When i change the value of the last parameter, it works FINE.
There's some sort of cache related to this ?
What i'm doing wrong ?
Soo, my question is:
How do i keep my full url in any scenario ??
Ty
There's really not enough information here, but what you're likely talking about is that the first time a user needs to be authorized, they are automatically redirected to the first URL, which includes the ReturnUrl bit. That's built into the framework to allow the user to be redirected back to that URL after logging in. However, if you need to persist this past that initial first redirect to the login page, that's on you. Any links must manually add the query string param:
#Url.Action("SomeAction", new { ReturnUrl = Request["ReturnUrl"] })
And any forms must include it as a hidden input:
#Html.Hidden("ReturnUrl", Request["ReturnUrl"])
Otherwise, yes, it will be lost, because the literal URL you're now requesting doesn't include it. It's not just magically appended.
My problem was cache...
I've used this annotation to avoid using cache by application.
[OutputCache(NoStore = true, Duration = 0)]

ASP.NET Identity 2 Email Confirmation Invalid Token

I know, this question has been asked many times in SO, but my problem is a bit different.
I'm having a odd problem with GenerateEmailConfirmationTokenAsync and ConfirmEmailAsync methods.
I'm properly using HttpUtility.UrlEncode and HttpUtility.UrlDecode methods before sending the email.
The odd thing is I can never reproduce any error while creating user and after getting the mail, confirming it. But in the same environment, 3 out of 10 users that signs up reports problem of Invalid Token.
I searched a bit more and I found it can happen due to Machine Key which can get changed if IIS is restarted or something or after publish. So to tackle the same, I have generated a Machine Key and kept in web.config but still the issue seems to be there.
I'm hosting this in Azure App Service.
Any more idea of what else is going wrong?
UPDATE: I'm adding the code here for you guys to review
string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
var callbackUrl = new Uri(string.Format("{0}?userId={1}&code={2}", ConfigurationManager.AppSettings["EmailConfirmationURL"], user.Id, HttpUtility.UrlEncode(code)));
string emailTemplate = MailTemplates.UserRegistrationEmailTemplate(FirstName, CompanyName, callbackUrl);
await CustomEmail.SendEmail(new List<string> { user.Email }, "Confirm your account", emailTemplate);
And the confirmation works as
IdentityResult result = await UserManager.ConfirmEmailAsync(userId, HttpUtility.UrlDecode(code));
One possible reason could be that the token is expiring before being validated if the user takes too long to submit the token. Tokens have a lifespan within which they must be used before they are invalidated. The default TokenLifespanis one day (24hrs).
Check the identity config code to see you any change was made to the token life span.
For example the following code sets the tokens to expire in 3 hours
if (dataProtectionProvider != null)
{
manager.UserTokenProvider =
new DataProtectorTokenProvider<ApplicationUser>
(dataProtectionProvider.Create("ASP.NET Identity"))
{
TokenLifespan = TimeSpan.FromHours(3)
};
}
With the code above, the forgotten password and the email confirmation tokens will expire in 3 hours. Users trying to use a token after the expiration will get invalid token error.
The OP indicated an inability to recreate the issue. This could be because the tokens are being validated within the token lifespan.
Please use Url.Action instead of creating the url using string concatenation. Url.Action does the encoding behind the scenes in latest MVC versions and you can avoid the encoding and decoding operations.
Below is the code snippet you can use.
string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
var callbackUrl = Url.Action("ConfirmEmail", "Account", new
{
userId = user.Id,
code = code,
returnUrl = model.ReturnUrl
}, protocol: Request.Url.Scheme);

DotNetOpenAuth: Want to access username while storing Nonce

I am using DotNetOpenAuth 4.0.20926 and trying to implement an OAuth2 based Authentication server with Db Nonce provider.
For some purpose I want to access username in NonceStore's StoreNonce function while processing the GetToken request.
I am not getting a way to retrieve Username in that call.
How can I solve this problem?
Hey Andrew thanks for your reply and DotNetOpenAuth.
My GetToken Method is like this
public ActionResult Token()
{
string userName = "";
//Want to fetch username here
//Using username here
var result = this.authorizationServer.HandleTokenRequest(this.Request);
return result.AsActionResult();
}
And I want to fetch the username before calling HandleTokenRequest.
Is there any Message Parser or Helper method to fetch the username from the request data / Code value .
As you've observed, the interface does not pass the username into the StoreNonce method. So the only way you may possibly be able to get the username would be for you to discover what it is first, before you instantiate your INonceStore instance, and pass it to that nonce store first, so that later when StoreNonce is invoked, it already knows the username.
That said, I believe any design where storing and checking a nonce requires the username needs some rethinking. Not only is it a mixing of concerns that otherwise should remain separate, you may be limiting yourself going forward or even introducing security holes.

How to pass query string parameter in asp.net?

I am using Access Control service (ACS). I fetched all identity providers (ip) which i set for my application using the following code :
public ActionResult IdentityProviders(string serviceNamespace, string appId)
{
string idpsJsonEndpoint = string.Format(Global.IdentityProviderJsonEndpoint, serviceNamespace, appId);
var client = new WebClient();
var data = client.DownloadData(idpsJsonEndpoint);
return Content(Encoding.UTF8.GetString(data), "application/json");
}
When user click over the signin link the above code called using ajax and get the ips and display them in jquery-ui dialog. And when user click any one of the ips for login the browser redirect to the selected ip login page. After successful login the control return to my control which i set as a returnUrl. Upto this every thing is works fine.
Now what i am trying to do is to pass some values to identity provider (ip) login page and want to get back those values at my returnUrl controller. For this i searched and came to know that there is a query string parameter known as wctx which we can set and get the value at return url. But i dont know how to do this. Can anybody please guid me how can i achieve this?
It is relatively (pretty) easy.
Your URL for listing IdPs looks something like this:
https://[your_namespace].accesscontrol.windows.net:443/v2/metadata/IdentityProviders.js?protocol=wsfederation&realm=[your_realm]&reply_to=[configured_return_url_for_your_rp]&context=&request_id=&version=1.0&callback=
This is the most complete request for list of Identity Providers. Your may miss some variables (such as context, or reply_to), but what I show is the complete request.
So now you have two options:
inclide your own reply_to parameter. It must be withing the configured realm. So if your realm is https://www.mygreatapp.com/, your default return URL would probably be something like https://www.mygreatapp.com/returnUrl/ (if your controller to handle ACS response is returnUrlController. Now, you can safely change the reply_to to be https://www.mygreatapp.com/returnUrl/?foo=bar, just make sure you URL Encode the query string.
Use the context parameter. It is safer to use and I would suggest using it. Now your URL for fetching list of IdPs will be something like:
https://[your_namespace].accesscontrol.windows.net:443/v2/metadata/IdentityProviders.js?protocol=wsfederation&realm=[your_realm]&reply_to=[configured_return_url_for_your_rp]&context=[your_custom_string_value_which_you_may_even_encrypt]&request_id=&version=1.0&callback=
Note the now there is context value present in the request for IdP list ([your_custom_string_value_which_you_may_even_encrypt]). In your returnUrl handler controller, you can check for it with code similar (or equal) to the following:
if (ControllerContext.HttpContext.Request.Form["wresult"] != null)
{
// This is a response from the ACS - you can further inspect the message if you will
SignInResponseMessage message =
WSFederationMessage.CreateFromNameValueCollection(
WSFederationMessage.GetBaseUrl(ControllerContext.HttpContext.Request.Url),
ControllerContext.HttpContext.Request.Form)
as SignInResponseMessage;
if (!string.IsNullOrWhiteSpace(message.Context))
{
// do whatever you want with the context value
}
}
You may want to perform any/more additional checks while handling the SignInResponse from ACS.

Categories