← All articles
3 Oct 2026 2 min read Umbraco Backoffice Management API Solved

Calling your own API from a custom Umbraco backoffice section

A custom section that keeps sending users back to the login screen? Here is why it happens and how a backoffice entry point fixes it.

BT
Bishal Tim ยท Umbraco MVP, AI & .NET Architect

You build a custom backoffice section with your own API controller. Every time the section loads, Umbraco throws you back to the login screen with "Your session has timed out". You sign in again, get one second of peace, and it happens again.

This is a common trap for extension authors. Here is why it happens and the fix.

What was going on

The controller was fine. It showed up in Swagger, so the server side was working. The trouble was on the client:

  • Calling it with umbHttpClient from firstUpdated() sent the user to the sign-in page in a loop.
  • Calling it with plain fetch returned a 401 Unauthorized.
  • Adding [AllowAnonymous] to the controller made it work, which is a clear sign the problem was authentication, not routing. It is also not a fix you should ship, because anyone could then call the endpoint.

In other words, the request was leaving the browser without the backoffice user's access token, so the server treated it as anonymous.

The fix: an entry point that sets up the client

The fix is to give your package a backoffice entry point. Inside it you can get the authentication context, read the OpenAPI configuration Umbraco already holds for the logged-in user, and give your API client what it needs.

import type { UmbEntryPointOnInit } from '@umbraco-cms/backoffice/extension-api';
import { UMB_AUTH_CONTEXT } from '@umbraco-cms/backoffice/auth';
import { client } from '../api';

export const onInit: UmbEntryPointOnInit = (host) => {
  host.consumeContext(UMB_AUTH_CONTEXT, (authContext) => {
    if (!authContext) return;

    const config = authContext.getOpenApiConfiguration();
    client.setConfig({ baseUrl: config.base, credentials: config.credentials });

    // Add the token to every request. Tokens expire and get refreshed,
    // so setting it once is not enough.
    client.interceptors.request.use(async (request) => {
      const token = await config.token();
      request.headers.set('Authorization', `Bearer ${token}`);
      return request;
    });
  });
};

The important detail is the interceptor. It asks for a fresh token on every request, so your calls keep working after the backoffice refreshes the user's session.

Things to watch out for

  1. Start from the extension template. The umbraco-extension .NET template sets up an entry point and an example API controller for you, which is the easiest way to get the wiring right.
  2. Use manifest files. Move the section, section view and entry point out of umbraco-package.json into proper manifest.ts files, bundle them, and reference only the bundle in the package file.
  3. Don't mix two clients. A common source of confusion is using a generated API client and umbHttpClient together. Pick one, configure that one in the entry point, and use it everywhere.
  4. Don't leave [AllowAnonymous] in. It is fine as a quick test to prove the route works, then remove it.
BT
Bishal Tim

Umbraco MVP from Kathmandu. I write about Umbraco, AI and .NET, and help people get started.

More on Skrift Get in touch