ASP.NET Identity 2 Email Confirmation Invalid Token - c#

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);

Related

How do I generate an assertion token for Sharepoint Access on behalf of another user in C#

I have a web application with a number of modules. One of the modules grabs a number of excel files from SharePoint directories, and then combines the data in them. So far, I have been just mapping the folders to OneDrive and accessing them that way. But this always uses my OneDrive credentials, which need to be refreshed from time to time. The right way to do this is to access them directly from Sharepoint on behalf of the user logged into my web application. I have the delegated API permission things set up in Azure, and I have the client ID and secret, etc.. I've been reading a number of articles on how to do this. All of them talk about how to get the token on behalf of someone else. These articles also talk about the assertion token needing to be passed in order to get the on behalf of token. However, they don't tell you how to get the assertion token in the first place. Here is the code I currently have:
'''var client = new RestClient("https://login.microsoftonline.com/XXXX/oauth2/v2.0/token");
var request = new RestRequest();
request.Method = Method.Post;
request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
request.AddParameter("client_id", "MYCLIENTID", ParameterType.GetOrPost);
request.AddParameter("client_secret", "MYSECRET", ParameterType.GetOrPost);
request.AddParameter("scope", "https://MYTenent.sharepoint.com/.default", ParameterType.GetOrPost);
request.AddParameter("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer", ParameterType.GetOrPost);
request.AddParameter("requested_token_use", "on_behalf_of", ParameterType.GetOrPost);
RestResponse response = client.Execute(request);'''
The result of this is of course an error that the assertion was not supplied. I didn't supply any more code, because I can't even get passed this. The rest of my code takes the token and passes it to an auth provider, which is then used to instantiate the GraphServiceClient. Based on what I've read, that client is then used to get the lists, files, etc...
So, my question is, how do I get the assertion token in the first place? I'm hoping the code I have written so far is in the correct direction and all I'm missing is the assertion token.
UPDATE:
I've gotten one answer that really didn't help. I pretty much copied and pasted the code (replacing the clientID, etc..) and I received an error> I was going to copy and paste it from the solution comments provided in the answer, but I guess you can't do that while editing.
Someone also asked if I was able to get the auth code from the URL. The answer to that is no. We use 2 factor authentication, and I tried to manually look at the URLS as I was logging in, while using break points to slow things down a bit. And I did not see an auth code. I did put a break point directly after the line of code:
var info = await _signInManager.GetExternalLoginInfoAsync();
And when I look at the info variable, I can see 4 tokens. One of them is an access token and another is an ID token. The last one is an expiration date. I don't see an auth code, and from what I understand, by the time I see the access code, it's too late. The auth code was already used to get the access code.
UPDATE 2:
I know that OBO is not what I want. I also know that in order to use delegated permissions, I need to use the Auth Code flow and not client credentials. I can't seem to get the auth code from the users initial log in. And I don't know how to get it otherwise.
For those of you that might be thinking "Does he need to be spoon fed?", the answer is yes, I do. I need a simple code example that will get me the auth code, so I can use it in the rest of the code I already have. If anyone can paste that code into an answer and not provide a link, that would be great. I'm sorry, but the links I have been given, just go to microsoft learn sites that go through the explaination, but don't give any code samples.
The OBO flow is obviously not applicable in your context, and if you're going to get an access token on behalf of a logged in user, then you should focus on auth code flow or ROPC flow.
The corresponding C# code segment is:
using Microsoft.Graph;
using Azure.Identity;
var scopes = new[] { "https://{tenant-name}.sharepoint.com/.default" };
// Multi-tenant apps can use "common",
// single-tenant apps must use the tenant ID from the Azure portal
var tenantId = "tenant id";
// Values from app registration
var clientId = "client id";
var clientSecret = "client secret";
// For authorization code flow, the user signs into the Microsoft
// identity platform, and the browser is redirected back to your app
// with an authorization code in the query parameters
var authorizationCode = "authorization code ";
// using Azure.Identity;
var options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
// https://learn.microsoft.com/dotnet/api/azure.identity.authorizationcodecredential
var authCodeCredential = new AuthorizationCodeCredential(
tenantId, clientId, clientSecret, authorizationCode, options);
var accessToken = await authCodeCredential.GetTokenAsync(new Azure.Core.TokenRequestContext(scopes) { });
Console.WriteLine(accessToken.Token);
//var graphClient = new GraphServiceClient(authCodeCredential, scopes);

Microsoft Identity reset password

I am working with a Blazor project with Microsoft Identity.
This seems fairly simple, but it's not working for me. I am executing the following (NOT production code, btw - just trying to figure out how this works!):
string token = await UserManager.GeneratePasswordResetTokenAsync(SiteUser);
result = await UserManager.ResetPasswordAsync(SiteUser, token, NewPassword);
The result is result.Succeeded yet when I logout, then try to log in with the new password, I receive an invalid login attempt.
I've also tried:
string token = await UserManager.GeneratePasswordResetTokenAsync(SiteUser);
result = await UserManager.ResetPasswordAsync(SiteUser, token, UserManager.PasswordHasher.HashPassword(SiteUser, NewPassword));
But again, no joy.
How do I change a registered user's password?
See the github repo here:
https://github.com/lxman/BlazorPasswordReset
Apparently this is related to changing the UserName. Reference ResetPassword.razor.
If I comment out lines 21, 22 and 23 it works. But if I put those lines back in, it fails.

Asp.net Core Email confirmation sometimes says InvalidToken

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.

OWIN MVC - Multiple LoginProviders, Current LoginProvider

I have multiple login providers available. I can login with external account or using forms auth. Everything works fine.
I'am redirecting user to HomePage, and now i would like to know which login provider was used.
Is there is a possibility to find out in controller, which loginprovider was used?
Thanks for help!
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
string provider = loginInfo.Login.LoginProvider; // Facebook, Google, Twitter, Microsoft...
I got this working although it's not something that would work in all cases.
Here's the code I used in a HomeController function:
// get the provider name
var authCtx = HttpContext.GetOwinContext();
var usrMgr = authCtx.GetUserManager<ApplicationUserManager>();
var user = usrMgr.FindByName(HttpContext.User.Identity.Name);
var extLoginInfo = usrMgr.GetLogins(user.Id).FirstOrDefault();
var loginProvider = extLoginInfo.LoginProvider.ToLower();
The main issue here is that I'm not getting the actual login provider that the user is currently logged-in with. I'm just getting the first login provider associated with this user (which in my case happens to be the same, so it works).
Would be great to find a way to get the actual provider that the user is currently logged-in with.

tweetsharp - app stops being able to tweet after a few hours

I have a asp.net 4.5 webforms site that allows users to link their account to twitter and tweet directly from my site.
My app is registered with twitter and I am able to successfully authorise my app for the user's account and initially can tweet fine, but after a few hours the tweets stop working. I am using tweetsharp to handle the authorisation.
my code is:
TwitterClientInfo twitterClientInfo = new TwitterClientInfo();
twitterClientInfo.ConsumerKey = ConsumerKey;
twitterClientInfo.ConsumerSecret = ConsumerSecret;
var requestToken = new OAuthRequestToken { Token = oauthtoken };
TwitterService twitterService = new TwitterService(ConsumerKey, ConsumerSecret);
OAuthAccessToken accessToken = twitterService.GetAccessToken(requestToken, oauthverifier);
twitterService.AuthenticateWith(accessToken.Token, accessToken.TokenSecret);
TwitterUser user = twitterService.VerifyCredentials(new VerifyCredentialsOptions());
SendTweetOptions options = new SendTweetOptions();
options.Status = tweetText;
twitterService.SendTweet(options);
what i have noticed is that while the app is successfully tweeting, the accessToken.Token value that is being used to authenticate the user has a proper value (a long string of numbers and upper/lowercase characters) however when it stops tweeting the accessToken.Token value is just a single question mark "?".
Twitter says it doesn't expire tokens so i am at a loss to understand what is happening or how it can be resolved? if i went in to my twitter account and deauthorised my app and went through the authorisation again it would work fine for a few hours, but obviously that's not something i can ask my users to do.
can anyone suggest a resolution to this - either to stop the accessToken value becoming ? or to handle it and get a proper value if it does (without reauthorising the app)
Well, without beginning to understand the actual issue, I managed to fix it
Instead of retrieving the access token every time via:
var requestToken = new OAuthRequestToken { Token = oauthtoken };
OAuthAccessToken accessToken = twitterService.GetAccessToken(requestToken, oauthverifier);
twitterService.AuthenticateWith(accessToken.Token, accessToken.TokenSecret);
i only do that once and store accessToken.Token and accessToken.TokenSecret in the database and retrieve them when tweeting and supply them
twitterService.AuthenticateWith(accessTokenFromDB, accessokenSecretFromDB);
I have seen somewhere that Twitter doesn't expire tokens, so this should work. Certainly it's been working for me all weekend whereas the original code would stop working after a few hours.
Thought this might help some others who have the same issue.

Categories