@HaoK need code to change the following for Account Confirmation and Password Recovery
security/authentication/accconfirm
@HaoK we have alot of customers asking how to change the confirmation email time out before 1 day. See this
If they are okay changing all data protection tokens at once services.Configure<DataProtectionTokenProviderOptions>(o => o.TokenLifespan = <whatever>)
If they want to target only a specific token provider like confirmation, they will have to register their own token provider by name, and target it, i.e.
services.AddIdentity(o => {
o.Tokens.ProviderMap.Add("Custom", typeof(CustomProvider);
o.Tokens.EmailConfirmationTokenProvider = "Custom";
});
@HaoK thanks. How about default log out time after inactivity?
Regarding, "If they are okay changing all data protection tokens at once," would you list (or give reference to the list of) all data protection tokens?
Regarding, "If they want to target only a specific token provider like confirmation," where does the code go that you provided? What other code is necessary; e.g., is it necessary to write the custom email token provider? If yes, would you please give a basic implementation?
You can look at
https://github.com/aspnet/AspNetCore/blob/master/src/Identity/Extensions.Core/src/TokenOptions.cs
for all the default token providers.
You can either copy the code from ~https://github.com/aspnet/Identity/blob/dev/src/Identity/DataProtectionTokenProvider.cs~
https://github.com/aspnet/AspNetCore/blob/master/src/Identity/Core/src/DataProtectionTokenProviderOptions.cs
and just hardcode the timespan you desire instead TokenLifespan, or derive from the class.
@Rick-Anderson changing the inactivty expiration would be
services.ConfigureApplicationCookie(o => {
o.ExpireTimeSpan = TimeSpan.FromMinutes(??);
o.SlidingExpiration = true;
});
I think custom TokenProviders can be created this way:
public class EmailConfirmationTokenProvider<TUser> : DataProtectorTokenProvider<TUser> where TUser : class
{
public EmailConfirmationTokenProvider(IDataProtectionProvider dataProtectionProvider,
IOptions<EmailConfirmationTokenProviderOptions> options)
: base(dataProtectionProvider, options)
{
}
}
public class EmailConfirmationTokenProviderOptions : DataProtectionTokenProviderOptions
{
}
And used this way:
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.SignIn.RequireConfirmedEmail = true;
options.Tokens.EmailConfirmationTokenProvider = "emailconf";
})
.AddEntityFrameworkStores<ApplicationUserDbContext>()
.AddDefaultTokenProviders()
.AddTokenProvider<EmailConfirmationTokenProvider<ApplicationUser>>("emailconf");
services.Configure<DataProtectionTokenProviderOptions>(options =>
options.TokenLifespan = TimeSpan.FromHours(2));
//lifespan for email confirmation token:
services.Configure<EmailConfirmationTokenProviderOptions>(options =>
options.TokenLifespan = TimeSpan.FromDays(1));
MOved to #10396
@stevejgordon I've created a project at https://github.com/Rick-Anderson/MyCustomProvider to show how to set the email TokenLifespan
to 30 seconds. I was trying to use Hao's code but need help on how to implement the TokenProviderDescriptor
here
Should I use Hao's approach or the @havotto approach?
Can you help me get this code working?
@Rick-Anderson , you want to have a lifetime of 30 seconds for all providers or only for the specific email confirmation (or password reset)?
@Ponant email only. See my sample code.
@stevejgordon I can't get @HaoK approach to work, but @havotto works well is more flexible. I'll go with the @havotto approach.
@Rick-Anderson , I was working on it but you have been faster. So I drop you some comments.
For Haok's method perhaps you could try
o.Tokens.ProviderMap.Add("Custom", new TokenProviderDescriptor(typeof(CustomTokenProvider<IdentityUser>)));//ApplicationUser
Also, I am not sure the havotto's methods will work if you add a third provider, such as for pwd reset where you might want another timespan than for registration. The reason for this is because the name of the options seems to remain to the default values "DataProtectorTokenProvider", so there might be some overriding. So I would rather do something like this:
public class CustomTokenProvider<TUser> : DataProtectorTokenProvider<TUser> where TUser : class
{
public CustomTokenProvider(IDataProtectionProvider dataProtectionProvider,
IOptions<EmailConfirmationTokenProviderOptions> options) : base(dataProtectionProvider, options)
{
options.Value.Name = "EmailDataProtectorTokenProvider";
options.Value.TokenLifespan = TimeSpan.FromMinutes(30);
}
}
public class EmailConfirmationTokenProviderOptions : DataProtectionTokenProviderOptions
{
}
But I cannot confirm my claims as I did not try it or dig further into the code.
@Ponant Much appreciated. I'll test this out.
You could of course inject the Name in service.Configure of course. I would be curious to know if the Name had an influence on it or not because it is called by DataProtectorTokenProvider to set its own name.
Cheers
@Ponant After you add
: base(dataProtectionProvider, options)
{
options.Value.Name = "EmailDataProtectorTokenProvider";
options.Value.TokenLifespan = TimeSpan.FromMinutes(30);
}
What do you use for ConfigureServices
services.Configure<EmailConfirmationTokenProviderOptions>(options =>
options.TokenLifespan = TimeSpan.FromDays(1));
For Haok's method perhaps you could try
o.Tokens.ProviderMap.Add("Custom", new TokenProviderDescriptor(typeof(CustomTokenProvider<IdentityUser>)));//ApplicationUser
I tried that, doesn't work.
In this case you wouldn't use the services.Config. Rather something like this:
services.AddDefaultIdentity<IdentityUser>(o =>
{
o.Tokens.ProviderMap.Add("Custom", new TokenProviderDescriptor(typeof(CustomTokenProvider<IdentityUser>)));
})
.AddDefaultUI(UIFramework.Bootstrap4)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddTransient<CustomTokenProvider<IdentityUser>>();
You do not call services.Config in this case.
And for the classes I would slightly change the code as it is more natural like so
public class CustomTokenProvider<TUser> : DataProtectorTokenProvider<TUser> where TUser : class
{
public CustomTokenProvider(IDataProtectionProvider dataProtectionProvider,
IOptions<EmailConfirmationTokenProviderOptions> options) : base(dataProtectionProvider, options)
{
}
}
public class EmailConfirmationTokenProviderOptions : DataProtectionTokenProviderOptions
{
public EmailConfirmationTokenProviderOptions()
{
Name = "EmailDataProtectorTokenProvider";
TokenLifespan = TimeSpan.FromMinutes(30);
}
I can try this out tomorrow as I need to fix it anyway so I could give you feedback. It is past midnight here. I tested the return values in a test razor page and they show up as expected but did not look into
GenerateEmailConfirmationTokenAsync
to see if the values are passed properly, but I expect it to work.
public class TestModel : PageModel
{
private readonly UserManager<IdentityUser> _userManager;
private readonly CustomTokenProvider<IdentityUser> _customTokenProvider;
private readonly DataProtectorTokenProvider<IdentityUser> _dataProtectorTokenProvider;
public TestModel(UserManager<IdentityUser> userManager, CustomTokenProvider<IdentityUser> customTokenProvider,
DataProtectorTokenProvider<IdentityUser> dataProtectorTokenProvider)
{
_userManager = userManager;
_customTokenProvider = customTokenProvider;
_dataProtectorTokenProvider = dataProtectorTokenProvider;
var store = _userManager.Options.Tokens.ProviderMap;
}
public IActionResult OnGet()
{
return Page();
}
}
I hope this helps. Does that work?
@Ponant Get some sleep, thanks for the help. I'll test this tonight.
OK I tried this and I forgot the EmailConfirmationTokenProvider
string in @HaoK's suggestion
services.AddDefaultIdentity<IdentityUser>(o =>
{
o.Tokens.ProviderMap.Add("Custom", new TokenProviderDescriptor(typeof(CustomTokenProvider<IdentityUser>)));
o.Tokens.EmailConfirmationTokenProvider = "Custom";
})
I can see the options work now, so this means the GenerateEmailConfirmationTokenAsync
should see "Custom". I checked this in the dirty way by having a class deriving UserManager such that I do not need to clone the whole Identity project
public class CustomUserManager : UserManager<IdentityUser>
{
public CustomUserManager(IUserStore<IdentityUser> store, IOptions<IdentityOptions> optionsAccessor, IPasswordHasher<IdentityUser> passwordHasher, IEnumerable<IUserValidator<IdentityUser>> userValidators, IEnumerable<IPasswordValidator<IdentityUser>> passwordValidators, ILookupNormalizer keyNormalizer, IdentityErrorDescriber errors, IServiceProvider services, ILogger<UserManager<IdentityUser>> logger) : base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger)
{
}
}
and register it as scoped and then in the test page.
public TestModel(UserManager<IdentityUser> userManager, CustomTokenProvider<IdentityUser> customTokenProvider,
DataProtectorTokenProvider<IdentityUser> dataProtectorTokenProvider, CustomUserManager customUserManager)
{
_userManager = userManager;
_customTokenProvider = customTokenProvider;
_dataProtectorTokenProvider = dataProtectorTokenProvider;
var store = _userManager.Options.Tokens.ProviderMap;
var emailConfirmationTokenProvider = customUserManager.Options.Tokens.EmailConfirmationTokenProvider;
}
the variable emailConfirmationTokenProvider
properly display in the debugger as the string "Custom". So I expect it to work now with a real email or email file dump. To follow up tomorrow. What I find confusing is the appearance of different names.
@Ponant I've got a new repo at https://github.com/Rick-Anderson/PWR2
Can you PR that, add a StartupPonant.cs class, and add all your code. I couldn't get it working.
Done @Rick-Anderson ,
Added two independent custom token providers with different lifetimes and it seems to work.
-Launch the website and look at the info in the index page
-Create a new user and follow the instructions
-Confirm your email or not depending on the token lifetime that is set in the custom files (F12 from startup)
-If Email is not confirmed you will not be able to reset a password, so ultimately after playing with lifetimes you should confirm your email and have EmailConfirmed set to true in the db.
-Now reset password with this same user and you get another flow with info. Play around and cross-check that changing the token lifetimes affects the result. You could at this point check that all token lifetimes are independently understood by the user manager. E.g. set the email confirmation token lifetime to 1 second while the one for pwd for 45 minutes. You will be able to reset pwd but hardly confirm email for a new user.
Cheers
Notice that if the user does not confirm email, either because the lifetime was too short or simply because he did not receive it then he will not be able to login without human intervention. That is an issue with the current API an template.
@Ponant
can you send me an email
@Rick-Anderson ok, done.
Most helpful comment
I think custom TokenProviders can be created this way:
And used this way: