feat: Add MFA challenge UI pages and demo support#60
feat: Add MFA challenge UI pages and demo support#60devondragon wants to merge 2 commits intomainfrom
Conversation
Add WebAuthn challenge page for MFA second-factor verification, MFA status display on the user profile page, and Playwright tests. Companion to SpringUserFramework#268 / PR #272. - Add user.mfa config block to application.yml (PASSWORD + WEBAUTHN) - Create /user/mfa/webauthn-challenge.html template and JS module - Add controller route in PageController for the challenge page - Extend auth-methods card on profile page with MFA status badges - Add Playwright tests for challenge page structure and MFA status endpoint - Disable MFA in playwright-test profile to keep existing tests unaffected - Bump ds-spring-user-framework to 4.2.2-SNAPSHOT Closes #59
There was a problem hiding this comment.
Pull request overview
Adds demo-app UI support for Spring Security MFA (password + WebAuthn), including a dedicated WebAuthn challenge page, profile-page MFA status badges, and Playwright coverage to validate the new UI/endpoint wiring.
Changes:
- Added a new
/user/mfa/webauthn-challenge.htmlThymeleaf page plus module JS to perform WebAuthn assertion and redirect. - Extended the profile “Authentication Methods” card to render MFA status badges sourced from
GET /user/mfa/status. - Added MFA configuration defaults and Playwright tests; bumped
ds-spring-user-frameworkto4.2.2-SNAPSHOT.
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
build.gradle |
Bumps demo dependency to framework snapshot that includes MFA support. |
src/main/resources/application.yml |
Enables MFA and configures required factors + entry-point URIs. |
src/main/resources/application-playwright-test.yml |
Disables MFA for Playwright profile runs. |
src/main/java/com/digitalsanctuary/spring/demo/controller/PageController.java |
Adds route to render the MFA WebAuthn challenge page. |
src/main/resources/templates/user/mfa/webauthn-challenge.html |
New challenge page template shown when WebAuthn factor is missing. |
src/main/resources/static/js/user/mfa-webauthn-challenge.js |
New challenge page logic that calls authenticateWithPasskey() and redirects. |
src/main/resources/templates/user/update-user.html |
Adds an MFA status container section for JS-populated badges. |
src/main/resources/static/js/user/webauthn-manage.js |
Fetches MFA status and renders badges in the auth-methods card. |
playwright/tests/mfa/mfa-challenge.spec.ts |
New E2E checks for challenge page structure and MFA status endpoint behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (!response.ok) { | ||
| container.classList.add('d-none'); | ||
| return; | ||
| } |
There was a problem hiding this comment.
updateMfaStatusUI() currently hides the MFA section on any non-OK response, which can mask real server-side errors (500s) and make debugging harder. Consider only hiding on 404 (MFA disabled) and treating other statuses as an error state (log + optional user-visible message).
| // With MFA disabled in playwright-test profile, expect 404 | ||
| // With MFA enabled, expect 200 with proper response shape | ||
| expect([200, 404]).toContain(response.status()); | ||
|
|
||
| if (response.status() === 200) { | ||
| const body = await response.json(); | ||
| expect(typeof body.mfaEnabled).toBe('boolean'); | ||
| expect(typeof body.fullyAuthenticated).toBe('boolean'); | ||
| } |
There was a problem hiding this comment.
This test is non-deterministic for the playwright-test profile: application-playwright-test.yml sets user.mfa.enabled: false, so /user/mfa/status should consistently return 404. Allowing both 200 and 404 weakens the assertion and can hide regressions; consider asserting 404 here and adding a separate test/project/profile where MFA is enabled to assert the 200 + JSON shape case.
| // With MFA disabled in playwright-test profile, expect 404 | |
| // With MFA enabled, expect 200 with proper response shape | |
| expect([200, 404]).toContain(response.status()); | |
| if (response.status() === 200) { | |
| const body = await response.json(); | |
| expect(typeof body.mfaEnabled).toBe('boolean'); | |
| expect(typeof body.fullyAuthenticated).toBe('boolean'); | |
| } | |
| // With MFA disabled in playwright-test profile, this endpoint should return 404 | |
| expect(response.status()).toBe(404); | |
| // NOTE: When adding a profile/project with MFA enabled, create a separate test | |
| // that asserts a 200 response and validates the JSON response shape. | |
| // This keeps the playwright-test profile deterministic. |
| /** | ||
| * Update the MFA Status section in the auth-methods card. | ||
| * Silently hides the container if the MFA status endpoint returns 404 (MFA disabled). | ||
| */ |
There was a problem hiding this comment.
The JSDoc says the MFA status container is hidden only when the endpoint returns 404, but the current implementation hides it for any non-2xx response. Either update the comment to match the behavior, or change the logic to check response.status === 404 specifically and handle other error statuses differently (e.g., log/show a message).
- Check for 404 specifically in updateMfaStatusUI() instead of hiding on any non-OK response; log a warning for other error statuses - Update JSDoc to document both 404 and non-OK handling behaviors - Make MFA status test assertion deterministic (expect 404 when MFA is disabled in playwright-test profile, not [200, 404])
Summary
Companion to SpringUserFramework#268 / PR #272. Closes #59.
/user/mfa/webauthn-challenge.html) — shown when a partially-authenticated user needs to verify with their passkey after password loginGET /user/mfa/statususer.mfablock toapplication.ymlwithPASSWORD + WEBAUTHNfactorsds-spring-user-framework4.2.1 → 4.2.2-SNAPSHOTFiles changed
build.gradleapplication.ymluser.mfaconfig blockapplication-playwright-test.ymluser.mfa.enabled: falsePageController.javatemplates/user/mfa/webauthn-challenge.htmlstatic/js/user/mfa-webauthn-challenge.jsauthenticateWithPasskey()templates/user/update-user.htmlstatic/js/user/webauthn-manage.jsupdateMfaStatusUI()+createBadge()playwright/tests/mfa/mfa-challenge.spec.tsHow MFA works
PASSWORDfactorWEBAUTHNfactor is missing → redirects to challenge pageTest plan
cd SpringUserFramework && ./gradlew publishLocal(version 4.2.2-SNAPSHOT)./gradlew bootRun --args='--spring.profiles.active=local'— login with password, verify redirect to challenge page, complete passkey verification./gradlew test— verify no regressionscurl http://localhost:8080/user/mfa/status→ verify JSON response shape