Skip to main content

Custom Login Templates for Out-of-band QR Codes and Links

The out-of-band patterns show a QR code in the browser and, on mobile devices, a link that opens the Nevis Access App directly. The default login templates of the authentication realm contain everything that is needed for this. This page explains what the templates, the JavaScript files and the labels must contain if you customize them, and what to check when you upgrade.

You only need this page if you use one of the following:

  • Template Customization in your authentication realm, with your own form.vm, js_end.vm or mauth_*.js files.
  • Custom Translations that override the mobile_auth.link label.
  • Your own frontend instead of nevisLogrend, calling the OutOfBandFidoUafAuthState directly.
Prefer no template override

Most branding needs are covered by the Template Style, logo, color and font settings of the authentication realm, and by Custom Translations. Every file you override is a file you have to merge again on each upgrade. If you leave Template Customization empty, there is nothing to do on this page.

The Link QR Code Dispatcher (link-png-qr-code) creates a link that contains the dispatch token and the redeem URL. It returns this link together with a QR code of the same link, rendered by nevisFIDO as a PNG image. The browser does not have to assemble or draw the QR code.

The response attribute dispatcherInformation.response is a JSON object with two attributes:

{
"dispatchResult": "dispatched",
"sessionId": "69519021-13d6-46c1-83f5-9016d7b05aa6",
"dispatcherInformation": {
"name": "link-png-qr-code",
"response": {
"link": "https://login.example.com?dispatchTokenResponse=eyJ...",
"linkQrCode": "iVBORw0KGgoAAAANSUhEUgAA..."
}
}
}
  • linkQrCode is the base64-encoded PNG. Render it as <img src="data:image/png;base64,...">.
  • link is shown as a tappable link when the browser runs on the phone itself. Whether tapping it opens the app or a web page depends on the link type, see Mobile-Only Operations with Deep Links and Custom URIs.

The legacy link dispatcher returns the link as a plain string instead, and the QR code has to be drawn in the browser.

GUI names and JavaScript files​

Each pattern and channel renders its own GUI and loads its own JavaScript file. Your template must handle the GUI names of the channels you use.

PatternChannelDispatcherGUI nameJavaScript file
Out-of-band Mobile OnboardingLink / QR-Codelink-png-qr-codemauth_onboard_png_qr_linkmauth_onboard_png_qr_link.js
Out-of-band Mobile OnboardingLink / QR-Code (legacy)linkmauth_onboardmauth_onboard.js and qrious.min.js
Out-of-band Mobile AuthenticationLink / QR-Codelink-png-qr-codemauth_png_qr_linkmauth_png_qr_link.js
Out-of-band Mobile AuthenticationLink / QR-Code (legacy)linkmauth_link_qrmauth_link_qr.js and qrious.min.js
Out-of-band Mobile AuthenticationPush / QR-Codefirebase-cloud-messagingmauth_push_qrmauth_push_qr.js and qrious.min.js
Usernameless Out-of-band Mobile Authentication-link-png-qr-codemauth_usernamelessmauth_usernameless.js

The Push / QR-Code channel does not show a tappable link.

Before version 9.2611 (8.2611 for LTS 2026), Usernameless Out-of-band Mobile Authentication used the link dispatcher and drew the QR code in the browser with qrious.min.js.

Onboarding: the QR code is part of the page​

For onboarding, nevisAuth calls nevisFIDO before the page is rendered:

  1. nevisAuth requests a registration dispatch token with the link-png-qr-code dispatcher.
  2. The GUI mauth_onboard_png_qr_link contains the QR code and the link as hidden fields mauth_qr_png and mauth_dispatcher_link. The default template renders them with #renderGeneric($gui).
  3. mauth_onboard_png_qr_link.js reads the hidden fields, replaces the mauth_qrcode placeholder with the image, sets the link on mobile devices and shows the mauth_started container.
  4. The script polls nevisAuth every 2 seconds. While the app is registering, it hides the QR code and shows a spinner. When the registration has succeeded or failed, it submits the form and the flow continues.

Authentication: the page fetches the QR code​

For authentication, the page requests the QR code after it has loaded:

  1. mauth_png_qr_link.js sends an empty JSON object with POST to the current URL.
  2. The OutOfBandFidoUafAuthState requests an authentication dispatch token with the link-png-qr-code dispatcher and returns the nevisFIDO response.
  3. The script renders linkQrCode, sets link on mobile devices and polls nevisAuth with the sessionId of the response.
  4. When the authentication has succeeded or failed, the script submits the form and the flow continues.

Usernameless Out-of-band Mobile Authentication works the same way with mauth_usernameless.js and the GUI mauth_usernameless. Because no user is known, the dispatch request contains no dispatch target.

Step 1: Start from the default template of your version​

Download the default template of the Template Style you use from the authentication realm of the pattern version you deploy. Keep an unmodified copy next to your customized copy, for example in version control. On the next upgrade you can then compare the old and the new default and merge only what changed.

Step 2: Keep the out-of-band block in form.vm​

The JavaScript files find the elements by ID. Keep all IDs, including the canvas placeholder that the script replaces with the image. This is the block of the modern template:

#if ($gui.name == "mauth_onboard_png_qr_link" || $gui.name == "mauth_png_qr_link")
<center id="mauth_started" style="display: none">
<img id="mauth_loading" style="width: 60px; height: 60px; display: none;" src="${resource:loading.svg}"/>
<p id="mauth_qrcode_info">$text.get("mobile_auth.scan")</p>
<canvas id="mauth_qrcode" style="width: 256px; height: 256px;"></canvas>
<div id="mauth_link_parent" class="form-group" style="display: none">
$text.get("mobile_auth.link")
</div>
</center>
#end

The default template has a separate block with the same elements for mauth_usernameless.

ElementUsed for
mauth_startedContainer, shown when the QR code is ready.
mauth_qrcodePlaceholder, replaced by an img element with the QR code.
mauth_qrcode_infoInstruction text, hidden while the app processes the operation.
mauth_loadingSpinner, shown while the app processes the operation.
mauth_link_parentContainer of the link, shown on mobile devices only.
mauth_linkThe tappable link. It is part of the mobile_auth.link label, not of the template, see step 4.
mauth_qr_png, mauth_dispatcher_linkHidden fields of the onboarding GUI. Keep #renderGeneric($gui) in the form, or render these two fields yourself. Without them, the onboarding page has no QR code to show.

Render the mobile_auth.link label directly inside mauth_link_parent. Do not wrap it in another <a id="mauth_link"> element, see Things to be aware of.

Step 3: Keep the script includes in js_end.vm​

#if ($gui.name == "mauth_onboard_png_qr_link")
<script src="${resource:mauth_onboard_png_qr_link.js}"></script>
#end
#if ($gui.name == "mauth_png_qr_link")
<script src="${resource:mauth_png_qr_link.js}"></script>
#end
#if ($gui.name == "mauth_usernameless")
<script src="${resource:mauth_usernameless.js}"></script>
#end

These GUIs do not need qrious.min.js. If you also override the JavaScript files, take them from the same version as the templates.

The patterns ship the mobile_auth.link label with the link element inside the text, so that each language can place the link on the right words. The onboarding pattern selects the text according to the link type. If you override the label, keep exactly one <a id="mauth_link"> element and do not set an href. The script sets it.

mobile_auth.link=Tap <a id="mauth_link">here</a> to install or open the app.

Step 5: Test on desktop and mobile​

CaseExpected result
Onboarding in a desktop browserThe QR code is shown and no link. Scanning it with the phone completes the onboarding.
Authentication in a desktop browserThe QR code is shown. Scanning it completes the login.
Onboarding in a mobile browserThe QR code and the link are shown. Tapping the link opens the app, or the installation page if the app is missing and you use deep links.
Authentication in a mobile browserTapping the link opens the app and the flow completes.
Browser consoleNo errors, and no warning about #mauth_link.
Fresh user without the appThe complete onboarding works from start to end.

You can simulate a mobile browser with the device emulation of the browser developer tools. The scripts decide based on the user agent.

Using your own frontend​

If your own frontend talks to the OutOfBandFidoUafAuthState instead of nevisLogrend, configure the AuthState with link-png-qr-code as dispatcher, fidoUafUsername as ${inargs:o.username.v}, and dispatchTargetId as ${inargs:o.dispatchTargetId.v}. Send the username and dispatch target ID in the request, and handle the object response:

const res = await fetch(authUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, dispatchTargetId })
});
const o = await res.json();
if (o.dispatchResult === "dispatched") {
const { linkQrCode, link } = o.dispatcherInformation.response; // an object, not a string
qrImage.src = "data:image/png;base64," + linkQrCode;
const anchor = document.getElementById("mauth_link");
if (anchor) {
anchor.href = link;
}
poll(o.sessionId); // POST { "fidoUafSessionId": sessionId } until the status is final
}

See OutOfBandFidoUafAuthState for the request and status formats.

Things to be aware of​

Overrides do not follow upgrades

Files you upload with Template Customization and labels you override with Custom Translations are not updated when you upgrade the patterns, and nevisAdmin 4 does not warn you when the default you override has changed. Compare your overrides with the new defaults on every upgrade.

  • The link element is part of the label, not of the template. If a custom form.vm wraps the label in another <a id="mauth_link"> element, the page contains nested links. Browsers split nested links into separate elements, so depending on the version of the scripts the visible "here" may not be clickable. Render the label without a wrapper.
  • A label without the link element removes the mobile link. The scripts log a warning in the browser console and continue with the QR code. Older versions of some scripts stop before polling starts, so the page never continues. Always keep the <a id="mauth_link"> element in overridden labels.
  • Custom templates must handle every GUI name you use. If you switch the channel, for example from Link / QR-Code (legacy) to Link / QR-Code, the pattern renders a different GUI and loads a different script. A template that only contains the old block shows no QR code.
  • Encryption needs a dispatch target. The link is only encrypted for a device when the dispatch request contains a dispatch target. Onboarding and usernameless authentication have no dispatch target, so their link is not encrypted.
  • Choose the link type for the use case. A custom URI does nothing if the app is not installed. A deep link falls back to a web page that can point to the app stores. During onboarding the app is often not installed yet, so use deep links. See Choosing a link type.
  • The link is shown based on the user agent. The page cannot detect whether the app is installed.
  • Clear caches after changing resources. After replacing templates or JavaScript files, clear the caches of CDNs, reverse proxies and browsers before you test. Otherwise you may test the previous version of a file.

Upgrade checklist​

  1. List all files in Template Customization and all overridden labels of your authentication realms.
  2. Download the new default template and compare it with the previous default, not with your customized copy.
  3. Merge the changes of the mauth blocks in form.vm and js_end.vm, and of the mauth_*.js files, into your customized copy. When upgrading from a version older than 9.2611 (8.2611 for LTS 2026), replace customized copies of mauth_usernameless.js: the old script expects a plain link and stops with an error when it receives the response of the link-png-qr-code dispatcher.
  4. Check that overridden mobile_auth.link labels still contain exactly one <a id="mauth_link"> element.
  5. Clear caches and run the tests of step 5, including a complete onboarding of a new user.
  6. Remove overrides that you no longer need.