Create Custom DashBoard

Hi,

I am upgrading a custom dashboard I built for Umbraco 13 to 17, and I have hit a problem, I seem to have the controller working but when I call it I get 401.

[ApiController]
//[Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)]
public class ScheduledContentController :Controller {

[Route("api/ScheduledContentDash/")]
[Authorize(Roles = "admin")]
[HttpGet]
public JsonResult getContent()
{

}

}

When I try and call it using

function loadData() {
fetch(‘/api/ScheduledContentDash/’).then(function (response) {
// The API call was successful!
console.log(‘success!’, response);
console.log(‘Jason Return!’,response.json());

}).catch(function (err) {
    // There was an error
    console.warn('Something went wrong.', err);
});

}

I have worked out I need the Bear token, but I can’t work out how to get it in Vanilla Javascript.

Can someone point me in the correct direction please.

Hey! So I’ve been going back and forth on this :grinning_face_with_smiling_eyes:i was actually workong on a coustom dashboard for our client here are two approaches that actually work in v17, pick whichever fits your setup better., maybe1,

Option 1 Plain ControllerBase with backoffice route (simpler)

this is the cleanest approach. No bearer token needed, the backoffice session cookie handles auth:

[ApiController]
[Route("umbraco/backoffice/api/v1/[controller]")]
public class ScheduledContentController : ControllerBase
{
    [HttpGet("getcontent")]
    public IActionResult GetContent()
    {
        return Ok(new { message = "works" });
    }
}

js

async function loadData() {
    const response = await fetch('/umbraco/backoffice/api/v1/scheduledcontent/getcontent', {
        method: 'GET',
        credentials: 'same-origin',
        headers: {
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        }
    });

    if (!response.ok) throw new Error(`HTTP error! ${response.status}`);
    const data = await response.json();
    console.log(data);
}

The key thing here is the route prefix /umbraco/backoffice/api/v1/ — your current /api/ prefix is likely why you’re getting the 401.


Option 2 UmbControllerBase with proper auth context (TypeScript), maybe not but still good to include

If you’re open to TypeScript and the Umbraco component system, this is the more “correct” v17 way uses UMB_AUTH_CONTEXT to get the bearer token cleanly:

ts

import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
import { UMB_AUTH_CONTEXT } from '@umbraco-cms/backoffice/auth';

export class ScheduledContentRepository extends UmbControllerBase {

    private async getAuthHeaders(): Promise<HeadersInit> {
        const authContext = await this.getContext(UMB_AUTH_CONTEXT);
        const token = await authContext?.getLatestToken();
        return {
            'Content-Type': 'application/json',
            ...(token ? { 'Authorization': `Bearer ${token}` } : {}),
        };
    }

    async getContent() {
        const headers = await this.getAuthHeaders();
        const response = await fetch('/umbraco/backoffice/api/v1/scheduledcontent/getcontent', {
            headers,
            credentials: 'include',
        });
        if (!response.ok) return null;
        return await response.json();
    }
}

Full working example of Option 2 in our TagManager package if it helps: Umbraco-Tag-Manager/TagManager/Client/src/api/tagmanager-repository.ts at main · ZAAKS/Umbraco-Tag-Manager · GitHub

For dashboards in the backoffice (or any endpoint that is meant only for the backoffice) you should really use a Management API controller. This will handle authentication for you.

Here a solution that seems to work based it on the above.

export default class MyDashboardElement extends UmbElementMixin(HTMLElement) {
    #notificationContext;
    #workspaceContext;
    #authContext;

    constructor() {
        super();

        console.log("Working.......");

        this.consumeContext(UMB_AUTH_CONTEXT, (_auth) => {
            console.log("Test Cont!!");
            if (_auth) {
                console.log("I've got the document workspace context: ", _auth);
                loadData(_auth);
            } else {
                console.log("The document workspace context is gone, I will make sure my code disassembles properly.")
            }
        });
        
        
        /*
        this.attachShadow({ mode: "open" });
        this.shadowRoot.appendChild(template.content.cloneNode(true));
        
        this.shadowRoot
            .getElementById("clickMe")
            .addEventListener("click", this.onClick.bind(this));
        
        this.consumeContext(UMB_NOTIFICATION_CONTEXT, (instance) => {
            this.#notificationContext = instance;
        });
        
        console.log(this);
        */
    }

    /*
    onClick = () => {
        this.#notificationContext?.peek("positive", {
            data: { headline: "Hello" },
        });
    };
    */
}


async function loadData(_auth) {
    console.log("Getting Header!");
    const TOKEN = await _auth?.getLatestToken();
    console.log("Header Got!!");
    const response = await fetch('/umbraco/management/api/v1/my/item', {
        method: 'GET',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': 'Bearer ' + TOKEN,
            'credentials': 'include'
        }
    });

    if (!response.ok) throw new Error(`HTTP error! ${response.status}`);
    const data = await response.json();
    console.log(data);
}

customElements.define("scheduled-extension", MyDashboardElement);


    [VersionedApiBackOfficeRoute("my/item")]
    [ApiExplorerSettings(GroupName = "My item API")]
    [MapToApi("my-item-api")]
    public class ScheduledContentController : ManagementApiControllerBase
{
.......
}

Hope it help some one else.

I recommend installing the Umbraco Extension dotnet template. This template ships with examples on dashboards using custom API controllers, including how to hook onto the Backoffice auth mechanisms:

For any other controllers, or if you want to roll your own fetch client, you need to configure it to use cookies and the bearer token. For that, you can look here:

TLDR: This function can be used to make a request using the Fetch API:

import { UMB_AUTH_CONTEXT } from '@umbraco-cms/backoffice/auth';

/**
 * Make an authorized request to any Backoffice API.
 * @param host A reference to the host element that can request a context.
 * @param url The URL to request.
 * @param method The HTTP method to use.
 * @param body The body to send with the request (if any).
 * @returns The response from the request as JSON.
 */
async function makeRequest(host, url, method = 'GET', body) {
  const authContext = await host.getContext(UMB_AUTH_CONTEXT);
  const token = await authContext.getLatestToken();
  const response = await fetch(url, {
    method,
    body: body ? JSON.stringify(body) : undefined,
    include: 'credentials',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`,
    },
  });
  return response.json();
}
1 Like