Skip to content

Commit 0de6a75

Browse files
committed
After session 9
1 parent 088de34 commit 0de6a75

31 files changed

+2159
-20
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"version": "0.2.0",
3+
"configurations": [
4+
{
5+
// Use IntelliSense to find out which attributes exist for C# debugging
6+
// Use hover for the description of the existing attributes
7+
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
8+
"name": ".NET Core Launch (web)",
9+
"type": "coreclr",
10+
"request": "launch",
11+
"preLaunchTask": "build",
12+
// If you have changed target frameworks, make sure to update the program path.
13+
"program": "${workspaceFolder}/src/bin/Debug/net6.0/MyCollectionSite.dll",
14+
"args": [],
15+
"cwd": "${workspaceFolder}/src",
16+
"stopAtEntry": false,
17+
// Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser
18+
"serverReadyAction": {
19+
"action": "openExternally",
20+
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
21+
},
22+
"env": {
23+
"ASPNETCORE_ENVIRONMENT": "Development"
24+
},
25+
"sourceFileMap": {
26+
"/Views": "${workspaceFolder}/Views"
27+
}
28+
},
29+
{
30+
"name": ".NET Core Attach",
31+
"type": "coreclr",
32+
"request": "attach"
33+
}
34+
]
35+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"version": "2.0.0",
3+
"tasks": [
4+
{
5+
"label": "build",
6+
"command": "dotnet",
7+
"type": "process",
8+
"args": [
9+
"build",
10+
"${workspaceFolder}/src/MyCollectionSite.csproj",
11+
"/property:GenerateFullPaths=true",
12+
"/consoleloggerparameters:NoSummary"
13+
],
14+
"problemMatcher": "$msCompile"
15+
},
16+
{
17+
"label": "publish",
18+
"command": "dotnet",
19+
"type": "process",
20+
"args": [
21+
"publish",
22+
"${workspaceFolder}/src/MyCollectionSite.csproj",
23+
"/property:GenerateFullPaths=true",
24+
"/consoleloggerparameters:NoSummary"
25+
],
26+
"problemMatcher": "$msCompile"
27+
},
28+
{
29+
"label": "watch",
30+
"command": "dotnet",
31+
"type": "process",
32+
"args": [
33+
"watch",
34+
"run",
35+
"--project",
36+
"${workspaceFolder}/src/MyCollectionSite.csproj"
37+
],
38+
"problemMatcher": "$msCompile"
39+
}
40+
]
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using Microsoft.AspNetCore.Identity;
2+
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
3+
using Microsoft.EntityFrameworkCore;
4+
5+
namespace MyCollectionSite.Areas.Identity.Data;
6+
7+
public class MyCollectionSiteIdentityDbContext : IdentityDbContext<IdentityUser>
8+
{
9+
public MyCollectionSiteIdentityDbContext(DbContextOptions<MyCollectionSiteIdentityDbContext> options)
10+
: base(options)
11+
{
12+
}
13+
14+
protected override void OnModelCreating(ModelBuilder builder)
15+
{
16+
base.OnModelCreating(builder);
17+
// Customize the ASP.NET Identity model and override the defaults if needed.
18+
// For example, you can rename the ASP.NET Identity table names and more.
19+
// Add your customizations after calling base.OnModelCreating(builder);
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
@page
2+
@model EnableAuthenticatorModel
3+
@{
4+
ViewData["Title"] = "Configure authenticator app";
5+
ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
6+
}
7+
8+
<partial name="_StatusMessage" for="StatusMessage" />
9+
<h3>@ViewData["Title"]</h3>
10+
<div>
11+
<p>To use an authenticator app go through the following steps:</p>
12+
<ol class="list">
13+
<li>
14+
<p>
15+
Download a two-factor authenticator app like Microsoft Authenticator for
16+
<a href="https://go.microsoft.com/fwlink/?Linkid=825072">Android</a> and
17+
<a href="https://go.microsoft.com/fwlink/?Linkid=825073">iOS</a> or
18+
Google Authenticator for
19+
<a href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&amp;hl=en">Android</a> and
20+
<a href="https://itunes.apple.com/us/app/google-authenticator/id388497605?mt=8">iOS</a>.
21+
</p>
22+
</li>
23+
<li>
24+
<p>Scan the QR Code or enter this key <kbd>@Model.SharedKey</kbd> into your two factor authenticator app. Spaces and casing do not matter.</p>
25+
<div class="alert alert-info">Learn how to <a href="https://go.microsoft.com/fwlink/?Linkid=852423">enable QR code generation</a>.</div>
26+
<div id="qrCode"></div>
27+
<div id="qrCodeData" data-url="@Model.AuthenticatorUri"></div>
28+
</li>
29+
<li>
30+
<p>
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+
</p>
34+
<div class="row">
35+
<div class="col-md-6">
36+
<form id="send-code" method="post">
37+
<div class="form-floating">
38+
<input asp-for="Input.Code" class="form-control" autocomplete="off" />
39+
<label asp-for="Input.Code" class="control-label form-label">Verification Code</label>
40+
<span asp-validation-for="Input.Code" class="text-danger"></span>
41+
</div>
42+
<button type="submit" class="w-100 btn btn-lg btn-primary">Verify</button>
43+
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
44+
</form>
45+
</div>
46+
</div>
47+
</li>
48+
</ol>
49+
</div>
50+
51+
@section Scripts {
52+
<partial name="_ValidationScriptsPartial" />
53+
<script type="text/javascript" src="~/lib/qrcode/qrcode.js"></script>
54+
<script type="text/javascript" src="~/js/qr.js"></script>
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
#nullable disable
4+
5+
using System;
6+
using System.ComponentModel.DataAnnotations;
7+
using System.Globalization;
8+
using System.Linq;
9+
using System.Text;
10+
using System.Text.Encodings.Web;
11+
using System.Threading.Tasks;
12+
using Microsoft.AspNetCore.Identity;
13+
using Microsoft.AspNetCore.Mvc;
14+
using Microsoft.AspNetCore.Mvc.RazorPages;
15+
using Microsoft.Extensions.Logging;
16+
17+
namespace MyCollectionSite.Areas.Identity.Pages.Account.Manage
18+
{
19+
public class EnableAuthenticatorModel : PageModel
20+
{
21+
private readonly UserManager<IdentityUser> _userManager;
22+
private readonly ILogger<EnableAuthenticatorModel> _logger;
23+
private readonly UrlEncoder _urlEncoder;
24+
25+
private const string AuthenticatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6";
26+
27+
public EnableAuthenticatorModel(
28+
UserManager<IdentityUser> userManager,
29+
ILogger<EnableAuthenticatorModel> logger,
30+
UrlEncoder urlEncoder)
31+
{
32+
_userManager = userManager;
33+
_logger = logger;
34+
_urlEncoder = urlEncoder;
35+
}
36+
37+
/// <summary>
38+
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
39+
/// directly from your code. This API may change or be removed in future releases.
40+
/// </summary>
41+
public string SharedKey { get; set; }
42+
43+
/// <summary>
44+
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
45+
/// directly from your code. This API may change or be removed in future releases.
46+
/// </summary>
47+
public string AuthenticatorUri { get; set; }
48+
49+
/// <summary>
50+
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
51+
/// directly from your code. This API may change or be removed in future releases.
52+
/// </summary>
53+
[TempData]
54+
public string[] RecoveryCodes { get; set; }
55+
56+
/// <summary>
57+
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
58+
/// directly from your code. This API may change or be removed in future releases.
59+
/// </summary>
60+
[TempData]
61+
public string StatusMessage { get; set; }
62+
63+
/// <summary>
64+
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
65+
/// directly from your code. This API may change or be removed in future releases.
66+
/// </summary>
67+
[BindProperty]
68+
public InputModel Input { get; set; }
69+
70+
/// <summary>
71+
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
72+
/// directly from your code. This API may change or be removed in future releases.
73+
/// </summary>
74+
public class InputModel
75+
{
76+
/// <summary>
77+
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
78+
/// directly from your code. This API may change or be removed in future releases.
79+
/// </summary>
80+
[Required]
81+
[StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
82+
[DataType(DataType.Text)]
83+
[Display(Name = "Verification Code")]
84+
public string Code { get; set; }
85+
}
86+
87+
public async Task<IActionResult> OnGetAsync()
88+
{
89+
var user = await _userManager.GetUserAsync(User);
90+
if (user == null)
91+
{
92+
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
93+
}
94+
95+
await LoadSharedKeyAndQrCodeUriAsync(user);
96+
97+
return Page();
98+
}
99+
100+
public async Task<IActionResult> OnPostAsync()
101+
{
102+
var user = await _userManager.GetUserAsync(User);
103+
if (user == null)
104+
{
105+
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
106+
}
107+
108+
if (!ModelState.IsValid)
109+
{
110+
await LoadSharedKeyAndQrCodeUriAsync(user);
111+
return Page();
112+
}
113+
114+
// Strip spaces and hyphens
115+
var verificationCode = Input.Code.Replace(" ", string.Empty).Replace("-", string.Empty);
116+
117+
var is2faTokenValid = await _userManager.VerifyTwoFactorTokenAsync(
118+
user, _userManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode);
119+
120+
if (!is2faTokenValid)
121+
{
122+
ModelState.AddModelError("Input.Code", "Verification code is invalid.");
123+
await LoadSharedKeyAndQrCodeUriAsync(user);
124+
return Page();
125+
}
126+
127+
await _userManager.SetTwoFactorEnabledAsync(user, true);
128+
var userId = await _userManager.GetUserIdAsync(user);
129+
_logger.LogInformation("User with ID '{UserId}' has enabled 2FA with an authenticator app.", userId);
130+
131+
StatusMessage = "Your authenticator app has been verified.";
132+
133+
if (await _userManager.CountRecoveryCodesAsync(user) == 0)
134+
{
135+
var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
136+
RecoveryCodes = recoveryCodes.ToArray();
137+
return RedirectToPage("./ShowRecoveryCodes");
138+
}
139+
else
140+
{
141+
return RedirectToPage("./TwoFactorAuthentication");
142+
}
143+
}
144+
145+
private async Task LoadSharedKeyAndQrCodeUriAsync(IdentityUser user)
146+
{
147+
// Load the authenticator key & QR code URI to display on the form
148+
var unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);
149+
if (string.IsNullOrEmpty(unformattedKey))
150+
{
151+
await _userManager.ResetAuthenticatorKeyAsync(user);
152+
unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);
153+
}
154+
155+
SharedKey = FormatKey(unformattedKey);
156+
157+
var email = await _userManager.GetEmailAsync(user);
158+
AuthenticatorUri = GenerateQrCodeUri(email, unformattedKey);
159+
}
160+
161+
private string FormatKey(string unformattedKey)
162+
{
163+
var result = new StringBuilder();
164+
int currentPosition = 0;
165+
while (currentPosition + 4 < unformattedKey.Length)
166+
{
167+
result.Append(unformattedKey.AsSpan(currentPosition, 4)).Append(' ');
168+
currentPosition += 4;
169+
}
170+
if (currentPosition < unformattedKey.Length)
171+
{
172+
result.Append(unformattedKey.AsSpan(currentPosition));
173+
}
174+
175+
return result.ToString().ToLowerInvariant();
176+
}
177+
178+
private string GenerateQrCodeUri(string email, string unformattedKey)
179+
{
180+
return string.Format(
181+
CultureInfo.InvariantCulture,
182+
AuthenticatorUriFormat,
183+
_urlEncoder.Encode("Microsoft.AspNetCore.Identity.UI"),
184+
_urlEncoder.Encode(email),
185+
unformattedKey);
186+
}
187+
}
188+
}

0 commit comments

Comments
 (0)