Are there any examples or guides on how to implement a frontend MFA extranet login flow?
Currently when we try to log into extranet with a user that has authentication method set to MFA, then we get the error "Login failed for user." on the frontend. We confirmed with Dynamicweb Care that this happens because there is no default MFA login flow for the extranet app on the frontend, and we need to implement that ourselves.
This is on the newest ring 1 release of Dynamicweb 10.22.5.
I am just updating the documentation article - below is the new version that is currently in review so it might contain mistakes.
Users - Authentication
The Users – Authentication app renders a frontend authentication UI and handles sign-in attempts (including MFA/OTP flows and external providers (SSO)). Your job as an implementer is mostly to:
Render the right inputs for the currently active login mode.
Post the expected field names back to the platform.
React to Model.Result to show errors / next-step UI.
Paragraph app settings
From the paragraph app you have the following settings as an editor:
Login template: choose a Razor template from /Templates/Users/UserAuthentication/Login.
Redirect after authentication
Redirect to specific page: choose a fallback page to send users to after a successful login.
Redirect back to referrer: if enabled, a successful login redirects to the page the user came from (when available).
Pages
Page to set password
Page to create user
[!NOTE]
If both Redirect back to referrer is enabled and a valid referrer exists, it wins; otherwise the module falls back to Redirect to specific page.
The old ForgotPasswordLink editor value is obsolete. Use the Page to set password setting instead.
Mental model: one module, several login “tracks”
The module renders a Razor template with a UserAuthenticationViewModel. That model tells you:
Which login type is active (Model.LoginType)
What happened last postback (Model.Result)
Where to send users after successful login (Model.RedirectAfterLogin)
Which extra actions exist (create user / set password links, external providers)
This is created server-side in the module and handed to your template.
Supported login methods (frontend)
Dynamicweb 10’s login pipeline supports these frontend experiences:
Username + password (“classic login”)
OTP (One-time code): username + emailed code (no password)
Magic link: username + emailed link (no password)
MFA: username + password, then a verification step (code or link)
External authentication: e.g. Azure AD / Google etc. (configured providers)
The server decides which one is relevant via the LoginType (globally and/or per user).
Choosing a login method (how the platform decides)
The platform resolves LoginType like this:
It can be set globally (system setting), and
it can be overridden per user (if the user has a specific login type configured)
That resolution is done in the authentication manager and surfaced to the template through Model.LoginType.
Practical guidance
Use Username/Password when you want a familiar login and don’t require step-up security.
Use MFA when you want password + second factor (best for employees, admins, high-risk users).
Use OTP / Magic link when you want “passwordless” (best for B2B buyers who hate passwords, or when you want to reduce credential stuffing risk).
Use External authentication when identity is owned elsewhere (Azure AD, Google Workspace, etc.) or when you want SSO.
The form contract: field names the backend actually listens for
The login middleware looks for specific form keys to decide what to do.
Template location: /Templates/Users/UserAuthentication/Login. Place a .cshtml file in /Templates/Users/UserAuthentication/Login.
The template should inherit ViewModelTemplate<UserAuthenticationViewModel> and import the Dynamicweb.Users.Frontend.UserAuthentication namespace.
Common fields
redirect (hidden): return URL after success (use Model.RedirectAfterLogin)
Autologin (checkbox): “keep me signed in” (persistent cookie)
username
password (only for password-based flows)
shopid (optional; only relevant if you use shop separation)
Special fields that trigger specific flows
Start passwordless (OTP / Magic link) without a password
Include username
Include dologin (any value)
Because the middleware only allows “username without password” when dologin exists.
Verify MFA/OTP code
Include dologin
Include code (the user-entered code)
This calls MFA verification on the backend.
External provider login
Post DwExternalLoginProvider = provider id
This starts the external challenge.
External provider callback
Query string contains DwExtranetExternalLogin=true
The middleware finalizes the external sign-in.
Magic link callback
Query string contains DwExtranetMagicLink=<encrypted code>
The middleware verifies and signs the user in.
Handling outcomes: UserAuthenticationResultType
Your template should treat Model.Result as the canonical “what just happened?” flag.
When the backend replies with MfaVerificationRequired, the module can send the email (code or magic link) and your template switches to the verification form.
@inherits ViewModelTemplate<Dynamicweb.Users.Frontend.UserAuthentication.Email.OneTimeCodeEmailViewModel>
@using Dynamicweb.Users.Frontend.UserAuthentication.Email
<h1>Your one-time code</h1>
<p><strong>@Model.Code</strong></p>
<p>This code is valid for @Model.CodeLifetime seconds.</p>
<p style="color:#666">
Requested from @Model.IP using @Model.Browser on @Model.OperationSystem.
</p>
Model fields are defined here.
Magic link email template example
@inherits ViewModelTemplate<Dynamicweb.Users.Frontend.UserAuthentication.Email.LinkEmailViewModel>
@using Dynamicweb.Users.Frontend.UserAuthentication.Email
<h1>Your sign-in link</h1>
<p><a href="@Model.Link">Click here to sign in</a></p>
<p style="color:#666">
This link expires at @Model.Expiration.
</p>
Model fields are defined here.
Security + UX details you should actually care about
Code lifetime + max attempts
Code lifetime is controlled by /Globalsettings/Modules/Extranet/LogOn/CodeLifetimeInSeconds (minimum 10s, default 120s).
Show an “expires at” hint using Model.MfaCodeExpiration when present.
When MfaVerificationFailed, show a clear message and let users restart the flow.
Redirect handling
You don’t need to reverse-engineer redirect rules. Just post redirect=@Model.RedirectAfterLogin back as a hidden field.
The platform will:
Prefer the redirect value if valid and safe,
Otherwise try user/group start page,
Otherwise referrer,
Otherwise /.
Accessibility basics
Use real <label for=""> bindings.
Use autocomplete="username" / "current-password" / "one-time-code" where relevant.
Put the error message near the submit button (people scan there).
Updating an existing Login.cshtml (what to change)
If you have an existing template like the current one (username/password + external providers), you mainly need to add:
A branch that detects Model.Result == MfaVerificationRequired (and failed) and renders the code-entry form.
A passwordless “send code” mode (username + hidden dologin) when Model.LoginType is passwordless.
Add handling for MfaVerificationFailed in your result messages.
Your current template is a good starting point; it already renders results and external providers, but it always requires a password and never shows the verification step.