ConfirmEmailAsync() getting Invalid Token no matter what - c#

I am trying to verify the email confirmation token for user but always getting INVALID TOKEN error no matter what I do.
My code is very simple
To Generate the token
EmailVerificationCode = await userManager.GenerateEmailConfirmationTokenAsync(user);
EmailVerificationHTMLFormatCode = HttpUtility.UrlEncode(EmailVerificationCode);
To Verify the Token
var result = await userManager.ConfirmEmailAsync(user, code);
I always get INVALID TOKEN error.
The things I have tried
Checking both generated token and received token for verification by putting them into the database, they are exactly the same.
Tried using HttpUtility.UrlDecode(Code) to decode the token at receiving end
Tried to just use RAW token without HttpUtility.UrlEncode to verify it
I also went through the following solutions
Asp.net 2.0 Identity, ConfirmEmailAsync() getting Invalid Token
Invalid Token. while verifying email verification code using UserManager.ConfirmEmailAsync(user.Id, code)
AspNet.Identit 2.1.0 ConfirmEmailAsync always returns Invalid token
Asp.NET Identity 2 giving "Invalid Token" error
No matter what I do, it is always an Invalid Token where I can clearly see the token is 100% correct.
Any idea what am I doing wrong?
Edit - If this helps, my Startup.cs has the following Identity configurations
// For Identity
services.AddIdentity<ApplicationUser, IdentityRole>(o =>
{
// configure identity options
o.Password.RequireDigit = false;
o.Password.RequireLowercase = false;
o.Password.RequireUppercase = false;
o.Password.RequireNonAlphanumeric = false;
o.Password.RequiredLength = 6;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();

There are a few questions about this implementation before I answer.
Do you implement a custom DataProtectionProvider?
Do you override the default token life time is x number of hours?
What you can do over come this issue, create a customer DataProtectionProvider like below,
public class EmailConfirmationTokenProvider<TUser> : DataProtectorTokenProvider<TUser> where TUser : class
{
public EmailConfirmationTokenProvider(IDataProtectionProvider dataProtectionProvider,
IOptions<EmailConfirmationTokenProviderOptions> options,
ILogger<DataProtectorTokenProvider<TUser>> logger)
: base(dataProtectionProvider, options, logger)
{
}
}
public class EmailConfirmationTokenProviderOptions : DataProtectionTokenProviderOptions
{
}
Then inject in configure services with lifespan is 2 hours.
services.AddTokenProvider<EmailConfirmationTokenProvider<User>>("emailconfirmation");
services.Configure(opt =>
opt.TokenLifespan = TimeSpan.FromHours(3));
Thank you

Related

401 Unauthorized with Azure b2c on Xamarin.Forms

I have a Xamarin.Forms application that I'm using to connect to an App Service backend, and I'm attempting to authenticate using Auzre B2C JWT tokens.
Through various tutorials I have managed to get B2C setup using microsoft accounts, and I am able to create users, change passwords, and generate access tokens.
My next step was to add the [Authorize] attribute to my controller and attempt to pass that token to my app service and authorize users, but no matter what I try I get a 401 Unauthorized response from my service.
I'm adding the JWT token to the Authorization header of my HttpClient, and it's getting to the service.
I can paste my token into https://jwt.ms/, and it correctly tells me what's in my token.
I've implemented this code in an attempt to figure out what's wrong.
ConfigureServices in startup.cs looks like this:
public void ConfigureServices(IServiceCollection services) {
services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options => {
options.Audience = Configuration["Authentication:AzureAd:ClientId"];
options.Events = new JwtBearerEvents {
OnAuthenticationFailed = AuthenticationFailed
};
options.Authority = $"https://{tenant name}.b2clogin.com/{tenant id}/{Configuration["Authentication:AzureAd:Policy"]}";
options.Events = new JwtBearerEvents {
OnAuthenticationFailed = ctx =>
{
ctx.Response.StatusCode = StatusCodes.Status401Unauthorized;
message += "From OnAuthenticationFailed:\n";
message += FlattenException(ctx.Exception);
return Task.CompletedTask;
},
OnChallenge = ctx =>
{
message += "From OnChallenge:\n";
ctx.Response.StatusCode = StatusCodes.Status401Unauthorized;
ctx.Response.ContentType = "text/plain";
return ctx.Response.WriteAsync(message);
},
OnMessageReceived = ctx =>
{
message = "From OnMessageReceived:\n";
ctx.Request.Headers.TryGetValue("Authorization", out var BearerToken);
if (BearerToken.Count == 0)
BearerToken = "no Bearer token sent\n";
message += "Authorization Header sent: " + BearerToken + "\n";
return Task.CompletedTask;
},
OnTokenValidated = ctx =>
{
Debug.WriteLine("token: " + ctx.SecurityToken.ToString());
return Task.CompletedTask;
}
};
});
services.AddMvc();
}
Configure looks like this:
public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
IdentityModelEventSource.ShowPII = true;
} else {
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseMvc();
}
And I've also added this call to AuthenticationFailed, so I'll know if my authentication is working or not:
Task AuthenticationFailed(AuthenticationFailedContext arg) {
Console.WriteLine(arg.Exception.Message);
return Task.FromResult(0);
}
With my current setup I'm getting a 401 error from the server, and that's right after it hits the OnChallenge event wired up in Startup.cs. According to the link above, that's what gets called right before it returns a 401 to the user, so it seems like the service is receiving the proper token, and authenticating, but maybe I don't have the correct rights set up?
I'm not sure where to go from here, but any guidance would be appreciated.
Edit:
As mentioned in a comment below, I was able to curl my website using the access token generated after logging in through my app like this:
curl https://mywebsite.azurewebsites.net/api/Values -i --header "Authorization: Bearer [TOKEN]"
And that seems to work with no issue, so it seems like it's something with how I'm making a call to the controller through my app, not the authentication itself.
Edit 2 (solution):
So, as per Edit 1, I was correct in that it was just how I was adding the token to the authorization header. It wasn't my brightest moment, but I wasn't calling .Value on the claim that contained my Access Token. I was only calling .ToString() on the claim itself, so the "token" was actually the entire claim text "Access Token: ". I didn't think much of it at the time when I was debugging my service, because I didn't realize it shouldn't have that text there.
Once I corrected that issue the service started working as expected.
So, in the end, I guess it was all working as expected. I was, in fact, not sending the expected token, so I was ... unauthorized.
As requested the line of code that I had to change was this:
So, this won't be 100% applicable to most because I'm using a business library called CSLA, but the idea is the same regardless.
After my b2c call returns the token I store it in the ApplicationContext.User.Identity that's built into the CSLA library. That allows me to get the access token claim later. The important part to take away from this is that I'm storing the token some place that I can access it later when I want to add it to the authorization header.
Later, when I'm making the call with my httpclient I need to get that token, so originally, I was doing this:
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ((ClaimsIdentity)ApplicationContext.User.Identity).Claims.FirstOrDefault(c => c.Type == "AccessToken").ToString());
This isn't correct. This was sending the "token" as with value "Access Token: [token value]. Essentially, it was adding the words "Access Token" to the token I needed to authenticate, and that was failing, because the words "Access Token" are not actually supposed to be part of the token you use to authenticate.
After I changed my call to this:
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ((ClaimsIdentity)ApplicationContext.User.Identity).Claims.FirstOrDefault(c => c.Type == "AccessToken").Value);
It started getting only the token value, and when that was added to the authorization header, it worked just fine.
Edit 2 explains the answer to my problem.
I wasn't adding the token correctly to the authorization header, so the service wasn't able to authenticate the token, or rather, it saw the token as invalid.

Ignore expired tokens on certain routes

Using JWT tokens, I was working on authentication for my .NET Core API. I was wondering if I can tell my API to ignore the expiry of my token when the user requests a certain route, like "/refresh" for example, to refresh expired tokens. I understand it can be worked around by supplying the tokens through a POST request, but I want the ability to ignore the expiry of a token when I only want to identify the calling user.
My code is done as below. Currently it works with a POST request but I want the ability to supply expired tokens through the header.
[AllowAnonymous]
[HttpPost("refresh")]
public async Task<IActionResult> Refresh(RefreshRequest refreshRequest)
{
// Validate refresh token
if (!_tokenService.RefreshTokenValid(refreshRequest.Refresh_Token)) {
return BadRequest("The request token is not a valid token.");
}
var tokenHandler = new JwtSecurityTokenHandler();
// Validate actual token
if (!tokenHandler.CanReadToken(refreshRequest.Token)) {
return BadRequest("The JWT token is not a valid token.");
}
// Get the user from the JWT token
var userId = _tokenService.GetUserIdFromtoken(refreshRequest.Token);
var repoUser = await _repo.GetUserById(userId);
// Map the user, generate a token and send response
var newToken = _tokenService.GenerateRefreshedJwtToken(repoUser);
var tokenUser = _mapper.Map(repoUser, new AuthRefreshedTokenUser(newToken));
return Ok(tokenUser);
}
A simple way is to create another authentication schema and set not to validate token's lifetime :
.AddJwtBearer(options =>
{
...
options.TokenValidationParameters.ValidateLifetime = false;
....
});
And apply that authentication scheme using [Authorize(AuthenticationSchemes = "schemeName")] on specific controllers/actions.

IdentityServer4 as external provider, how to avoid logout prompt?

I am working with two identity providers, both implemented using IdentityServer4 in ASP.NET MVC Core 2.2. One of them is used as an external provider by the other. Let's call them "primary" and "external". The primary provider is referenced directly by the web application. The external provider is an optional login method provided by the primary provider.
The web application uses the oidc-client-js library to implement authentication. The logout operation in the web app calls UserManager.signoutRedirect. This works fine when the primary identity provider is used (no logout confirmation prompt is shown). However, when the external provider is used, the user is prompted to sign out from the external provider.
The sequence of requests when logging out are:
GET http://{primary}/connect/endsession?id_token_hint=...&post_logout_redirect_uri=http://{webapp}
GET http://{primary}/Account/Logout?logoutId=...
GET http://{external}/connect/endsession?state=...&post_logout_redirect_uri=http://{primary}/signout-callback-{idp}&x-client-SKU=ID_NETSTANDARD2_0&x-client-ver=5.3.0.0
GET http://{external}/Account/Logout?logoutId=...
This last request above shows the logout confirmation screen from the external provider.
The code for the /Account/Logout page on the primary provider is almost identical to the sample code in the documentation:
[HttpGet]
public async Task<IActionResult> Logout(string logoutId)
{
var vm = await BuildLogoutViewModelAsync(logoutId);
if (!vm.ShowLogoutPrompt)
{
// If the request is authenticated don't show the prompt,
// just log the user out by calling the POST handler directly.
return Logout(vm);
}
return View(vm);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Logout(LogoutInputModel model)
{
var vm = await BuildLoggedOutViewModelAsync(model.LogoutId);
if (User?.Identity.IsAuthenticated)
{
// delete local authentication cookie
await _signInManager.SignOutAsync();
// raise the logout event
await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName()));
}
// check if we need to trigger sign-out at an upstream identity provider
if (vm.TriggerExternalSignout)
{
// build a return URL so the upstream provider will redirect back
// to us after the user has logged out. this allows us to then
// complete our single sign-out processing.
var url = Url.Action("Logout", new { logoutId = vm.LogoutId });
// this triggers a redirect to the external provider for sign-out
var ap = new AuthenticationProperties { RedirectUri = url };
return SignOut(ap, vm.ExternalAuthenticationScheme);
}
return View("LoggedOut", vm);
}
The BuildLogoutViewModelAsync method calls GetLogoutContextAsync to check if the logout is authenticated, like so:
public async Task<LogoutViewModel> BuildLogoutViewModelAsync(string logoutId)
{
var vm = new LogoutViewModel
{
LogoutId = logoutId,
ShowLogoutPrompt = true
};
var context = await _interaction.GetLogoutContextAsync(logoutId);
if (context?.ShowSignoutPrompt == false)
{
// It's safe to automatically sign-out
vm.ShowLogoutPrompt = false;
}
return vm;
}
The BuildLoggedOutViewModelAsync method basically just checks for an external identity provider and sets the TriggerExternalSignout property if one was used.
I hate to make this a wall of code, but I'll include the ConfigureServices code used to configure the primary identity server because it is probably relevant:
var authenticationBuilder = services.AddAuthentication();
authenticationBuilder.AddOpenIdConnect(openIdConfig.Scheme, "external", ConfigureOptions);
void ConfigureOptions(OpenIdConnectOptions opts)
{
opts.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
opts.SignOutScheme = IdentityServerConstants.SignoutScheme;
opts.Authority = openIdConfig.ProviderAuthority;
opts.ClientId = openIdConfig.ClientId;
opts.ClientSecret = openIdConfig.ClientSecret;
opts.ResponseType = "code id_token";
opts.RequireHttpsMetadata = false;
opts.CallbackPath = $"/signin-{openIdConfig.Scheme}";
opts.SignedOutCallbackPath = $"/signout-callback-{openIdConfig.Scheme}";
opts.RemoteSignOutPath = $"/signout-{openIdConfig.Scheme}";
opts.Scope.Clear();
opts.Scope.Add("openid");
opts.Scope.Add("profile");
opts.Scope.Add("email");
opts.Scope.Add("phone");
opts.Scope.Add("roles");
opts.SaveTokens = true;
opts.GetClaimsFromUserInfoEndpoint = true;
var mapAdditionalClaims = new[] { JwtClaimTypes.Role, ... };
foreach (string additionalClaim in mapAdditionalClaims)
{
opts.ClaimActions.MapJsonKey(additionalClaim, additionalClaim);
}
opts.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = JwtClaimTypes.Name,
RoleClaimType = JwtClaimTypes.Role
};
}
My understanding is that the id_token_hint parameter passed to the first /connect/endsession endpoint will "authenticate" the logout request, which allows us to bypass the prompt based on the ShowSignoutPrompt property returned by GetLogoutContextAsync. However, this does not happen when the user is redirected to the external provider. The call to SignOut generates the second /connect/endsession URL with a state parameter, but no id_token_hint.
The logout code in the external provider is basically the same as the code shown above. When it calls GetLogoutContextAsync, that method does not see the request as authenticated, so the ShowSignoutPrompt property is true.
Any idea how to authenticate the request to the external provider?
The final block of code, you hate, but luckily added, contains one significant row:
opts.SaveTokens = true;
That allows you later to restore the id_token you got from the external provider.Then you can use it as a "second level hint".
if (vm.TriggerExternalSignout)
{
var url = Url.Action("Logout", new { logoutId = vm.LogoutId });
var props = new AuthenticationProperties {RedirectUri = url};
props.SetParameter("id_token_hint", HttpContext.GetTokenAsync("id_token"));
return SignOut(props, vm.ExternalAuthenticationScheme);
}
I had the exact same problem as OP and was able to correct it by explicitly stating that the ID Token is to be added on to the logout request as per this Github Issue
https://github.com/IdentityServer/IdentityServer4/issues/3510
options.SaveTokens = true; // required for single sign out
options.Events = new OpenIdConnectEvents // required for single sign out
{
OnRedirectToIdentityProviderForSignOut = async (context) => context.ProtocolMessage.IdTokenHint = await context.HttpContext.GetTokenAsync("id_token")
};
I have come up with a solution, though it seems to contradict what is done in the samples.
The problem seems to be caused by two lines of code that were both from the IdentityServer samples that we used as a basis for our IDP implementations. The problem code is in the "primary" IDP.
The first line is in ConfigureServices in Startup.cs:
var authenticationBuilder = services.AddAuthentication();
authenticationBuilder.AddOpenIdConnect(openIdConfig.Scheme, "external", ConfigureOptions);
void ConfigureOptions(OpenIdConnectOptions opts)
{
opts.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
opts.SignOutScheme = IdentityServerConstants.SignoutScheme; // this is a problem
The second place is in ExternalController.cs, in the Callback method. Here we diverged from the samples, using IdentityServerConstants.ExternalCookieAuthenticationScheme instead of IdentityConstants.ExternalScheme:
// Read external identity from the temporary cookie
var result = await this.HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
// ...
// delete temporary cookie used during external authentication
await HttpContext.SignOutAsync(
IdentityServerConstants.ExternalCookieAuthenticationScheme); // this is a problem
What happens at logout is: since the SignOutScheme is overridden, it is looking for a cookie that doesn't exist. Simply removing that doesn't fix it because the call to SignOutAsync has deleted the cookie that contains the information required for the identity code to authenticate the scheme. Since it can't authenticate the scheme, it does not include the id_token_hint in the request to the "external" IDP.
I've been able to fix this by removing the code that overrides SignOutScheme in Startup.cs, and moving the code that deletes the ExternalCookieAuthenticationScheme cookie to the Logout endpoint in AccountController.cs:
// check if we need to trigger sign-out at an upstream identity provider
if (vm.TriggerExternalSignout)
{
// delete temporary cookie used during external authentication
await this.HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
// build a return URL so the upstream provider will redirect back...
This way the "temporary" external cookie is left around until it is needed, but is deleted when the user logs out.
I'm not sure if this is the "correct" solution, but it does seem to work correctly in all cases that I've tested. I'm not really sure why we deviated from the sample in ExternalController.cs, either, but I suspect it is because we have two standalone IDP rather than a site with a single standalone IDP. Also, the sample appears to be using implicit flow while we are using hybrid flow.

ASP.NET Core Invalid Password Reset Tokens

I have two servers that are using the same ASP.NET Core Identity backend. I generate the password reset token with the following:
var token = await _userManager.GeneratePasswordResetTokenAsync(applicationUser);
I send this token via an email link. When the user clicks the link, they are taken to a separate site which should provide a UI to change the password. The following code handles the user's password submission of both the token and their new password:
var identityResult = await _userManager.ResetPasswordAsync(applicationUser, code, password);
On the second server, the identity result always returns false because "invalid token".
Looking through the source, I see that the token is generated using the IP address (so I understand why the token validation failed).
My question is how do I enable successful token creation/validation across different machines? In previous forms of ASP.NET, I would likely use a shared machine key to prevent these scenarios. ASP.NET Core doesn't seem to have a similar concept. From what I've read, it seems that this might be a scenario to use the DataProtection API. Unfortunately, I haven't seen any examples as how to apply this to generating the reset token.
Have you tried setting the application name to the same value in both applications?
services.AddDataProtection().SetApplicationName("same for both apps");
https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview
P.S - I'm struggling with exactly the same problem.
you should encode your token before you send it. You should do something like this:
var token = await _userManager.GeneratePasswordResetTokenAsync(applicationUser);
var encodedCode = HttpUtility.UrlEncode(token);
After encoding it, you must pass the encoded token rather than the generated token.
I faced the similar problem. Its not about 2 servers actually. Its about identity framework. You can derived from usermanager and you can override provider with central one. But I tried something different and it worked.
First of all ConfirmEmail method looks into database, if you have one database the shouldn't be a problem between tokens with more than one server.
In your usermanager you should create dataprovider at your constructor like this.
public ApplicationUserManager(IUserStore<ApplicationUser> store)
: base(store)
{
var dataProtectorProvider = Startup.DataProtectionProvider;
var dataProtector = dataProtectorProvider.Create("My Identity");
this.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser, string>(dataProtector);
//this.UserTokenProvider.TokenLifespan = TimeSpan.FromHours(24);
}
Also you should be see token in your database table for users. After this line of code.
string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
UserManager.EmailService = new EmailService();
await UserManager.SendEmailAsync(user.Id, "Confirm your account", "Please confirm your account by clicking here");
When you see token in your database, check email for same. then click you callback url and correct the encode of url.
For using dataProtectorProvider ;
public partial class Startup
{
public static IDataProtectionProvider DataProtectionProvider { get; set; }
public void ConfigureAuth(IAppBuilder app)
{
DataProtectionProvider = app.GetDataProtectionProvider();
}
}

secure webapi with a valid JWT based on claims

Here is how I've created an claim based authorization attribute. But I have some doubts about how this work.
Given the code from my startup class:
public void Configuration(IAppBuilder app)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = ConfigurationManager.AppSettings["Authentication:Authority"],
RequiredScopes = ConfigurationManager.AppSettings["Authentication:Scopes"].Split(' ').ToList(),
PreserveAccessToken = true
});
}
I was expecting that if I have this attribute to my controller and I send an invalid token(invalid signature) the request will be automatically rejected as unauthorized, but the code from the attribute is executed.
Shouldn't OWIN validate the token first?
How to make sure that the token is valid (valid stricture, signature, not expired, etc) and only after validate the claims?
The issue is within your linked question in your ClaimAuthorizationAttribute - it doesn't ever call base.IsAuthorized(), thus bypassing the built in protection mechanisms offered by AuthorizeAttribute.
Instead of just returning here after seeing whether or not the claim is present:
return token.Claims.Any(c => c.Type.Equals(this.Claim) && c.Value.Equals("True", StringComparison.OrdinalIgnoreCase));
You should instead carry on with making sure that the base class is satisfied, and thus the token itself is valid, too:
var claimValid = token.Claims.Any(c => c.Type.Equals(this.Claim) && c.Value.Equals("True", StringComparison.OrdinalIgnoreCase));
if (claimValid)
return base.IsAuthorized();
else
return false;

Categories