Skip to main content

DNN · Widget embed

The widget is loaded from the CDN as a single <script> with data-token pointing at the Widget Token (24 h) issued by Neuroon. The DNN-specific piece is:

  1. Issuing the token server-to-server from DNN, with in-memory cache.
  2. Rendering the <script> in an .ascx (Skin or SkinObject) printing the token from code-behind.
  3. Pinned SRI to ensure the integrity of the widget binary.

The token is NOT signed by the client

The Widget Token is opaque and issued by the Neuroon backend. Your integration does not sign anything; you request the token with your Shop API Key and inject it into data-token. See Authentication · Widget Token.

Endpoint used:

POST/api/shops/{id}/widget-token

Server-side cache (24 h with margin)

DNN should not hit the /widget-token endpoint on every request. The standard pattern:

public static class WidgetTokenProvider
{
private static readonly object Lock = new();
private static string _token;
private static DateTime _expires;

public static string Get()
{
lock (Lock)
{
if (_token != null && DateTime.UtcNow < _expires.AddMinutes(-5))
return _token;

var fresh = new NeuroonClient().IssueWidgetTokenAsync().GetAwaiter().GetResult();
_token = fresh;
_expires = DateTime.UtcNow.AddHours(24);
return _token;
}
}
}

Notes:

  • 5 minute margin before expiry to avoid 401 on edge requests.
  • In multi-AppPool / web farm environments, replace the static with a distributed cache (Redis, OutputCache).
  • On .NET 8, prefer SemaphoreSlim or IMemoryCache with Lazy<Task<string>>.

Render in .ascx

Minimal Skin / SkinObject. Place a placeholder where you want the search bar:

<%@ Control Language="C#" AutoEventWireup="true" %>
<%@ Register TagPrefix="dnn" Namespace="DotNetNuke.UI.Skins.Controls" Assembly="DotNetNuke" %>
<asp:PlaceHolder runat="server">
<div id="neuroon-search"></div>
<script
src="https://cdn.neuroon.ai/widget@0.9.10/widget.js"
integrity="sha384-JTaG/IN0Jj/ImfUj2x5QVMG4HkbFHzui7fTpLtwl1hsP+kY9W8OODeSJRFWN1ZP5"
data-token='<%= WidgetTokenProvider.Get() %>'
data-container="#neuroon-search"
data-theme="auto"
data-locale='<%= System.Threading.Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName %>'
data-api-url='<%= NeuroonSettings.ApiUrl %>'
crossorigin="anonymous"
async></script>
</asp:PlaceHolder>

The SRI hash and widget version come from the official manifest: https://cdn.neuroon.ai/widget@VERSION/sri-manifest.json. Version 0.9.10 and its hash come from the WordPress plugin .

CSP (Content Security Policy)

If your Skin enforces a strict CSP, add the minimum hosts:

Content-Security-Policy:
script-src 'self' https://cdn.neuroon.ai;
connect-src 'self' https://api.neuroon.ai https://dev-api.neuroon.ai;
img-src 'self' data: https:;
style-src 'self' 'unsafe-inline';

For Production-only, drop dev-api.neuroon.ai. The widget does not require unsafe-eval.

Locale

data-locale accepts any ISO 639-1 code (es, en, de, etc.). In DNN, the natural source is:

data-locale='<%= System.Threading.Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName %>'

If your site is multi-portal, use PortalSettings.Current.PortalAlias.CultureCode. Read also Widget · i18n for the client-side fallback behavior.

Verify the embed

  1. Open the public page with DevTools.
  2. Network → Filter widget: you should see a request to https://cdn.neuroon.ai/widget@0.9.10/widget.js with status 200 and header X-Content-Type-Options: nosniff.
  3. Console: no Failed to find a valid digest in the 'integrity' attribute. If it appears, the SRI or version do not match.
  4. Application → Cookies: the widget should not set tracking cookies in this domain.

Conditional embed

To render the widget only on certain pages / portals:

protected void Page_PreRender(object sender, EventArgs e)
{
var portal = PortalSettings.Current;
if (portal != null && portal.HomeTabId == this.TabId)
{
widgetPlaceholder.Visible = true;
}
}

Next steps