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.
I see that this app only exists for Swift solutions, would there be a way to have this be available for other non-Swift solutions on the newest DW10 release? We have an older website which was upgraded from Dynamicweb 9 that uses a custom solution (no Swift) and want to be able to also get the benefits of users being able to log in on the newest release without having to convert to Swift.
The functionality id DW10, the implementation is Swift.
I don't think there is an easy way to have a "generic" implementation.
You will have to customize the Swift templates to match your own custom implementation.
If you don't have the resources or knowledge to do it, I would be happy to help. I have a team of experienced developers who can surely take care of this customization.
How would I go about adding a new app similar to Swift for User Authentication if I add the template from Swift 1 inside our solution at Users/UserAuthentication/Login.cshtml and started customising it for our solution? The main problem I have now is adding custom apps similar to how Swift does it, as I can't find any documentation on it.
If I can do that, then hopefully I should be able to do it on my own.
Alright that is very helpful. This helped me get the frontend step for MFA login flow implemented now, however the email for the second step verification (MFA) to the frontend login is never sent out.
I checked EmailHandler logs and I have ensured our SMTP settings are correctly configured and read through Nicholai's documentation and saw that he wrote "/Templates/Users/UserAuthentication/Email (email templates)" so I added my own OneTimeCode.cshtml, but there is no app settings to choose the email template and it doesn't seem to send a default one out either.
OneTimeCodeTemplate is the setting i am trying to find which is from Nicholai's text. The email for MFA login types to backend seem to work fine too and have a default Dynamicweb email template. I've been checking through the log files for a while and cannot see any errors or email logs being posted when I try to receive a one time code by email from the frontend.
That is very good to know. I can send out emails just fine now however the code and code life time within the template is not available and I keep getting this error now while using your default one time code email template: An error occurred while attaching module 'UserAuthentication' (Dynamicweb.Frontend.Content).
I tried sending out a bare bones template with TemplateTags() which only returned BaseUrl and another Url as values (missing code and code life time values).
The template I used:
@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>
This worked and now I got a working MFA login flow with emails configured correctly.
Now we have a new problem related to the new Users - Authentication app and permissions. I configured the following permissions on a reports page:
When I go to the page “Reports” we get redirected to the login page due to missing permissions (which is correct). I enabled Redirect back to referrer on the new Users - Authentication app, which then redirects us back to reports once we logged into the frontend. In this case I logged in as a user who should not have access to the reports page. When I get redirected back to the reports page after successfully logging in, it immediately redirected me back to login page again which again is correct.
If I manually insert the URL to the reports page, then I can bypass the DW permissions and still get to see and use the page which was meant to be locked to that 1 user even though I am logged out of the backend and only logged into the frontend using a new user with no permissions.
I am pretty sure permissions work - but we would have to take a look at the setup and see this. Can you either send information to care or I can have them contact you to get the information.