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
umbHttpClientfromfirstUpdated()sent the user to the sign-in page in a loop. - Calling it with plain
fetchreturned a401 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
- 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. - Use manifest files. Move the section, section view and entry point out of
umbraco-package.jsoninto propermanifest.tsfiles, bundle them, and reference only the bundle in the package file. - Don't mix two clients. A common source of confusion is using a generated API client and
umbHttpClienttogether. Pick one, configure that one in the entry point, and use it everywhere. - Don't leave
[AllowAnonymous]in. It is fine as a quick test to prove the route works, then remove it.