Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2FA/TOTP coverage for WASM+Identity #34189

Merged
merged 25 commits into from
Dec 11, 2024
Merged

Conversation

guardrex
Copy link
Collaborator

@guardrex guardrex commented Nov 20, 2024

Fixes #33772

On the PR

There are changes here to a few articles, including some touches to the account confirmation and PW recovery article. Those changes started out with just a few NITs but kind'a snowballed on me. They aren't really germane to the thrust of the PR on 2FA/TOTP.

The main article for review is the NEW WASM+Identity 2FA article at the bottom of the diff ...

aspnetcore/blazor/security/webassembly/standalone-with-identity/qrcodes-for-authenticator-apps.md

If you want to review all of the changes, that's great, of course, but you don't have to look at the rest of this if you're busy. I feel good about just taking on-going reader feedback on the rest of it.

Single login component

I see what was done for server-based Identity components ... three login components; but thus far, I like the approach I've taken here for a single Login component that can manage non-2FA, 2FA TOTP, and 2FA recovery code logins. I don't think it's too complicated, and it's economical to just have a single component. It also makes state management from the email/PW step to the 2FA step a snap.

New QR code generator

I'm going with https://github.com/manuelbl/QrCodeGenerator per DR's suggestion.

Sample app

You can pull down ...

https://github.com/guardrex/BlazorWebAssemblyStandaloneWithIdentity

... and use the seeded "Leela" account (leela@contoso.com, Passw0rd!), which is preloaded into the Login component. That account is confirmed by seeding code, so you don't need to implement account confirmation.

All of the 2FA features light up OOB ... no app config or additional steps required.

Revamp of TwoFactorRequestAsync

Halter, took your advice from the offline convo and added the serializer option for ...

DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull

... to shorten the TwoFactorRequestAsync method nicely by only passing the relevant non-null params to the POST.

AFAICT, all good! 🍻

Requiring a TOTP every login

Offline, we discussed setting useSessionCookies to true. However, that didn't work. It only required a TOTP for one more login. After that, it went back to remembering the user. BTW: I noticed that useSessionCookies wasn't in the main doc set article, so I opened #34255. We link to the main doc set article; so if we don't need to mention it here for any common use case, that's fine.

I wondered if calling the manage endpoint with ForgetMachine (true) after login would work. Yes! That approach appears to work 🎉, but I don't know if doing it that way is a supported scenario. I simply did this for after a 2FA login is successful ...

if (Input.TwoFactorCode.Length == 6)
{
    formResult = await Acct.LoginTwoFactorCodeAsync(Input.Email, Input.Password, Input.TwoFactorCode);

    if (formResult.Succeeded)
    {
        var forgetMachine = await Acct.TwoFactorRequestAsync(new() { ForgetMachine = true });
    }
}

MIA rendering

I built the Login component so that it would display the number of remaining recovery codes after a successful recovery code login. However, it's just NOT working ... not rendering the value of my recoveryCodesRemainingMessage. The message string is assigned to the variable, but the string just won't display for some strange 👽 rendering reason.

Halter, you said you'll debug that and see why it isn't working. I did remove the silly StateHasChanged call from the sample code. I just had that there on a lark, and it didn't work.

Added a loading check

This morning (Monday, 12/2), I added a loading check to the Manage2fa component (and a check in the companion JS to stop trim from a 💥 when the QR code element isn't rendered) because the page was flashing the enable-2FA UI pieces before rendering the correct UI when 2FA is already enabled. The loading UI seems to be working ok here.


Internal previews

📄 File 🔗 Preview link
aspnetcore/blazor/fundamentals/index.md aspnetcore/blazor/fundamentals/index
aspnetcore/blazor/security/qrcodes-for-authenticator-apps.md aspnetcore/blazor/security/qrcodes-for-authenticator-apps
aspnetcore/blazor/security/webassembly/standalone-with-identity/account-confirmation-and-password-recovery.md aspnetcore/blazor/security/webassembly/standalone-with-identity/account-confirmation-and-password-recovery
aspnetcore/blazor/security/webassembly/standalone-with-identity/index.md aspnetcore/blazor/security/webassembly/standalone-with-identity/index
aspnetcore/blazor/security/webassembly/standalone-with-identity/qrcodes-for-authenticator-apps.md aspnetcore/blazor/security/webassembly/standalone-with-identity/qrcodes-for-authenticator-apps
aspnetcore/security/authentication/identity-enable-qrcodes.md aspnetcore/security/authentication/identity-enable-qrcodes
aspnetcore/toc.yml aspnetcore/toc

@guardrex guardrex self-assigned this Nov 22, 2024
@guardrex guardrex mentioned this pull request Dec 1, 2024
@guardrex guardrex marked this pull request as ready for review December 2, 2024 15:41
These instructions use [Shim Sangmin](https://hogangnono.com)'s [qrcode.js: Cross-browser QRCode generator for JavaScript](https://davidshimjs.github.io/qrcodejs/) ([`davidshimjs/qrcodejs` GitHub repository](https://github.com/davidshimjs/qrcodejs)).
A QR code generated by the app to set up 2FA with an TOTP authenticator app must be generated by a QR code library.

The guidance in this article uses [`nimiq/qr-creator`](https://github.com/nimiq/qr-creator), but you can use any QR code generation library.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Rick-Anderson Can we update the corresponding content for MVC & Razor Pages as well for consistency? https://learn.microsoft.com/aspnet/core/security/authentication/identity-enable-qrcodes

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@guardrex Since this is for Blazor, is there a reason we can't use a .NET QR code generator for this? Maybe https://github.com/manuelbl/QrCodeGenerator?

Copy link
Collaborator Author

@guardrex guardrex Dec 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll have that swapped in tomorrow morning.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

@guardrex guardrex Dec 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@danroth27 @Rick-Anderson ... Done! 👍 I rolled the new QR code lib into both the BWA 2FA/TOTP article and the new WASM article here. It SUPER cleaned up the insanity on calling the JS lib in the BWA article to generate the QR code. I've also updated the WASM sample app, and you can see the revised Manage2fa component here ...

https://github.com/guardrex/BlazorWebAssemblyStandaloneWithIdentity/blob/main/BlazorWasmAuth/Components/Identity/Manage2fa.razor

@danroth27 ... Rick asked me offline about showing the Login and Manage2fa components here versus cross-linking to them in sample code, so they wouldn't appear in the article. Readers would need to follow a link to reach them.

I agree that they're long, but I don't think it matters all that much. Yeah, they have to scroll, but they don't need to click away from the text, which is something that I hate having to do myself when reading an article.

It would rope devs into taking that code when they go for a basic nuts-'n-bolts experience with the WASM+Identity sample app. However, hacking is so crazy bad these days that perhaps we should force the code on them. Then, should we also place the account confirmation and PW recovery code into the sample app? ... but don't require a confirmed account to log in because the dev then must set up an email provider just to get the basic experiences working as a demo.

Would placing all of these features into the sample app lead us to .... brace yourself ... lead us to the Music Store of Identity sample apps!!?? 🙈😆 ... i.e., ... it just adds so much stuff that it scares the poor new dev or new-to-Blazor dev to death. There are pros and cons to moving the code out of the article and into the sample app. It's your call 👨‍⚖️🏛️. I'm 👂 for your decision.

Copy link
Contributor

@Rick-Anderson Rick-Anderson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Outstanding

Co-authored-by: Rick Anderson <3605364+Rick-Anderson@users.noreply.github.com>
@guardrex
Copy link
Collaborator Author

guardrex commented Dec 4, 2024

🦖 NOTE TO SELF Done! 👍

Cross-link from the main doc set SPA article's additional resources ...

https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity-enable-qrcodes?view=aspnetcore-9.0



Delete the `<div>` element that contains the QR code instructions:
Delete the `<div>` element that contains the QR code instructions and the two `<div>` elements where the QR code should appear and where the QR code data is stored in the page:

```diff
- <div class="alert alert-info">
- Learn how to <a href="https://go.microsoft.com/fwlink/?Linkid=852423">enable
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get a new go.microsoft.com link that points to this Blazor QR code generation docs? It would be nice to update the Blazor Identity project template and scaffolding to point to this instead. Maybe we could remove <div></div><div data-url="@authenticatorUri"></div> while we're at it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless we need to change the current filename/URL, the GO link destination will be ...

https://learn.microsoft.com/aspnet/core/blazor/security/webassembly/standalone-with-identity/qrcodes-for-authenticator-apps

}
}
}
catch { }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove the catch { } blocks from everywhere we have them? If there's an exception, it's better to let it bubble up for debugging/logging purposes.

Copy link
Collaborator Author

@guardrex guardrex Dec 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this is mirroring your approach ... from the code that you submitted ...

https://github.com/dotnet/blazor-samples/blob/main/9.0/BlazorWebAssemblyStandaloneWithIdentity/BlazorWasmAuth/Identity/CookieAuthenticationStateProvider.cs#L98

If we remove the try-catch, then how can it return a FormResult on an error with Succeeded of false and the error message for the user?

Can we log the exception there and leave the try-catch in place?

UPDATE: I'm having it log the exceptions now.

Copy link
Collaborator Author

@guardrex guardrex Dec 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UPDATE (12/5): I've worked further on a couple of items, and I've updated this comment to reflect the latest bits.

WRT that question ☝️ on the try-catch situation

I left the try-catch statements in place but I'm logging the exceptions now.

There's going to be a instruction in the new article to inject the logger (manually add it to the code). In the future, I'll work dotnet/blazor-samples#401 to have the main sample app inject the logger, and then I'll remove the manual instruction on doing it from the article.

Forgetting the machine on a 2FA disable

I had to add ...

await Acct.TwoFactorRequestAsync(new() { ForgetMachine = true });

... to the Disable2fa method because if it isn't there and 2FA is disabled by the user and re-enabled, it remembers them on the next login attempt and doesn't ask for a 2FA code. By forgetting the machine when 2FA is disabled, it does request a new 2FA/recovery code on the next login after they've re-enabled 2FA. I think that's a better UE.

Give new recovery codes every time 2FA is enabled, not just the first time

If the user disables 2FA and then re-enables it, I would kind'a expect a new set of recovery codes to get generated. Apparently, if they're still on the user's account from the last time that they had them established, the API doesn't generate a new set. I feel that a better UE is if they disable and re-enable that they get a new set of codes. I'll take care of that in the morning. I'm off to 🛌💤 shortly. Done! 👍

Display of the number of remaining recovery codes

I had a world of trouble 😈 trying to get the count to appear in the UI after the user successfully logs in with a recovery code. I've pulled that code out now.

Instead, I have the Manage2fa component display the number of recovery codes left anytime they visit the Manage2fa page after enabling 2FA. Let's roll with that approach.

@guardrex
Copy link
Collaborator Author

Going ahead with this. Of course, I can patch whatever needs more work on a follow-up PR.

@danroth27 ... One open item mentioned here is Halter's remark at #34189 (comment) for ...

Can we get a new go.microsoft.com link that points to this Blazor QR code generation docs? It would be nice to update the Blazor Identity project template and scaffolding to point to this instead. Maybe we could remove <div></div><div data-url="@authenticatorUri"></div> while we're at it.

When that link is generated, it should point to ...

https://learn.microsoft.com/aspnet/core/blazor/security/webassembly/standalone-with-identity/qrcodes-for-authenticator-apps

Second, there was Rick's question on perhaps using a sample app for the code and pulling it in from there at #34189 (comment).

I think it's best for now not to pull from a sample app; however, I'm making a tracking note to discuss it further in 25Q1. The question will be if the WASM+Identity sample app should have account confirmation with PW recovery and this 2FA/TOTP stuff all OOB ... and then, yes, the code can be pulled over into the article from the sample app. Let's take that discussion up next year.

@guardrex guardrex merged commit 9e42cc7 into main Dec 11, 2024
3 checks passed
@guardrex guardrex deleted the guardrex/blazor-wasm-identity-2fa-totp branch December 11, 2024 19:24
@guardrex guardrex mentioned this pull request Jan 3, 2025
75 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add 2FA/TOTP coverage to the Standalone+Identity article+sample
4 participants