13 |
--------------------------------------------------------------------------------
/SampleProject/Pages/Account/ConfirmEmail.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 RouteDebugging.Data;
9 |
10 | namespace RouteDebugging.Pages.Account
11 | {
12 | public class ConfirmEmailModel : PageModel
13 | {
14 | private readonly UserManager _userManager;
15 |
16 | public ConfirmEmailModel(UserManager userManager)
17 | {
18 | _userManager = userManager;
19 | }
20 |
21 | public async Task OnGetAsync(string userId, string code)
22 | {
23 | if (userId == null || code == null)
24 | {
25 | return RedirectToPage("/Index");
26 | }
27 |
28 | var user = await _userManager.FindByIdAsync(userId);
29 | if (user == null)
30 | {
31 | throw new ApplicationException($"Unable to load user with ID '{userId}'.");
32 | }
33 |
34 | var result = await _userManager.ConfirmEmailAsync(user, code);
35 | if (!result.Succeeded)
36 | {
37 | throw new ApplicationException($"Error confirming email for user with ID '{userId}':");
38 | }
39 |
40 | return Page();
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/SampleProject/Pages/Account/ExternalLogin.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model ExternalLoginModel
3 | @{
4 | ViewData["Title"] = "Register";
5 | }
6 |
7 |
@ViewData["Title"]
8 |
Associate your @Model.LoginProvider account.
9 |
10 |
11 |
12 | You've successfully authenticated with @Model.LoginProvider.
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 | @await Html.PartialAsync("_ValidationScriptsPartial")
33 | }
34 |
--------------------------------------------------------------------------------
/SampleProject/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.Threading.Tasks;
7 | using Microsoft.AspNetCore.Identity;
8 | using Microsoft.AspNetCore.Mvc;
9 | using Microsoft.AspNetCore.Mvc.RazorPages;
10 | using Microsoft.Extensions.Logging;
11 | using RouteDebugging.Data;
12 |
13 | namespace RouteDebugging.Pages.Account
14 | {
15 | public class ExternalLoginModel : PageModel
16 | {
17 | private readonly SignInManager _signInManager;
18 | private readonly UserManager _userManager;
19 | private readonly ILogger _logger;
20 |
21 | public ExternalLoginModel(
22 | SignInManager signInManager,
23 | UserManager userManager,
24 | ILogger logger)
25 | {
26 | _signInManager = signInManager;
27 | _userManager = userManager;
28 | _logger = logger;
29 | }
30 |
31 | [BindProperty]
32 | public InputModel Input { get; set; }
33 |
34 | public string LoginProvider { get; set; }
35 |
36 | public string ReturnUrl { get; set; }
37 |
38 | [TempData]
39 | public string ErrorMessage { get; set; }
40 |
41 | public class InputModel
42 | {
43 | [Required]
44 | [EmailAddress]
45 | public string Email { get; set; }
46 | }
47 |
48 | public IActionResult OnGetAsync()
49 | {
50 | return RedirectToPage("./Login");
51 | }
52 |
53 | public IActionResult OnPost(string provider, string returnUrl = null)
54 | {
55 | // Request a redirect to the external login provider.
56 | var redirectUrl = Url.Page("./ExternalLogin", pageHandler: "Callback", values: new { returnUrl });
57 | var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
58 | return new ChallengeResult(provider, properties);
59 | }
60 |
61 | public async Task OnGetCallbackAsync(string returnUrl = null, string remoteError = null)
62 | {
63 | if (remoteError != null)
64 | {
65 | ErrorMessage = $"Error from external provider: {remoteError}";
66 | return RedirectToPage("./Login");
67 | }
68 | var info = await _signInManager.GetExternalLoginInfoAsync();
69 | if (info == null)
70 | {
71 | return RedirectToPage("./Login");
72 | }
73 |
74 | // Sign in the user with this external login provider if the user already has a login.
75 | var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor : true);
76 | if (result.Succeeded)
77 | {
78 | _logger.LogInformation("{Name} logged in with {LoginProvider} provider.", info.Principal.Identity.Name, info.LoginProvider);
79 | return LocalRedirect(Url.GetLocalUrl(returnUrl));
80 | }
81 | if (result.IsLockedOut)
82 | {
83 | return RedirectToPage("./Lockout");
84 | }
85 | else
86 | {
87 | // If the user does not have an account, then ask the user to create an account.
88 | ReturnUrl = returnUrl;
89 | LoginProvider = info.LoginProvider;
90 | if (info.Principal.HasClaim(c => c.Type == ClaimTypes.Email))
91 | {
92 | Input = new InputModel
93 | {
94 | Email = info.Principal.FindFirstValue(ClaimTypes.Email)
95 | };
96 | }
97 | return Page();
98 | }
99 | }
100 |
101 | public async Task OnPostConfirmationAsync(string returnUrl = null)
102 | {
103 | if (ModelState.IsValid)
104 | {
105 | // Get the information about the user from the external login provider
106 | var info = await _signInManager.GetExternalLoginInfoAsync();
107 | if (info == null)
108 | {
109 | throw new ApplicationException("Error loading external login information during confirmation.");
110 | }
111 | var user = new ApplicationUser { UserName = Input.Email, Email = Input.Email };
112 | var result = await _userManager.CreateAsync(user);
113 | if (result.Succeeded)
114 | {
115 | result = await _userManager.AddLoginAsync(user, info);
116 | if (result.Succeeded)
117 | {
118 | await _signInManager.SignInAsync(user, isPersistent: false);
119 | _logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);
120 | return LocalRedirect(Url.GetLocalUrl(returnUrl));
121 | }
122 | }
123 | foreach (var error in result.Errors)
124 | {
125 | ModelState.AddModelError(string.Empty, error.Description);
126 | }
127 | }
128 |
129 | ReturnUrl = returnUrl;
130 | return Page();
131 | }
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/SampleProject/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 | @await Html.PartialAsync("_ValidationScriptsPartial")
26 | }
27 |
--------------------------------------------------------------------------------
/SampleProject/Pages/Account/ForgotPassword.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.DataAnnotations;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Identity;
6 | using Microsoft.AspNetCore.Mvc;
7 | using Microsoft.AspNetCore.Mvc.RazorPages;
8 | using RouteDebugging.Data;
9 | using RouteDebugging.Services;
10 |
11 | namespace RouteDebugging.Pages.Account
12 | {
13 | public class ForgotPasswordModel : PageModel
14 | {
15 | private readonly UserManager _userManager;
16 | private readonly IEmailSender _emailSender;
17 |
18 | public ForgotPasswordModel(UserManager userManager, IEmailSender emailSender)
19 | {
20 | _userManager = userManager;
21 | _emailSender = emailSender;
22 | }
23 |
24 | [BindProperty]
25 | public InputModel Input { get; set; }
26 |
27 | public class InputModel
28 | {
29 | [Required]
30 | [EmailAddress]
31 | public string Email { get; set; }
32 | }
33 |
34 | public async Task OnPostAsync()
35 | {
36 | if (ModelState.IsValid)
37 | {
38 | var user = await _userManager.FindByEmailAsync(Input.Email);
39 | if (user == null || !(await _userManager.IsEmailConfirmedAsync(user)))
40 | {
41 | // Don't reveal that the user does not exist or is not confirmed
42 | return RedirectToPage("./ForgotPasswordConfirmation");
43 | }
44 |
45 | // For more information on how to enable account confirmation and password reset please
46 | // visit https://go.microsoft.com/fwlink/?LinkID=532713
47 | var code = await _userManager.GeneratePasswordResetTokenAsync(user);
48 | var callbackUrl = Url.ResetPasswordCallbackLink(user.Id, code, Request.Scheme);
49 | await _emailSender.SendResetPasswordAsync(Input.Email, callbackUrl);
50 | return RedirectToPage("./ForgotPasswordConfirmation");
51 | }
52 |
53 | return Page();
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/SampleProject/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 |
57 | There are no external authentication services configured. See this article
58 | for details on setting up this ASP.NET application to support logging in via external services.
59 |
38 |
39 | @section Scripts {
40 | @await Html.PartialAsync("_ValidationScriptsPartial")
41 | }
--------------------------------------------------------------------------------
/SampleProject/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.Identity;
7 | using Microsoft.AspNetCore.Mvc;
8 | using Microsoft.AspNetCore.Mvc.RazorPages;
9 | using Microsoft.Extensions.Logging;
10 | using RouteDebugging.Data;
11 |
12 | namespace RouteDebugging.Pages.Account
13 | {
14 | public class LoginWith2faModel : PageModel
15 | {
16 | private readonly SignInManager _signInManager;
17 | private readonly ILogger _logger;
18 |
19 | public LoginWith2faModel(SignInManager signInManager, ILogger logger)
20 | {
21 | _signInManager = signInManager;
22 | _logger = logger;
23 | }
24 |
25 | [BindProperty]
26 | public InputModel Input { get; set; }
27 |
28 | public bool RememberMe { get; set; }
29 |
30 | public string ReturnUrl { get; set; }
31 |
32 | public class InputModel
33 | {
34 | [Required]
35 | [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
36 | [DataType(DataType.Text)]
37 | [Display(Name = "Authenticator code")]
38 | public string TwoFactorCode { get; set; }
39 |
40 | [Display(Name = "Remember this machine")]
41 | public bool RememberMachine { get; set; }
42 | }
43 |
44 | public async Task OnGetAsync(bool rememberMe, string returnUrl = null)
45 | {
46 | // Ensure the user has gone through the username & password screen first
47 | var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
48 |
49 | if (user == null)
50 | {
51 | throw new ApplicationException($"Unable to load two-factor authentication user.");
52 | }
53 |
54 | ReturnUrl = returnUrl;
55 | RememberMe = rememberMe;
56 |
57 | return Page();
58 | }
59 |
60 | public async Task OnPostAsync(bool rememberMe, string returnUrl = null)
61 | {
62 | if (!ModelState.IsValid)
63 | {
64 | return Page();
65 | }
66 |
67 | var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
68 | if (user == null)
69 | {
70 | throw new ApplicationException($"Unable to load two-factor authentication user.");
71 | }
72 |
73 | var authenticatorCode = Input.TwoFactorCode.Replace(" ", string.Empty).Replace("-", string.Empty);
74 |
75 | var result = await _signInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, rememberMe, Input.RememberMachine);
76 |
77 | if (result.Succeeded)
78 | {
79 | _logger.LogInformation("User with ID '{UserId}' logged in with 2fa.", user.Id);
80 | return LocalRedirect(Url.GetLocalUrl(returnUrl));
81 | }
82 | else if (result.IsLockedOut)
83 | {
84 | _logger.LogWarning("User with ID '{UserId}' account locked out.", user.Id);
85 | return RedirectToPage("./Lockout");
86 | }
87 | else
88 | {
89 | _logger.LogWarning("Invalid authenticator code entered for user with ID '{UserId}'.", user.Id);
90 | ModelState.AddModelError(string.Empty, "Invalid authenticator code.");
91 | return Page();
92 | }
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/SampleProject/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 | @await Html.PartialAsync("_ValidationScriptsPartial")
29 | }
--------------------------------------------------------------------------------
/SampleProject/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.Identity;
7 | using Microsoft.AspNetCore.Mvc;
8 | using Microsoft.AspNetCore.Mvc.RazorPages;
9 | using Microsoft.Extensions.Logging;
10 | using RouteDebugging.Data;
11 |
12 | namespace RouteDebugging.Pages.Account
13 | {
14 | public class LoginWithRecoveryCodeModel : PageModel
15 | {
16 | private readonly SignInManager _signInManager;
17 | private readonly ILogger _logger;
18 |
19 | public LoginWithRecoveryCodeModel(SignInManager signInManager, ILogger logger)
20 | {
21 | _signInManager = signInManager;
22 | _logger = logger;
23 | }
24 |
25 | [BindProperty]
26 | public InputModel Input { get; set; }
27 |
28 | public string ReturnUrl { get; set; }
29 |
30 | public class InputModel
31 | {
32 | [BindProperty]
33 | [Required]
34 | [DataType(DataType.Text)]
35 | [Display(Name = "Recovery Code")]
36 | public string RecoveryCode { get; set; }
37 | }
38 |
39 | public async Task OnGetAsync(string returnUrl = null)
40 | {
41 | // Ensure the user has gone through the username & password screen first
42 | var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
43 | if (user == null)
44 | {
45 | throw new ApplicationException($"Unable to load two-factor authentication user.");
46 | }
47 |
48 | ReturnUrl = returnUrl;
49 |
50 | return Page();
51 | }
52 |
53 | public async Task OnPostAsync(string returnUrl = null)
54 | {
55 | if (!ModelState.IsValid)
56 | {
57 | return Page();
58 | }
59 |
60 | var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
61 | if (user == null)
62 | {
63 | throw new ApplicationException($"Unable to load two-factor authentication user.");
64 | }
65 |
66 | var recoveryCode = Input.RecoveryCode.Replace(" ", string.Empty);
67 |
68 | var result = await _signInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode);
69 |
70 | if (result.Succeeded)
71 | {
72 | _logger.LogInformation("User with ID '{UserId}' logged in with a recovery code.", user.Id);
73 | return LocalRedirect(Url.GetLocalUrl(returnUrl));
74 | }
75 | if (result.IsLockedOut)
76 | {
77 | _logger.LogWarning("User with ID '{UserId}' account locked out.", user.Id);
78 | return RedirectToPage("./Lockout");
79 | }
80 | else
81 | {
82 | _logger.LogWarning("Invalid recovery code entered for user with ID '{UserId}' ", user.Id);
83 | ModelState.AddModelError(string.Empty, "Invalid recovery code entered.");
84 | return Page();
85 | }
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/SampleProject/Pages/Account/Manage/ChangePassword.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model ChangePasswordModel
3 | @{
4 | ViewData["Title"] = "Change password";
5 | }
6 |
7 |
32 |
33 | @section Scripts {
34 | @await Html.PartialAsync("_ValidationScriptsPartial")
35 | }
36 |
--------------------------------------------------------------------------------
/SampleProject/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 | using RouteDebugging.Data;
11 |
12 | namespace RouteDebugging.Pages.Account.Manage
13 | {
14 | public class ChangePasswordModel : PageModel
15 | {
16 | private readonly UserManager _userManager;
17 | private readonly SignInManager _signInManager;
18 | private readonly ILogger _logger;
19 |
20 | public ChangePasswordModel(
21 | UserManager userManager,
22 | SignInManager signInManager,
23 | ILogger logger)
24 | {
25 | _userManager = userManager;
26 | _signInManager = signInManager;
27 | _logger = logger;
28 | }
29 |
30 | [BindProperty]
31 | public InputModel Input { get; set; }
32 |
33 | [TempData]
34 | public string StatusMessage { get; set; }
35 |
36 | public class InputModel
37 | {
38 | [Required]
39 | [DataType(DataType.Password)]
40 | [Display(Name = "Current password")]
41 | public string OldPassword { get; set; }
42 |
43 | [Required]
44 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
45 | [DataType(DataType.Password)]
46 | [Display(Name = "New password")]
47 | public string NewPassword { get; set; }
48 |
49 | [DataType(DataType.Password)]
50 | [Display(Name = "Confirm new password")]
51 | [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
52 | public string ConfirmPassword { get; set; }
53 | }
54 |
55 | public async Task OnGetAsync()
56 | {
57 | var user = await _userManager.GetUserAsync(User);
58 | if (user == null)
59 | {
60 | throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
61 | }
62 |
63 | var hasPassword = await _userManager.HasPasswordAsync(user);
64 | if (!hasPassword)
65 | {
66 | return RedirectToPage("./SetPassword");
67 | }
68 |
69 | return Page();
70 | }
71 |
72 | public async Task OnPostAsync()
73 | {
74 | if (!ModelState.IsValid)
75 | {
76 | return Page();
77 | }
78 |
79 | var user = await _userManager.GetUserAsync(User);
80 | if (user == null)
81 | {
82 | throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
83 | }
84 |
85 | var changePasswordResult = await _userManager.ChangePasswordAsync(user, Input.OldPassword, Input.NewPassword);
86 | if (!changePasswordResult.Succeeded)
87 | {
88 | foreach (var error in changePasswordResult.Errors)
89 | {
90 | ModelState.AddModelError(string.Empty, error.Description);
91 | }
92 | return Page();
93 | }
94 |
95 | await _signInManager.SignInAsync(user, isPersistent: false);
96 | _logger.LogInformation("User changed their password successfully.");
97 | StatusMessage = "Your password has been changed.";
98 |
99 | return RedirectToPage();
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/SampleProject/Pages/Account/Manage/Disable2fa.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model Disable2faModel
3 | @{
4 | ViewData["Title"] = "Disable two-factor authentication (2FA)";
5 | ViewData["ActivePage"] = "TwoFactorAuthentication";
6 | }
7 |
8 |
@ViewData["Title"]
9 |
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 |
--------------------------------------------------------------------------------
/SampleProject/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 | using RouteDebugging.Data;
10 |
11 | namespace RouteDebugging.Pages.Account.Manage
12 | {
13 | public class Disable2faModel : PageModel
14 | {
15 | private readonly UserManager _userManager;
16 | private readonly ILogger _logger;
17 |
18 | public Disable2faModel(
19 | UserManager userManager,
20 | ILogger logger)
21 | {
22 | _userManager = userManager;
23 | _logger = logger;
24 | }
25 |
26 | public async Task OnGet()
27 | {
28 | var user = await _userManager.GetUserAsync(User);
29 | if (user == null)
30 | {
31 | throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
32 | }
33 |
34 | if (!await _userManager.GetTwoFactorEnabledAsync(user))
35 | {
36 | throw new ApplicationException($"Cannot disable 2FA for user with ID '{_userManager.GetUserId(User)}' as it's not currently enabled.");
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 | throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
48 | }
49 |
50 | var disable2faResult = await _userManager.SetTwoFactorEnabledAsync(user, false);
51 | if (!disable2faResult.Succeeded)
52 | {
53 | throw new ApplicationException($"Unexpected error occurred disabling 2FA for user with ID '{_userManager.GetUserId(User)}'.");
54 | }
55 |
56 | _logger.LogInformation("User with ID '{UserId}' has disabled 2fa.", _userManager.GetUserId(User));
57 |
58 | return RedirectToPage("./TwoFactorAuthentication");
59 | }
60 | }
61 | }
--------------------------------------------------------------------------------
/SampleProject/Pages/Account/Manage/EnableAuthenticator.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model EnableAuthenticatorModel
3 | @{
4 | ViewData["Title"] = "Configure authenticator app";
5 | ViewData["ActivePage"] = "TwoFactorAuthentication";
6 | }
7 |
8 |
@ViewData["Title"]
9 |
10 |
To use an authenticator app go through the following steps:
11 |
12 |
13 |
14 | Download a two-factor authenticator app like Microsoft Authenticator for
15 | Windows Phone,
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.
25 |
To enable QR code generation please read our documentation.
26 |
27 |
28 |
29 |
30 |
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 | @await Html.PartialAsync("_ValidationScriptsPartial")
53 | }
54 |
--------------------------------------------------------------------------------
/SampleProject/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 | using RouteDebugging.Data;
14 |
15 | namespace RouteDebugging.Pages.Account.Manage
16 | {
17 | public class EnableAuthenticatorModel : PageModel
18 | {
19 | private readonly UserManager _userManager;
20 | private readonly ILogger _logger;
21 | private readonly UrlEncoder _urlEncoder;
22 |
23 | private const string AuthenicatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6";
24 |
25 | public EnableAuthenticatorModel(
26 | UserManager userManager,
27 | ILogger logger,
28 | UrlEncoder urlEncoder)
29 | {
30 | _userManager = userManager;
31 | _logger = logger;
32 | _urlEncoder = urlEncoder;
33 | }
34 |
35 | public string SharedKey { get; set; }
36 |
37 | public string AuthenticatorUri { get; set; }
38 |
39 | [BindProperty]
40 | public InputModel Input { get; set; }
41 |
42 | public class InputModel
43 | {
44 | [Required]
45 | [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
46 | [DataType(DataType.Text)]
47 | [Display(Name = "Verification Code")]
48 | public string Code { get; set; }
49 | }
50 |
51 | public async Task OnGetAsync()
52 | {
53 | var user = await _userManager.GetUserAsync(User);
54 | if (user == null)
55 | {
56 | throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
57 | }
58 |
59 | await LoadSharedKeyAndQrCodeUriAsync(user);
60 | if (string.IsNullOrEmpty(SharedKey))
61 | {
62 | await _userManager.ResetAuthenticatorKeyAsync(user);
63 | await LoadSharedKeyAndQrCodeUriAsync(user);
64 | }
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 | throw new ApplicationException($"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 | _logger.LogInformation("User with ID '{UserId}' has enabled 2FA with an authenticator app.", user.Id);
98 | return RedirectToPage("./GenerateRecoveryCodes");
99 | }
100 |
101 | private async Task LoadSharedKeyAndQrCodeUriAsync(ApplicationUser user)
102 | {
103 | // Load the authenticator key & QR code URI to display on the form
104 | var unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);
105 | if (!string.IsNullOrEmpty(unformattedKey))
106 | {
107 | SharedKey = FormatKey(unformattedKey);
108 | AuthenticatorUri = GenerateQrCodeUri(user.Email, unformattedKey);
109 | }
110 | }
111 |
112 | private string FormatKey(string unformattedKey)
113 | {
114 | var result = new StringBuilder();
115 | int currentPosition = 0;
116 | while (currentPosition + 4 < unformattedKey.Length)
117 | {
118 | result.Append(unformattedKey.Substring(currentPosition, 4)).Append(" ");
119 | currentPosition += 4;
120 | }
121 | if (currentPosition < unformattedKey.Length)
122 | {
123 | result.Append(unformattedKey.Substring(currentPosition));
124 | }
125 |
126 | return result.ToString().ToLowerInvariant();
127 | }
128 |
129 | private string GenerateQrCodeUri(string email, string unformattedKey)
130 | {
131 | return string.Format(
132 | AuthenicatorUriFormat,
133 | _urlEncoder.Encode("RouteDebugging"),
134 | _urlEncoder.Encode(email),
135 | unformattedKey);
136 | }
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/SampleProject/Pages/Account/Manage/ExternalLogins.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model ExternalLoginsModel
3 | @{
4 | ViewData["Title"] = "Manage your external logins";
5 | }
6 |
7 | @Html.Partial("_StatusMessage", Model.StatusMessage)
8 | @if (Model.CurrentLogins?.Count > 0)
9 | {
10 |
42 |
43 | @section Scripts {
44 | @await Html.PartialAsync("_ValidationScriptsPartial")
45 | }
46 |
--------------------------------------------------------------------------------
/SampleProject/Pages/Account/Manage/Index.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.DataAnnotations;
4 | using System.Linq;
5 | using System.Text.Encodings.Web;
6 | using System.Threading.Tasks;
7 | using Microsoft.AspNetCore.Identity;
8 | using Microsoft.AspNetCore.Mvc;
9 | using Microsoft.AspNetCore.Mvc.RazorPages;
10 | using RouteDebugging.Data;
11 | using RouteDebugging.Services;
12 |
13 | namespace RouteDebugging.Pages.Account.Manage
14 | {
15 | public partial class IndexModel : PageModel
16 | {
17 | private readonly UserManager _userManager;
18 | private readonly SignInManager _signInManager;
19 | private readonly IEmailSender _emailSender;
20 |
21 | public IndexModel(
22 | UserManager userManager,
23 | SignInManager signInManager,
24 | IEmailSender emailSender)
25 | {
26 | _userManager = userManager;
27 | _signInManager = signInManager;
28 | _emailSender = emailSender;
29 | }
30 |
31 | public string Username { get; set; }
32 |
33 | public bool IsEmailConfirmed { get; set; }
34 |
35 | [TempData]
36 | public string StatusMessage { get; set; }
37 |
38 | [BindProperty]
39 | public InputModel Input { get; set; }
40 |
41 | public class InputModel
42 | {
43 | [Required]
44 | [EmailAddress]
45 | public string Email { get; set; }
46 |
47 | [Phone]
48 | [Display(Name = "Phone number")]
49 | public string PhoneNumber { get; set; }
50 | }
51 |
52 | public async Task OnGetAsync()
53 | {
54 | var user = await _userManager.GetUserAsync(User);
55 | if (user == null)
56 | {
57 | throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
58 | }
59 |
60 | Username = user.UserName;
61 |
62 | Input = new InputModel
63 | {
64 | Email = user.Email,
65 | PhoneNumber = user.PhoneNumber
66 | };
67 |
68 | IsEmailConfirmed = await _userManager.IsEmailConfirmedAsync(user);
69 |
70 | return Page();
71 | }
72 |
73 | public async Task OnPostAsync()
74 | {
75 | if (!ModelState.IsValid)
76 | {
77 | return Page();
78 | }
79 |
80 | var user = await _userManager.GetUserAsync(User);
81 | if (user == null)
82 | {
83 | throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
84 | }
85 |
86 | if (Input.Email != user.Email)
87 | {
88 | var setEmailResult = await _userManager.SetEmailAsync(user, Input.Email);
89 | if (!setEmailResult.Succeeded)
90 | {
91 | throw new ApplicationException($"Unexpected error occurred setting email for user with ID '{user.Id}'.");
92 | }
93 | }
94 |
95 | if (Input.PhoneNumber != user.PhoneNumber)
96 | {
97 | var setPhoneResult = await _userManager.SetPhoneNumberAsync(user, Input.PhoneNumber);
98 | if (!setPhoneResult.Succeeded)
99 | {
100 | throw new ApplicationException($"Unexpected error occurred setting phone number for user with ID '{user.Id}'.");
101 | }
102 | }
103 |
104 | StatusMessage = "Your profile has been updated";
105 | return RedirectToPage();
106 | }
107 | public async Task OnPostSendVerificationEmailAsync()
108 | {
109 | if (!ModelState.IsValid)
110 | {
111 | return Page();
112 | }
113 |
114 | var user = await _userManager.GetUserAsync(User);
115 | if (user == null)
116 | {
117 | throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
118 | }
119 |
120 | var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
121 | var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
122 | await _emailSender.SendEmailConfirmationAsync(user.Email, callbackUrl);
123 |
124 | StatusMessage = "Verification email sent. Please check your email.";
125 | return RedirectToPage();
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/SampleProject/Pages/Account/Manage/ManageNavPages.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Mvc.Rendering;
6 |
7 | namespace RouteDebugging.Pages.Account.Manage
8 | {
9 | public static class ManageNavPages
10 | {
11 | public static string Index => "Index";
12 |
13 | public static string ChangePassword => "ChangePassword";
14 |
15 | public static string ExternalLogins => "ExternalLogins";
16 |
17 | public static string TwoFactorAuthentication => "TwoFactorAuthentication";
18 |
19 | public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index);
20 |
21 | public static string ChangePasswordNavClass(ViewContext viewContext) => PageNavClass(viewContext, ChangePassword);
22 |
23 | public static string ExternalLoginsNavClass(ViewContext viewContext) => PageNavClass(viewContext, ExternalLogins);
24 |
25 | public static string TwoFactorAuthenticationNavClass(ViewContext viewContext) => PageNavClass(viewContext, TwoFactorAuthentication);
26 |
27 | public static string PageNavClass(ViewContext viewContext, string page)
28 | {
29 | var activePage = viewContext.ViewData["ActivePage"] as string
30 | ?? System.IO.Path.GetFileNameWithoutExtension(viewContext.ActionDescriptor.DisplayName);
31 | return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? "active" : null;
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/SampleProject/Pages/Account/Manage/ResetAuthenticator.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model ResetAuthenticatorModel
3 | @{
4 | ViewData["Title"] = "Reset authenticator key";
5 | ViewData["ActivePage"] = "TwoFactorAuthentication";
6 | }
7 |
8 |
@ViewData["Title"]
9 |
10 |
11 |
12 | If you reset your authenticator key your authenticator app will not work until you reconfigure it.
13 |
14 |
15 | This process disables 2FA until you verify your authenticator app and will also reset your 2FA recovery codes.
16 | If you do not complete your authenticator app configuration you may lose access to your account.
17 |
18 |
19 |
20 |
23 |
--------------------------------------------------------------------------------
/SampleProject/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 | using RouteDebugging.Data;
10 |
11 | namespace RouteDebugging.Pages.Account.Manage
12 | {
13 | public class ResetAuthenticatorModel : PageModel
14 | {
15 | UserManager _userManager;
16 | ILogger _logger;
17 |
18 | public ResetAuthenticatorModel(
19 | UserManager userManager,
20 | ILogger logger)
21 | {
22 | _userManager = userManager;
23 | _logger = logger;
24 | }
25 | public async Task OnGet()
26 | {
27 | var user = await _userManager.GetUserAsync(User);
28 | if (user == null)
29 | {
30 | throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
31 | }
32 |
33 | return Page();
34 | }
35 |
36 | public async Task OnPostAsync()
37 | {
38 | var user = await _userManager.GetUserAsync(User);
39 | if (user == null)
40 | {
41 | throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
42 | }
43 |
44 | await _userManager.SetTwoFactorEnabledAsync(user, false);
45 | await _userManager.ResetAuthenticatorKeyAsync(user);
46 | _logger.LogInformation("User with ID '{UserId}' has reset their authentication app key.", user.Id);
47 |
48 | return RedirectToPage("./EnableAuthenticator");
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/SampleProject/Pages/Account/Manage/SetPassword.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model SetPasswordModel
3 | @{
4 | ViewData["Title"] = "Set password";
5 | ViewData["ActivePage"] = "ChangePassword";
6 | }
7 |
8 |
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 |