--------------------------------------------------------------------------------
/ScaffoldedIdentity/Areas/Identity/Pages/Account/ConfirmEmail.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using Microsoft.AspNetCore.Authorization;
7 | using Microsoft.AspNetCore.Identity;
8 | using Microsoft.AspNetCore.Mvc;
9 | using Microsoft.AspNetCore.Mvc.RazorPages;
10 | using Microsoft.AspNetCore.WebUtilities;
11 |
12 | namespace ScaffoldedIdentity.Areas.Identity.Pages.Account
13 | {
14 | [AllowAnonymous]
15 | public class ConfirmEmailModel : PageModel
16 | {
17 | private readonly UserManager _userManager;
18 |
19 | public ConfirmEmailModel(UserManager userManager)
20 | {
21 | _userManager = userManager;
22 | }
23 |
24 | [TempData]
25 | public string StatusMessage { get; set; }
26 |
27 | public async Task OnGetAsync(string userId, string code)
28 | {
29 | if (userId == null || code == null)
30 | {
31 | return RedirectToPage("/Index");
32 | }
33 |
34 | var user = await _userManager.FindByIdAsync(userId);
35 | if (user == null)
36 | {
37 | return NotFound($"Unable to load user with ID '{userId}'.");
38 | }
39 |
40 | code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));
41 | var result = await _userManager.ConfirmEmailAsync(user, code);
42 | StatusMessage = result.Succeeded ? "Thank you for confirming your email." : "Error confirming your email.";
43 | return Page();
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/ScaffoldedIdentity/Areas/Identity/Pages/Account/ConfirmEmailChange.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model ConfirmEmailChangeModel
3 | @{
4 | ViewData["Title"] = "Confirm email change";
5 | }
6 |
7 |
@ViewData["Title"]
8 |
--------------------------------------------------------------------------------
/ScaffoldedIdentity/Areas/Identity/Pages/Account/ConfirmEmailChange.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using Microsoft.AspNetCore.Authorization;
7 | using Microsoft.AspNetCore.Identity;
8 | using Microsoft.AspNetCore.Mvc;
9 | using Microsoft.AspNetCore.Mvc.RazorPages;
10 | using Microsoft.AspNetCore.WebUtilities;
11 |
12 | namespace ScaffoldedIdentity.Areas.Identity.Pages.Account
13 | {
14 | [AllowAnonymous]
15 | public class ConfirmEmailChangeModel : PageModel
16 | {
17 | private readonly UserManager _userManager;
18 | private readonly SignInManager _signInManager;
19 |
20 | public ConfirmEmailChangeModel(UserManager userManager, SignInManager signInManager)
21 | {
22 | _userManager = userManager;
23 | _signInManager = signInManager;
24 | }
25 |
26 | [TempData]
27 | public string StatusMessage { get; set; }
28 |
29 | public async Task OnGetAsync(string userId, string email, string code)
30 | {
31 | if (userId == null || email == null || code == null)
32 | {
33 | return RedirectToPage("/Index");
34 | }
35 |
36 | var user = await _userManager.FindByIdAsync(userId);
37 | if (user == null)
38 | {
39 | return NotFound($"Unable to load user with ID '{userId}'.");
40 | }
41 |
42 | code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));
43 | var result = await _userManager.ChangeEmailAsync(user, email, code);
44 | if (!result.Succeeded)
45 | {
46 | StatusMessage = "Error changing email.";
47 | return Page();
48 | }
49 |
50 | // In our UI email and user name are one and the same, so when we update the email
51 | // we need to update the user name.
52 | var setUserNameResult = await _userManager.SetUserNameAsync(user, email);
53 | if (!setUserNameResult.Succeeded)
54 | {
55 | StatusMessage = "Error changing user name.";
56 | return Page();
57 | }
58 |
59 | await _signInManager.RefreshSignInAsync(user);
60 | StatusMessage = "Thank you for confirming your email change.";
61 | return Page();
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/ScaffoldedIdentity/Areas/Identity/Pages/Account/ExternalLogin.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model ExternalLoginModel
3 | @{
4 | ViewData["Title"] = "Register";
5 | }
6 |
7 |
@ViewData["Title"]
8 |
Associate your @Model.ProviderDisplayName account.
9 |
10 |
11 |
12 | You've successfully authenticated with @Model.ProviderDisplayName.
13 | Please enter an email address for this site below and click the Register button to finish
14 | logging in.
15 |
16 |
17 |
18 |
19 |
28 |
29 |
30 |
31 | @section Scripts {
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/ScaffoldedIdentity/Areas/Identity/Pages/Account/ExternalLogin.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.DataAnnotations;
4 | using System.Linq;
5 | using System.Security.Claims;
6 | using System.Text;
7 | using System.Text.Encodings.Web;
8 | using System.Threading.Tasks;
9 | using Microsoft.AspNetCore.Authorization;
10 | using Microsoft.AspNetCore.Identity;
11 | using Microsoft.AspNetCore.Identity.UI.Services;
12 | using Microsoft.AspNetCore.Mvc;
13 | using Microsoft.AspNetCore.Mvc.RazorPages;
14 | using Microsoft.AspNetCore.WebUtilities;
15 | using Microsoft.Extensions.Logging;
16 |
17 | namespace ScaffoldedIdentity.Areas.Identity.Pages.Account
18 | {
19 | [AllowAnonymous]
20 | public class ExternalLoginModel : PageModel
21 | {
22 | private readonly SignInManager _signInManager;
23 | private readonly UserManager _userManager;
24 | private readonly IEmailSender _emailSender;
25 | private readonly ILogger _logger;
26 |
27 | public ExternalLoginModel(
28 | SignInManager signInManager,
29 | UserManager userManager,
30 | ILogger logger,
31 | IEmailSender emailSender)
32 | {
33 | _signInManager = signInManager;
34 | _userManager = userManager;
35 | _logger = logger;
36 | _emailSender = emailSender;
37 | }
38 |
39 | [BindProperty]
40 | public InputModel Input { get; set; }
41 |
42 | public string ProviderDisplayName { get; set; }
43 |
44 | public string ReturnUrl { get; set; }
45 |
46 | [TempData]
47 | public string ErrorMessage { get; set; }
48 |
49 | public class InputModel
50 | {
51 | [Required]
52 | [EmailAddress]
53 | public string Email { get; set; }
54 | }
55 |
56 | public IActionResult OnGetAsync()
57 | {
58 | return RedirectToPage("./Login");
59 | }
60 |
61 | public IActionResult OnPost(string provider, string returnUrl = null)
62 | {
63 | // Request a redirect to the external login provider.
64 | var redirectUrl = Url.Page("./ExternalLogin", pageHandler: "Callback", values: new { returnUrl });
65 | var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
66 | return new ChallengeResult(provider, properties);
67 | }
68 |
69 | public async Task OnGetCallbackAsync(string returnUrl = null, string remoteError = null)
70 | {
71 | returnUrl = returnUrl ?? Url.Content("~/");
72 | if (remoteError != null)
73 | {
74 | ErrorMessage = $"Error from external provider: {remoteError}";
75 | return RedirectToPage("./Login", new {ReturnUrl = returnUrl });
76 | }
77 | var info = await _signInManager.GetExternalLoginInfoAsync();
78 | if (info == null)
79 | {
80 | ErrorMessage = "Error loading external login information.";
81 | return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
82 | }
83 |
84 | // Sign in the user with this external login provider if the user already has a login.
85 | var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor : true);
86 | if (result.Succeeded)
87 | {
88 | _logger.LogInformation("{Name} logged in with {LoginProvider} provider.", info.Principal.Identity.Name, info.LoginProvider);
89 | return LocalRedirect(returnUrl);
90 | }
91 | if (result.IsLockedOut)
92 | {
93 | return RedirectToPage("./Lockout");
94 | }
95 | else
96 | {
97 | // If the user does not have an account, then ask the user to create an account.
98 | ReturnUrl = returnUrl;
99 | ProviderDisplayName = info.ProviderDisplayName;
100 | if (info.Principal.HasClaim(c => c.Type == ClaimTypes.Email))
101 | {
102 | Input = new InputModel
103 | {
104 | Email = info.Principal.FindFirstValue(ClaimTypes.Email)
105 | };
106 | }
107 | return Page();
108 | }
109 | }
110 |
111 | public async Task OnPostConfirmationAsync(string returnUrl = null)
112 | {
113 | returnUrl = returnUrl ?? Url.Content("~/");
114 | // Get the information about the user from the external login provider
115 | var info = await _signInManager.GetExternalLoginInfoAsync();
116 | if (info == null)
117 | {
118 | ErrorMessage = "Error loading external login information during confirmation.";
119 | return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
120 | }
121 |
122 | if (ModelState.IsValid)
123 | {
124 | var user = new IdentityUser { UserName = Input.Email, Email = Input.Email };
125 |
126 | var result = await _userManager.CreateAsync(user);
127 | if (result.Succeeded)
128 | {
129 | result = await _userManager.AddLoginAsync(user, info);
130 | if (result.Succeeded)
131 | {
132 | _logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);
133 |
134 | var userId = await _userManager.GetUserIdAsync(user);
135 | var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
136 | code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
137 | var callbackUrl = Url.Page(
138 | "/Account/ConfirmEmail",
139 | pageHandler: null,
140 | values: new { area = "Identity", userId = userId, code = code },
141 | protocol: Request.Scheme);
142 |
143 | await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
144 | $"Please confirm your account by clicking here.");
145 |
146 | // If account confirmation is required, we need to show the link if we don't have a real email sender
147 | if (_userManager.Options.SignIn.RequireConfirmedAccount)
148 | {
149 | return RedirectToPage("./RegisterConfirmation", new { Email = Input.Email });
150 | }
151 |
152 | await _signInManager.SignInAsync(user, isPersistent: false, info.LoginProvider);
153 |
154 | return LocalRedirect(returnUrl);
155 | }
156 | }
157 | foreach (var error in result.Errors)
158 | {
159 | ModelState.AddModelError(string.Empty, error.Description);
160 | }
161 | }
162 |
163 | ProviderDisplayName = info.ProviderDisplayName;
164 | ReturnUrl = returnUrl;
165 | return Page();
166 | }
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/ScaffoldedIdentity/Areas/Identity/Pages/Account/ForgotPassword.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model ForgotPasswordModel
3 | @{
4 | ViewData["Title"] = "Forgot your password?";
5 | }
6 |
7 |
@ViewData["Title"]
8 |
Enter your email.
9 |
10 |
11 |
12 |
21 |
22 |
23 |
24 | @section Scripts {
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/ScaffoldedIdentity/Areas/Identity/Pages/Account/ForgotPassword.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.DataAnnotations;
4 | using System.Text.Encodings.Web;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using Microsoft.AspNetCore.Authorization;
8 | using Microsoft.AspNetCore.Identity;
9 | using Microsoft.AspNetCore.Identity.UI.Services;
10 | using Microsoft.AspNetCore.Mvc;
11 | using Microsoft.AspNetCore.Mvc.RazorPages;
12 | using Microsoft.AspNetCore.WebUtilities;
13 |
14 | namespace ScaffoldedIdentity.Areas.Identity.Pages.Account
15 | {
16 | [AllowAnonymous]
17 | public class ForgotPasswordModel : PageModel
18 | {
19 | private readonly UserManager _userManager;
20 | private readonly IEmailSender _emailSender;
21 |
22 | public ForgotPasswordModel(UserManager userManager, IEmailSender emailSender)
23 | {
24 | _userManager = userManager;
25 | _emailSender = emailSender;
26 | }
27 |
28 | [BindProperty]
29 | public InputModel Input { get; set; }
30 |
31 | public class InputModel
32 | {
33 | [Required]
34 | [EmailAddress]
35 | public string Email { get; set; }
36 | }
37 |
38 | public async Task OnPostAsync()
39 | {
40 | if (ModelState.IsValid)
41 | {
42 | var user = await _userManager.FindByEmailAsync(Input.Email);
43 | if (user == null || !(await _userManager.IsEmailConfirmedAsync(user)))
44 | {
45 | // Don't reveal that the user does not exist or is not confirmed
46 | return RedirectToPage("./ForgotPasswordConfirmation");
47 | }
48 |
49 | // For more information on how to enable account confirmation and password reset please
50 | // visit https://go.microsoft.com/fwlink/?LinkID=532713
51 | var code = await _userManager.GeneratePasswordResetTokenAsync(user);
52 | code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
53 | var callbackUrl = Url.Page(
54 | "/Account/ResetPassword",
55 | pageHandler: null,
56 | values: new { area = "Identity", code },
57 | protocol: Request.Scheme);
58 |
59 | await _emailSender.SendEmailAsync(
60 | Input.Email,
61 | "Reset Password",
62 | $"Please reset your password by clicking here.");
63 |
64 | return RedirectToPage("./ForgotPasswordConfirmation");
65 | }
66 |
67 | return Page();
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/ScaffoldedIdentity/Areas/Identity/Pages/Account/ForgotPasswordConfirmation.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model ForgotPasswordConfirmation
3 | @{
4 | ViewData["Title"] = "Forgot password confirmation";
5 | }
6 |
7 |
@ViewData["Title"]
8 |
9 | Please check your email to reset your password.
10 |
60 | There are no external authentication services configured. See this article
61 | for details on setting up this ASP.NET application to support logging in via external services.
62 |
38 |
39 | @section Scripts {
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/ScaffoldedIdentity/Areas/Identity/Pages/Account/LoginWith2fa.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.DataAnnotations;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 | using Microsoft.AspNetCore.Authorization;
7 | using Microsoft.AspNetCore.Identity;
8 | using Microsoft.AspNetCore.Mvc;
9 | using Microsoft.AspNetCore.Mvc.RazorPages;
10 | using Microsoft.Extensions.Logging;
11 |
12 | namespace ScaffoldedIdentity.Areas.Identity.Pages.Account
13 | {
14 | [AllowAnonymous]
15 | public class LoginWith2faModel : PageModel
16 | {
17 | private readonly SignInManager _signInManager;
18 | private readonly ILogger _logger;
19 |
20 | public LoginWith2faModel(SignInManager signInManager, ILogger logger)
21 | {
22 | _signInManager = signInManager;
23 | _logger = logger;
24 | }
25 |
26 | [BindProperty]
27 | public InputModel Input { get; set; }
28 |
29 | public bool RememberMe { get; set; }
30 |
31 | public string ReturnUrl { get; set; }
32 |
33 | public class InputModel
34 | {
35 | [Required]
36 | [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
37 | [DataType(DataType.Text)]
38 | [Display(Name = "Authenticator code")]
39 | public string TwoFactorCode { get; set; }
40 |
41 | [Display(Name = "Remember this machine")]
42 | public bool RememberMachine { get; set; }
43 | }
44 |
45 | public async Task OnGetAsync(bool rememberMe, string returnUrl = null)
46 | {
47 | // Ensure the user has gone through the username & password screen first
48 | var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
49 |
50 | if (user == null)
51 | {
52 | throw new InvalidOperationException($"Unable to load two-factor authentication user.");
53 | }
54 |
55 | ReturnUrl = returnUrl;
56 | RememberMe = rememberMe;
57 |
58 | return Page();
59 | }
60 |
61 | public async Task OnPostAsync(bool rememberMe, string returnUrl = null)
62 | {
63 | if (!ModelState.IsValid)
64 | {
65 | return Page();
66 | }
67 |
68 | returnUrl = returnUrl ?? Url.Content("~/");
69 |
70 | var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
71 | if (user == null)
72 | {
73 | throw new InvalidOperationException($"Unable to load two-factor authentication user.");
74 | }
75 |
76 | var authenticatorCode = Input.TwoFactorCode.Replace(" ", string.Empty).Replace("-", string.Empty);
77 |
78 | var result = await _signInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, rememberMe, Input.RememberMachine);
79 |
80 | if (result.Succeeded)
81 | {
82 | _logger.LogInformation("User with ID '{UserId}' logged in with 2fa.", user.Id);
83 | return LocalRedirect(returnUrl);
84 | }
85 | else if (result.IsLockedOut)
86 | {
87 | _logger.LogWarning("User with ID '{UserId}' account locked out.", user.Id);
88 | return RedirectToPage("./Lockout");
89 | }
90 | else
91 | {
92 | _logger.LogWarning("Invalid authenticator code entered for user with ID '{UserId}'.", user.Id);
93 | ModelState.AddModelError(string.Empty, "Invalid authenticator code.");
94 | return Page();
95 | }
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/ScaffoldedIdentity/Areas/Identity/Pages/Account/LoginWithRecoveryCode.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model LoginWithRecoveryCodeModel
3 | @{
4 | ViewData["Title"] = "Recovery code verification";
5 | }
6 |
7 |
@ViewData["Title"]
8 |
9 |
10 | You have requested to log in with a recovery code. This login will not be remembered until you provide
11 | an authenticator app code at log in or disable 2FA and log in again.
12 |
13 |
14 |
15 |
24 |
25 |
26 |
27 | @section Scripts {
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/ScaffoldedIdentity/Areas/Identity/Pages/Account/LoginWithRecoveryCode.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.DataAnnotations;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 | using Microsoft.AspNetCore.Authorization;
7 | using Microsoft.AspNetCore.Identity;
8 | using Microsoft.AspNetCore.Mvc;
9 | using Microsoft.AspNetCore.Mvc.RazorPages;
10 | using Microsoft.Extensions.Logging;
11 |
12 | namespace ScaffoldedIdentity.Areas.Identity.Pages.Account
13 | {
14 | [AllowAnonymous]
15 | public class LoginWithRecoveryCodeModel : PageModel
16 | {
17 | private readonly SignInManager _signInManager;
18 | private readonly ILogger _logger;
19 |
20 | public LoginWithRecoveryCodeModel(SignInManager signInManager, ILogger logger)
21 | {
22 | _signInManager = signInManager;
23 | _logger = logger;
24 | }
25 |
26 | [BindProperty]
27 | public InputModel Input { get; set; }
28 |
29 | public string ReturnUrl { get; set; }
30 |
31 | public class InputModel
32 | {
33 | [BindProperty]
34 | [Required]
35 | [DataType(DataType.Text)]
36 | [Display(Name = "Recovery Code")]
37 | public string RecoveryCode { get; set; }
38 | }
39 |
40 | public async Task OnGetAsync(string returnUrl = null)
41 | {
42 | // Ensure the user has gone through the username & password screen first
43 | var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
44 | if (user == null)
45 | {
46 | throw new InvalidOperationException($"Unable to load two-factor authentication user.");
47 | }
48 |
49 | ReturnUrl = returnUrl;
50 |
51 | return Page();
52 | }
53 |
54 | public async Task OnPostAsync(string returnUrl = null)
55 | {
56 | if (!ModelState.IsValid)
57 | {
58 | return Page();
59 | }
60 |
61 | var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
62 | if (user == null)
63 | {
64 | throw new InvalidOperationException($"Unable to load two-factor authentication user.");
65 | }
66 |
67 | var recoveryCode = Input.RecoveryCode.Replace(" ", string.Empty);
68 |
69 | var result = await _signInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode);
70 |
71 | if (result.Succeeded)
72 | {
73 | _logger.LogInformation("User with ID '{UserId}' logged in with a recovery code.", user.Id);
74 | return LocalRedirect(returnUrl ?? Url.Content("~/"));
75 | }
76 | if (result.IsLockedOut)
77 | {
78 | _logger.LogWarning("User with ID '{UserId}' account locked out.", user.Id);
79 | return RedirectToPage("./Lockout");
80 | }
81 | else
82 | {
83 | _logger.LogWarning("Invalid recovery code entered for user with ID '{UserId}' ", user.Id);
84 | ModelState.AddModelError(string.Empty, "Invalid recovery code entered.");
85 | return Page();
86 | }
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/ScaffoldedIdentity/Areas/Identity/Pages/Account/Logout.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model LogoutModel
3 | @{
4 | ViewData["Title"] = "Log out";
5 | }
6 |
7 |
8 |
33 |
34 | @section Scripts {
35 |
36 | }
--------------------------------------------------------------------------------
/ScaffoldedIdentity/Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.DataAnnotations;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 | using Microsoft.AspNetCore.Identity;
7 | using Microsoft.AspNetCore.Mvc;
8 | using Microsoft.AspNetCore.Mvc.RazorPages;
9 | using Microsoft.Extensions.Logging;
10 | namespace ScaffoldedIdentity.Areas.Identity.Pages.Account.Manage
11 | {
12 | public class ChangePasswordModel : PageModel
13 | {
14 | private readonly UserManager _userManager;
15 | private readonly SignInManager _signInManager;
16 | private readonly ILogger _logger;
17 |
18 | public ChangePasswordModel(
19 | UserManager userManager,
20 | SignInManager signInManager,
21 | ILogger logger)
22 | {
23 | _userManager = userManager;
24 | _signInManager = signInManager;
25 | _logger = logger;
26 | }
27 |
28 | [BindProperty]
29 | public InputModel Input { get; set; }
30 |
31 | [TempData]
32 | public string StatusMessage { get; set; }
33 |
34 | public class InputModel
35 | {
36 | [Required]
37 | [DataType(DataType.Password)]
38 | [Display(Name = "Current password")]
39 | public string OldPassword { get; set; }
40 |
41 | [Required]
42 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
43 | [DataType(DataType.Password)]
44 | [Display(Name = "New password")]
45 | public string NewPassword { get; set; }
46 |
47 | [DataType(DataType.Password)]
48 | [Display(Name = "Confirm new password")]
49 | [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
50 | public string ConfirmPassword { get; set; }
51 | }
52 |
53 | public async Task OnGetAsync()
54 | {
55 | var user = await _userManager.GetUserAsync(User);
56 | if (user == null)
57 | {
58 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
59 | }
60 |
61 | var hasPassword = await _userManager.HasPasswordAsync(user);
62 | if (!hasPassword)
63 | {
64 | return RedirectToPage("./SetPassword");
65 | }
66 |
67 | return Page();
68 | }
69 |
70 | public async Task OnPostAsync()
71 | {
72 | if (!ModelState.IsValid)
73 | {
74 | return Page();
75 | }
76 |
77 | var user = await _userManager.GetUserAsync(User);
78 | if (user == null)
79 | {
80 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
81 | }
82 |
83 | var changePasswordResult = await _userManager.ChangePasswordAsync(user, Input.OldPassword, Input.NewPassword);
84 | if (!changePasswordResult.Succeeded)
85 | {
86 | foreach (var error in changePasswordResult.Errors)
87 | {
88 | ModelState.AddModelError(string.Empty, error.Description);
89 | }
90 | return Page();
91 | }
92 |
93 | await _signInManager.RefreshSignInAsync(user);
94 | _logger.LogInformation("User changed their password successfully.");
95 | StatusMessage = "Your password has been changed.";
96 |
97 | return RedirectToPage();
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/ScaffoldedIdentity/Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model DeletePersonalDataModel
3 | @{
4 | ViewData["Title"] = "Delete Personal Data";
5 | ViewData["ActivePage"] = ManageNavPages.PersonalData;
6 | }
7 |
8 |
@ViewData["Title"]
9 |
10 |
11 |
12 | Deleting this data will permanently remove your account, and this cannot be recovered.
13 |
14 |
15 |
16 |
17 |
29 |
30 |
31 | @section Scripts {
32 |
33 | }
--------------------------------------------------------------------------------
/ScaffoldedIdentity/Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel.DataAnnotations;
3 | using System.Threading.Tasks;
4 | using Microsoft.AspNetCore.Identity;
5 | using Microsoft.AspNetCore.Mvc;
6 | using Microsoft.AspNetCore.Mvc.RazorPages;
7 | using Microsoft.Extensions.Logging;
8 |
9 | namespace ScaffoldedIdentity.Areas.Identity.Pages.Account.Manage
10 | {
11 | public class DeletePersonalDataModel : PageModel
12 | {
13 | private readonly UserManager _userManager;
14 | private readonly SignInManager _signInManager;
15 | private readonly ILogger _logger;
16 |
17 | public DeletePersonalDataModel(
18 | UserManager userManager,
19 | SignInManager signInManager,
20 | ILogger logger)
21 | {
22 | _userManager = userManager;
23 | _signInManager = signInManager;
24 | _logger = logger;
25 | }
26 |
27 | [BindProperty]
28 | public InputModel Input { get; set; }
29 |
30 | public class InputModel
31 | {
32 | [Required]
33 | [DataType(DataType.Password)]
34 | public string Password { get; set; }
35 | }
36 |
37 | public bool RequirePassword { get; set; }
38 |
39 | public async Task OnGet()
40 | {
41 | var user = await _userManager.GetUserAsync(User);
42 | if (user == null)
43 | {
44 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
45 | }
46 |
47 | RequirePassword = await _userManager.HasPasswordAsync(user);
48 | return Page();
49 | }
50 |
51 | public async Task OnPostAsync()
52 | {
53 | var user = await _userManager.GetUserAsync(User);
54 | if (user == null)
55 | {
56 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
57 | }
58 |
59 | RequirePassword = await _userManager.HasPasswordAsync(user);
60 | if (RequirePassword)
61 | {
62 | if (!await _userManager.CheckPasswordAsync(user, Input.Password))
63 | {
64 | ModelState.AddModelError(string.Empty, "Incorrect password.");
65 | return Page();
66 | }
67 | }
68 |
69 | var result = await _userManager.DeleteAsync(user);
70 | var userId = await _userManager.GetUserIdAsync(user);
71 | if (!result.Succeeded)
72 | {
73 | throw new InvalidOperationException($"Unexpected error occurred deleting user with ID '{userId}'.");
74 | }
75 |
76 | await _signInManager.SignOutAsync();
77 |
78 | _logger.LogInformation("User with ID '{UserId}' deleted themselves.", userId);
79 |
80 | return Redirect("~/");
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/ScaffoldedIdentity/Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model Disable2faModel
3 | @{
4 | ViewData["Title"] = "Disable two-factor authentication (2FA)";
5 | ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
6 | }
7 |
8 |
9 |
@ViewData["Title"]
10 |
11 |
12 |
13 | This action only disables 2FA.
14 |
15 |
16 | Disabling 2FA does not change the keys used in authenticator apps. If you wish to change the key
17 | used in an authenticator app you should reset your authenticator keys.
18 |
19 |
20 |
21 |
22 |
25 |
26 |
--------------------------------------------------------------------------------
/ScaffoldedIdentity/Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Identity;
6 | using Microsoft.AspNetCore.Mvc;
7 | using Microsoft.AspNetCore.Mvc.RazorPages;
8 | using Microsoft.Extensions.Logging;
9 |
10 | namespace ScaffoldedIdentity.Areas.Identity.Pages.Account.Manage
11 | {
12 | public class Disable2faModel : PageModel
13 | {
14 | private readonly UserManager _userManager;
15 | private readonly ILogger _logger;
16 |
17 | public Disable2faModel(
18 | UserManager userManager,
19 | ILogger logger)
20 | {
21 | _userManager = userManager;
22 | _logger = logger;
23 | }
24 |
25 | [TempData]
26 | public string StatusMessage { get; set; }
27 |
28 | public async Task OnGet()
29 | {
30 | var user = await _userManager.GetUserAsync(User);
31 | if (user == null)
32 | {
33 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
34 | }
35 |
36 | if (!await _userManager.GetTwoFactorEnabledAsync(user))
37 | {
38 | throw new InvalidOperationException($"Cannot disable 2FA for user with ID '{_userManager.GetUserId(User)}' as it's not currently enabled.");
39 | }
40 |
41 | return Page();
42 | }
43 |
44 | public async Task OnPostAsync()
45 | {
46 | var user = await _userManager.GetUserAsync(User);
47 | if (user == null)
48 | {
49 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
50 | }
51 |
52 | var disable2faResult = await _userManager.SetTwoFactorEnabledAsync(user, false);
53 | if (!disable2faResult.Succeeded)
54 | {
55 | throw new InvalidOperationException($"Unexpected error occurred disabling 2FA for user with ID '{_userManager.GetUserId(User)}'.");
56 | }
57 |
58 | _logger.LogInformation("User with ID '{UserId}' has disabled 2fa.", _userManager.GetUserId(User));
59 | StatusMessage = "2fa has been disabled. You can reenable 2fa when you setup an authenticator app";
60 | return RedirectToPage("./TwoFactorAuthentication");
61 | }
62 | }
63 | }
--------------------------------------------------------------------------------
/ScaffoldedIdentity/Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model DownloadPersonalDataModel
3 | @{
4 | ViewData["Title"] = "Download Your Data";
5 | ViewData["ActivePage"] = ManageNavPages.PersonalData;
6 | }
7 |
8 |
@ViewData["Title"]
9 |
10 | @section Scripts {
11 |
12 | }
--------------------------------------------------------------------------------
/ScaffoldedIdentity/Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Text.Json;
6 | using System.Threading.Tasks;
7 | using Microsoft.AspNetCore.Identity;
8 | using Microsoft.AspNetCore.Mvc;
9 | using Microsoft.AspNetCore.Mvc.RazorPages;
10 | using Microsoft.Extensions.Logging;
11 |
12 | namespace ScaffoldedIdentity.Areas.Identity.Pages.Account.Manage
13 | {
14 | public class DownloadPersonalDataModel : PageModel
15 | {
16 | private readonly UserManager _userManager;
17 | private readonly ILogger _logger;
18 |
19 | public DownloadPersonalDataModel(
20 | UserManager userManager,
21 | ILogger logger)
22 | {
23 | _userManager = userManager;
24 | _logger = logger;
25 | }
26 |
27 | public async Task OnPostAsync()
28 | {
29 | var user = await _userManager.GetUserAsync(User);
30 | if (user == null)
31 | {
32 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
33 | }
34 |
35 | _logger.LogInformation("User with ID '{UserId}' asked for their personal data.", _userManager.GetUserId(User));
36 |
37 | // Only include personal data for download
38 | var personalData = new Dictionary();
39 | var personalDataProps = typeof(IdentityUser).GetProperties().Where(
40 | prop => Attribute.IsDefined(prop, typeof(PersonalDataAttribute)));
41 | foreach (var p in personalDataProps)
42 | {
43 | personalData.Add(p.Name, p.GetValue(user)?.ToString() ?? "null");
44 | }
45 |
46 | var logins = await _userManager.GetLoginsAsync(user);
47 | foreach (var l in logins)
48 | {
49 | personalData.Add($"{l.LoginProvider} external login provider key", l.ProviderKey);
50 | }
51 |
52 | Response.Headers.Add("Content-Disposition", "attachment; filename=PersonalData.json");
53 | return new FileContentResult(JsonSerializer.SerializeToUtf8Bytes(personalData), "application/json");
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/ScaffoldedIdentity/Areas/Identity/Pages/Account/Manage/Email.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model EmailModel
3 | @{
4 | ViewData["Title"] = "Manage Email";
5 | ViewData["ActivePage"] = ManageNavPages.Email;
6 | }
7 |
8 |
@ViewData["Title"]
9 |
10 |
11 |
12 |
38 |
39 |
40 |
41 | @section Scripts {
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/ScaffoldedIdentity/Areas/Identity/Pages/Account/Manage/Email.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.DataAnnotations;
4 | using System.Text;
5 | using System.Text.Encodings.Web;
6 | using System.Linq;
7 | using System.Threading.Tasks;
8 | using Microsoft.AspNetCore.Identity;
9 | using Microsoft.AspNetCore.Identity.UI.Services;
10 | using Microsoft.AspNetCore.Mvc;
11 | using Microsoft.AspNetCore.Mvc.RazorPages;
12 | using Microsoft.AspNetCore.WebUtilities;
13 |
14 | namespace ScaffoldedIdentity.Areas.Identity.Pages.Account.Manage
15 | {
16 | public partial class EmailModel : PageModel
17 | {
18 | private readonly UserManager _userManager;
19 | private readonly SignInManager _signInManager;
20 | private readonly IEmailSender _emailSender;
21 |
22 | public EmailModel(
23 | UserManager userManager,
24 | SignInManager signInManager,
25 | IEmailSender emailSender)
26 | {
27 | _userManager = userManager;
28 | _signInManager = signInManager;
29 | _emailSender = emailSender;
30 | }
31 |
32 | public string Username { get; set; }
33 |
34 | public string Email { get; set; }
35 |
36 | public bool IsEmailConfirmed { get; set; }
37 |
38 | [TempData]
39 | public string StatusMessage { get; set; }
40 |
41 | [BindProperty]
42 | public InputModel Input { get; set; }
43 |
44 | public class InputModel
45 | {
46 | [Required]
47 | [EmailAddress]
48 | [Display(Name = "New email")]
49 | public string NewEmail { get; set; }
50 | }
51 |
52 | private async Task LoadAsync(IdentityUser user)
53 | {
54 | var email = await _userManager.GetEmailAsync(user);
55 | Email = email;
56 |
57 | Input = new InputModel
58 | {
59 | NewEmail = email,
60 | };
61 |
62 | IsEmailConfirmed = await _userManager.IsEmailConfirmedAsync(user);
63 | }
64 |
65 | public async Task OnGetAsync()
66 | {
67 | var user = await _userManager.GetUserAsync(User);
68 | if (user == null)
69 | {
70 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
71 | }
72 |
73 | await LoadAsync(user);
74 | return Page();
75 | }
76 |
77 | public async Task OnPostChangeEmailAsync()
78 | {
79 | var user = await _userManager.GetUserAsync(User);
80 | if (user == null)
81 | {
82 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
83 | }
84 |
85 | if (!ModelState.IsValid)
86 | {
87 | await LoadAsync(user);
88 | return Page();
89 | }
90 |
91 | var email = await _userManager.GetEmailAsync(user);
92 | if (Input.NewEmail != email)
93 | {
94 | var userId = await _userManager.GetUserIdAsync(user);
95 | var code = await _userManager.GenerateChangeEmailTokenAsync(user, Input.NewEmail);
96 | code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
97 | var callbackUrl = Url.Page(
98 | "/Account/ConfirmEmailChange",
99 | pageHandler: null,
100 | values: new { userId = userId, email = Input.NewEmail, code = code },
101 | protocol: Request.Scheme);
102 | await _emailSender.SendEmailAsync(
103 | Input.NewEmail,
104 | "Confirm your email",
105 | $"Please confirm your account by clicking here.");
106 |
107 | StatusMessage = "Confirmation link to change email sent. Please check your email.";
108 | return RedirectToPage();
109 | }
110 |
111 | StatusMessage = "Your email is unchanged.";
112 | return RedirectToPage();
113 | }
114 |
115 | public async Task OnPostSendVerificationEmailAsync()
116 | {
117 | var user = await _userManager.GetUserAsync(User);
118 | if (user == null)
119 | {
120 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
121 | }
122 |
123 | if (!ModelState.IsValid)
124 | {
125 | await LoadAsync(user);
126 | return Page();
127 | }
128 |
129 | var userId = await _userManager.GetUserIdAsync(user);
130 | var email = await _userManager.GetEmailAsync(user);
131 | var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
132 | code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
133 | var callbackUrl = Url.Page(
134 | "/Account/ConfirmEmail",
135 | pageHandler: null,
136 | values: new { area = "Identity", userId = userId, code = code },
137 | protocol: Request.Scheme);
138 | await _emailSender.SendEmailAsync(
139 | email,
140 | "Confirm your email",
141 | $"Please confirm your account by clicking here.");
142 |
143 | StatusMessage = "Verification email sent. Please check your email.";
144 | return RedirectToPage();
145 | }
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/ScaffoldedIdentity/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model EnableAuthenticatorModel
3 | @{
4 | ViewData["Title"] = "Configure authenticator app";
5 | ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
6 | }
7 |
8 |
9 |
@ViewData["Title"]
10 |
11 |
To use an authenticator app go through the following steps:
12 |
13 |
14 |
15 | Download a two-factor authenticator app like Microsoft Authenticator for
16 | Android and
17 | iOS or
18 | Google Authenticator for
19 | Android and
20 | iOS.
21 |
22 |
23 |
24 |
Scan the QR Code or enter this key @Model.SharedKey into your two factor authenticator app. Spaces and casing do not matter.
31 | Once you have scanned the QR code or input the key above, your two factor authentication app will provide you
32 | with a unique code. Enter the code in the confirmation box below.
33 |
34 |
35 |
36 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | @section Scripts {
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/ScaffoldedIdentity/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel;
3 | using System.ComponentModel.DataAnnotations;
4 | using System.Collections.Generic;
5 | using System.Text;
6 | using System.Text.Encodings.Web;
7 | using System.Linq;
8 | using System.Threading.Tasks;
9 | using Microsoft.AspNetCore.Identity;
10 | using Microsoft.AspNetCore.Mvc;
11 | using Microsoft.AspNetCore.Mvc.RazorPages;
12 | using Microsoft.Extensions.Logging;
13 |
14 | namespace ScaffoldedIdentity.Areas.Identity.Pages.Account.Manage
15 | {
16 | public class EnableAuthenticatorModel : PageModel
17 | {
18 | private readonly UserManager _userManager;
19 | private readonly ILogger _logger;
20 | private readonly UrlEncoder _urlEncoder;
21 |
22 | private const string AuthenticatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6";
23 |
24 | public EnableAuthenticatorModel(
25 | UserManager userManager,
26 | ILogger logger,
27 | UrlEncoder urlEncoder)
28 | {
29 | _userManager = userManager;
30 | _logger = logger;
31 | _urlEncoder = urlEncoder;
32 | }
33 |
34 | public string SharedKey { get; set; }
35 |
36 | public string AuthenticatorUri { get; set; }
37 |
38 | [TempData]
39 | public string[] RecoveryCodes { get; set; }
40 |
41 | [TempData]
42 | public string StatusMessage { get; set; }
43 |
44 | [BindProperty]
45 | public InputModel Input { get; set; }
46 |
47 | public class InputModel
48 | {
49 | [Required]
50 | [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
51 | [DataType(DataType.Text)]
52 | [Display(Name = "Verification Code")]
53 | public string Code { get; set; }
54 | }
55 |
56 | public async Task OnGetAsync()
57 | {
58 | var user = await _userManager.GetUserAsync(User);
59 | if (user == null)
60 | {
61 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
62 | }
63 |
64 | await LoadSharedKeyAndQrCodeUriAsync(user);
65 |
66 | return Page();
67 | }
68 |
69 | public async Task OnPostAsync()
70 | {
71 | var user = await _userManager.GetUserAsync(User);
72 | if (user == null)
73 | {
74 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
75 | }
76 |
77 | if (!ModelState.IsValid)
78 | {
79 | await LoadSharedKeyAndQrCodeUriAsync(user);
80 | return Page();
81 | }
82 |
83 | // Strip spaces and hypens
84 | var verificationCode = Input.Code.Replace(" ", string.Empty).Replace("-", string.Empty);
85 |
86 | var is2faTokenValid = await _userManager.VerifyTwoFactorTokenAsync(
87 | user, _userManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode);
88 |
89 | if (!is2faTokenValid)
90 | {
91 | ModelState.AddModelError("Input.Code", "Verification code is invalid.");
92 | await LoadSharedKeyAndQrCodeUriAsync(user);
93 | return Page();
94 | }
95 |
96 | await _userManager.SetTwoFactorEnabledAsync(user, true);
97 | var userId = await _userManager.GetUserIdAsync(user);
98 | _logger.LogInformation("User with ID '{UserId}' has enabled 2FA with an authenticator app.", userId);
99 |
100 | StatusMessage = "Your authenticator app has been verified.";
101 |
102 | if (await _userManager.CountRecoveryCodesAsync(user) == 0)
103 | {
104 | var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
105 | RecoveryCodes = recoveryCodes.ToArray();
106 | return RedirectToPage("./ShowRecoveryCodes");
107 | }
108 | else
109 | {
110 | return RedirectToPage("./TwoFactorAuthentication");
111 | }
112 | }
113 |
114 | private async Task LoadSharedKeyAndQrCodeUriAsync(IdentityUser user)
115 | {
116 | // Load the authenticator key & QR code URI to display on the form
117 | var unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);
118 | if (string.IsNullOrEmpty(unformattedKey))
119 | {
120 | await _userManager.ResetAuthenticatorKeyAsync(user);
121 | unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);
122 | }
123 |
124 | SharedKey = FormatKey(unformattedKey);
125 |
126 | var email = await _userManager.GetEmailAsync(user);
127 | AuthenticatorUri = GenerateQrCodeUri(email, unformattedKey);
128 | }
129 |
130 | private string FormatKey(string unformattedKey)
131 | {
132 | var result = new StringBuilder();
133 | int currentPosition = 0;
134 | while (currentPosition + 4 < unformattedKey.Length)
135 | {
136 | result.Append(unformattedKey.Substring(currentPosition, 4)).Append(" ");
137 | currentPosition += 4;
138 | }
139 | if (currentPosition < unformattedKey.Length)
140 | {
141 | result.Append(unformattedKey.Substring(currentPosition));
142 | }
143 |
144 | return result.ToString().ToLowerInvariant();
145 | }
146 |
147 | private string GenerateQrCodeUri(string email, string unformattedKey)
148 | {
149 | return string.Format(
150 | AuthenticatorUriFormat,
151 | _urlEncoder.Encode("ScaffoldedIdentity"),
152 | _urlEncoder.Encode(email),
153 | unformattedKey);
154 | }
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/ScaffoldedIdentity/Areas/Identity/Pages/Account/Manage/ExternalLogins.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model ExternalLoginsModel
3 | @{
4 | ViewData["Title"] = "Manage your external logins";
5 | ViewData["ActivePage"] = ManageNavPages.ExternalLogins;
6 | }
7 |
8 |
9 | @if (Model.CurrentLogins?.Count > 0)
10 | {
11 |
42 |
43 |
53 | }
54 |
--------------------------------------------------------------------------------
/ScaffoldedIdentity/Areas/Identity/Pages/Account/Manage/ExternalLogins.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Authentication;
6 | using Microsoft.AspNetCore.Identity;
7 | using Microsoft.AspNetCore.Mvc;
8 | using Microsoft.AspNetCore.Mvc.RazorPages;
9 |
10 | namespace ScaffoldedIdentity.Areas.Identity.Pages.Account.Manage
11 | {
12 | public class ExternalLoginsModel : PageModel
13 | {
14 | private readonly UserManager _userManager;
15 | private readonly SignInManager _signInManager;
16 |
17 | public ExternalLoginsModel(
18 | UserManager userManager,
19 | SignInManager signInManager)
20 | {
21 | _userManager = userManager;
22 | _signInManager = signInManager;
23 | }
24 |
25 | public IList CurrentLogins { get; set; }
26 |
27 | public IList OtherLogins { get; set; }
28 |
29 | public bool ShowRemoveButton { get; set; }
30 |
31 | [TempData]
32 | public string StatusMessage { get; set; }
33 |
34 | public async Task OnGetAsync()
35 | {
36 | var user = await _userManager.GetUserAsync(User);
37 | if (user == null)
38 | {
39 | return NotFound($"Unable to load user with ID 'user.Id'.");
40 | }
41 |
42 | CurrentLogins = await _userManager.GetLoginsAsync(user);
43 | OtherLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync())
44 | .Where(auth => CurrentLogins.All(ul => auth.Name != ul.LoginProvider))
45 | .ToList();
46 | ShowRemoveButton = user.PasswordHash != null || CurrentLogins.Count > 1;
47 | return Page();
48 | }
49 |
50 | public async Task OnPostRemoveLoginAsync(string loginProvider, string providerKey)
51 | {
52 | var user = await _userManager.GetUserAsync(User);
53 | if (user == null)
54 | {
55 | return NotFound($"Unable to load user with ID 'user.Id'.");
56 | }
57 |
58 | var result = await _userManager.RemoveLoginAsync(user, loginProvider, providerKey);
59 | if (!result.Succeeded)
60 | {
61 | StatusMessage = "The external login was not removed.";
62 | return RedirectToPage();
63 | }
64 |
65 | await _signInManager.RefreshSignInAsync(user);
66 | StatusMessage = "The external login was removed.";
67 | return RedirectToPage();
68 | }
69 |
70 | public async Task OnPostLinkLoginAsync(string provider)
71 | {
72 | // Clear the existing external cookie to ensure a clean login process
73 | await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
74 |
75 | // Request a redirect to the external login provider to link a login for the current user
76 | var redirectUrl = Url.Page("./ExternalLogins", pageHandler: "LinkLoginCallback");
77 | var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, _userManager.GetUserId(User));
78 | return new ChallengeResult(provider, properties);
79 | }
80 |
81 | public async Task OnGetLinkLoginCallbackAsync()
82 | {
83 | var user = await _userManager.GetUserAsync(User);
84 | if (user == null)
85 | {
86 | return NotFound($"Unable to load user with ID 'user.Id'.");
87 | }
88 |
89 | var info = await _signInManager.GetExternalLoginInfoAsync(user.Id);
90 | if (info == null)
91 | {
92 | throw new InvalidOperationException($"Unexpected error occurred loading external login info for user with ID '{user.Id}'.");
93 | }
94 |
95 | var result = await _userManager.AddLoginAsync(user, info);
96 | if (!result.Succeeded)
97 | {
98 | StatusMessage = "The external login was not added. External logins can only be associated with one account.";
99 | return RedirectToPage();
100 | }
101 |
102 | // Clear the existing external cookie to ensure a clean login process
103 | await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
104 |
105 | StatusMessage = "The external login was added.";
106 | return RedirectToPage();
107 | }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/ScaffoldedIdentity/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model GenerateRecoveryCodesModel
3 | @{
4 | ViewData["Title"] = "Generate two-factor authentication (2FA) recovery codes";
5 | ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
6 | }
7 |
8 |
9 |
@ViewData["Title"]
10 |
11 |
12 |
13 | Put these codes in a safe place.
14 |
15 |
16 | If you lose your device and don't have the recovery codes you will lose access to your account.
17 |
18 |
19 | Generating new recovery codes does not change the keys used in authenticator apps. If you wish to change the key
20 | used in an authenticator app you should reset your authenticator keys.
21 |
22 |
23 |
24 |
27 |
--------------------------------------------------------------------------------
/ScaffoldedIdentity/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Identity;
6 | using Microsoft.AspNetCore.Mvc;
7 | using Microsoft.AspNetCore.Mvc.RazorPages;
8 | using Microsoft.Extensions.Logging;
9 |
10 | namespace ScaffoldedIdentity.Areas.Identity.Pages.Account.Manage
11 | {
12 | public class GenerateRecoveryCodesModel : PageModel
13 | {
14 | private readonly UserManager _userManager;
15 | private readonly ILogger _logger;
16 |
17 | public GenerateRecoveryCodesModel(
18 | UserManager userManager,
19 | ILogger logger)
20 | {
21 | _userManager = userManager;
22 | _logger = logger;
23 | }
24 |
25 | [TempData]
26 | public string[] RecoveryCodes { get; set; }
27 |
28 | [TempData]
29 | public string StatusMessage { get; set; }
30 |
31 | public async Task OnGetAsync()
32 | {
33 | var user = await _userManager.GetUserAsync(User);
34 | if (user == null)
35 | {
36 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
37 | }
38 |
39 | var isTwoFactorEnabled = await _userManager.GetTwoFactorEnabledAsync(user);
40 | if (!isTwoFactorEnabled)
41 | {
42 | var userId = await _userManager.GetUserIdAsync(user);
43 | throw new InvalidOperationException($"Cannot generate recovery codes for user with ID '{userId}' because they do not have 2FA enabled.");
44 | }
45 |
46 | return Page();
47 | }
48 |
49 | public async Task OnPostAsync()
50 | {
51 | var user = await _userManager.GetUserAsync(User);
52 | if (user == null)
53 | {
54 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
55 | }
56 |
57 | var isTwoFactorEnabled = await _userManager.GetTwoFactorEnabledAsync(user);
58 | var userId = await _userManager.GetUserIdAsync(user);
59 | if (!isTwoFactorEnabled)
60 | {
61 | throw new InvalidOperationException($"Cannot generate recovery codes for user with ID '{userId}' as they do not have 2FA enabled.");
62 | }
63 |
64 | var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
65 | RecoveryCodes = recoveryCodes.ToArray();
66 |
67 | _logger.LogInformation("User with ID '{UserId}' has generated new 2FA recovery codes.", userId);
68 | StatusMessage = "You have generated new recovery codes.";
69 | return RedirectToPage("./ShowRecoveryCodes");
70 | }
71 | }
72 | }
--------------------------------------------------------------------------------
/ScaffoldedIdentity/Areas/Identity/Pages/Account/Manage/Index.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model IndexModel
3 | @{
4 | ViewData["Title"] = "Profile";
5 | ViewData["ActivePage"] = ManageNavPages.Index;
6 | }
7 |
8 |
12 |
13 | If you reset your authenticator key your authenticator app will not work until you reconfigure it.
14 |
15 |
16 | This process disables 2FA until you verify your authenticator app.
17 | If you do not complete your authenticator app configuration you may lose access to your account.
18 |
19 |
20 |
21 |
24 |
--------------------------------------------------------------------------------
/ScaffoldedIdentity/Areas/Identity/Pages/Account/Manage/ResetAuthenticator.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Identity;
6 | using Microsoft.AspNetCore.Mvc;
7 | using Microsoft.AspNetCore.Mvc.RazorPages;
8 | using Microsoft.Extensions.Logging;
9 |
10 | namespace ScaffoldedIdentity.Areas.Identity.Pages.Account.Manage
11 | {
12 | public class ResetAuthenticatorModel : PageModel
13 | {
14 | UserManager _userManager;
15 | private readonly SignInManager _signInManager;
16 | ILogger _logger;
17 |
18 | public ResetAuthenticatorModel(
19 | UserManager userManager,
20 | SignInManager signInManager,
21 | ILogger logger)
22 | {
23 | _userManager = userManager;
24 | _signInManager = signInManager;
25 | _logger = logger;
26 | }
27 |
28 | [TempData]
29 | public string StatusMessage { get; set; }
30 |
31 | public async Task OnGet()
32 | {
33 | var user = await _userManager.GetUserAsync(User);
34 | if (user == null)
35 | {
36 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
37 | }
38 |
39 | return Page();
40 | }
41 |
42 | public async Task OnPostAsync()
43 | {
44 | var user = await _userManager.GetUserAsync(User);
45 | if (user == null)
46 | {
47 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
48 | }
49 |
50 | await _userManager.SetTwoFactorEnabledAsync(user, false);
51 | await _userManager.ResetAuthenticatorKeyAsync(user);
52 | _logger.LogInformation("User with ID '{UserId}' has reset their authentication app key.", user.Id);
53 |
54 | await _signInManager.RefreshSignInAsync(user);
55 | StatusMessage = "Your authenticator app key has been reset, you will need to configure your authenticator app using the new key.";
56 |
57 | return RedirectToPage("./EnableAuthenticator");
58 | }
59 | }
60 | }
--------------------------------------------------------------------------------
/ScaffoldedIdentity/Areas/Identity/Pages/Account/Manage/SetPassword.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model SetPasswordModel
3 | @{
4 | ViewData["Title"] = "Set password";
5 | ViewData["ActivePage"] = ManageNavPages.ChangePassword;
6 | }
7 |
8 |
Set your password
9 |
10 |
11 | You do not have a local username/password for this site. Add a local
12 | account so you can log in without an external login.
13 |
14 |
15 |
16 |
30 |
31 |
32 |
33 | @section Scripts {
34 |
35 | }
--------------------------------------------------------------------------------
/ScaffoldedIdentity/Areas/Identity/Pages/Account/Manage/SetPassword.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.DataAnnotations;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 | using Microsoft.AspNetCore.Identity;
7 | using Microsoft.AspNetCore.Mvc;
8 | using Microsoft.AspNetCore.Mvc.RazorPages;
9 |
10 | namespace ScaffoldedIdentity.Areas.Identity.Pages.Account.Manage
11 | {
12 | public class SetPasswordModel : PageModel
13 | {
14 | private readonly UserManager _userManager;
15 | private readonly SignInManager _signInManager;
16 |
17 | public SetPasswordModel(
18 | UserManager userManager,
19 | SignInManager signInManager)
20 | {
21 | _userManager = userManager;
22 | _signInManager = signInManager;
23 | }
24 |
25 | [BindProperty]
26 | public InputModel Input { get; set; }
27 |
28 | [TempData]
29 | public string StatusMessage { get; set; }
30 |
31 | public class InputModel
32 | {
33 | [Required]
34 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
35 | [DataType(DataType.Password)]
36 | [Display(Name = "New password")]
37 | public string NewPassword { get; set; }
38 |
39 | [DataType(DataType.Password)]
40 | [Display(Name = "Confirm new password")]
41 | [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
42 | public string ConfirmPassword { get; set; }
43 | }
44 |
45 | public async Task OnGetAsync()
46 | {
47 | var user = await _userManager.GetUserAsync(User);
48 | if (user == null)
49 | {
50 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
51 | }
52 |
53 | var hasPassword = await _userManager.HasPasswordAsync(user);
54 |
55 | if (hasPassword)
56 | {
57 | return RedirectToPage("./ChangePassword");
58 | }
59 |
60 | return Page();
61 | }
62 |
63 | public async Task OnPostAsync()
64 | {
65 | if (!ModelState.IsValid)
66 | {
67 | return Page();
68 | }
69 |
70 | var user = await _userManager.GetUserAsync(User);
71 | if (user == null)
72 | {
73 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
74 | }
75 |
76 | var addPasswordResult = await _userManager.AddPasswordAsync(user, Input.NewPassword);
77 | if (!addPasswordResult.Succeeded)
78 | {
79 | foreach (var error in addPasswordResult.Errors)
80 | {
81 | ModelState.AddModelError(string.Empty, error.Description);
82 | }
83 | return Page();
84 | }
85 |
86 | await _signInManager.RefreshSignInAsync(user);
87 | StatusMessage = "Your password has been set.";
88 |
89 | return RedirectToPage();
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/ScaffoldedIdentity/Areas/Identity/Pages/Account/Manage/ShowRecoveryCodes.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model ShowRecoveryCodesModel
3 | @{
4 | ViewData["Title"] = "Recovery codes";
5 | ViewData["ActivePage"] = "TwoFactorAuthentication";
6 | }
7 |
8 |
9 |
@ViewData["Title"]
10 |
11 |
12 | Put these codes in a safe place.
13 |
14 |
15 | If you lose your device and don't have the recovery codes you will lose access to your account.
16 |
42 | There are no external authentication services configured. See this article
43 | for details on setting up this ASP.NET application to support logging in via external services.
44 |
12 | This app does not currently have a real email sender registered, see these docs for how to configure a real email sender.
13 | Normally this would be emailed: Click here to confirm your account
14 |
15 | }
16 | else
17 | {
18 |
19 | Please check your email to confirm your account.
20 |
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/ScaffoldedIdentity/Areas/Identity/Pages/Account/RegisterConfirmation.cshtml.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Authorization;
2 | using System.Text;
3 | using System.Threading.Tasks;
4 | using Microsoft.AspNetCore.Identity;
5 | using Microsoft.AspNetCore.Identity.UI.Services;
6 | using Microsoft.AspNetCore.Mvc;
7 | using Microsoft.AspNetCore.Mvc.RazorPages;
8 | using Microsoft.AspNetCore.WebUtilities;
9 |
10 | namespace ScaffoldedIdentity.Areas.Identity.Pages.Account
11 | {
12 | [AllowAnonymous]
13 | public class RegisterConfirmationModel : PageModel
14 | {
15 | private readonly UserManager _userManager;
16 | private readonly IEmailSender _sender;
17 |
18 | public RegisterConfirmationModel(UserManager userManager, IEmailSender sender)
19 | {
20 | _userManager = userManager;
21 | _sender = sender;
22 | }
23 |
24 | public string Email { get; set; }
25 |
26 | public bool DisplayConfirmAccountLink { get; set; }
27 |
28 | public string EmailConfirmationUrl { get; set; }
29 |
30 | public async Task OnGetAsync(string email, string returnUrl = null)
31 | {
32 | if (email == null)
33 | {
34 | return RedirectToPage("/Index");
35 | }
36 |
37 | var user = await _userManager.FindByEmailAsync(email);
38 | if (user == null)
39 | {
40 | return NotFound($"Unable to load user with email '{email}'.");
41 | }
42 |
43 | Email = email;
44 | // Once you add a real email sender, you should remove this code that lets you confirm the account
45 | DisplayConfirmAccountLink = true;
46 | if (DisplayConfirmAccountLink)
47 | {
48 | var userId = await _userManager.GetUserIdAsync(user);
49 | var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
50 | code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
51 | EmailConfirmationUrl = Url.Page(
52 | "/Account/ConfirmEmail",
53 | pageHandler: null,
54 | values: new { area = "Identity", userId = userId, code = code, returnUrl = returnUrl },
55 | protocol: Request.Scheme);
56 | }
57 |
58 | return Page();
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/ScaffoldedIdentity/Areas/Identity/Pages/Account/ResendEmailConfirmation.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model ResendEmailConfirmationModel
3 | @{
4 | ViewData["Title"] = "Resend email confirmation";
5 | }
6 |
7 |
19 | Swapping to Development environment will display more detailed information about the error that occurred.
20 |
21 |
22 | Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application.
23 |