Skip to content
AXOWLDevelopers
Quickstart

.NET EndUser auth

A Blazor app signs end users in through Axowl’s hosted login page. Your app never sees a password: it hands the user to the hosted page, and gets back a signed JWT it can send to your own API.

This is the end-user plane. For your server verifying those tokens see Reference → Authentication; for permission checks see SDK → Identity.

WhereWhat
Blazor WebAssembly (browser)The hosted login round trip — two HTTP steps you can make from C#, or through the framework-free npm package @axowl/sdk-core (0.3.3 or later) loaded as a JS interop module.
Your API (server)Axowl.Sdk.Identity.Client — validates the JWT and enforces [RequirePermission].
@inject NavigationManager Nav
@code {
void SignIn()
{
var returnTo = Nav.BaseUri; // must be a registered Callback URL
Nav.NavigateTo(
$"https://login.axowl.com/login/{clientKey}?redirect_url={Uri.EscapeDataString(returnTo)}",
forceLoad: true);
}
}

clientKey is your application’s client key (client_…, from Applications in the dashboard).

Sign-in is a redirect: the user leaves for the hosted page and comes back with a one-time code in the URL (?code=…). Exchange it once on app start — typically in your layout’s OnInitializedAsync — or sign-in never finishes.

@using System.Net.Http.Json
@inject NavigationManager Nav
@inject HttpClient Http
@code {
protected override async Task OnInitializedAsync()
{
var uri = new Uri(Nav.Uri);
var code = System.Web.HttpUtility.ParseQueryString(uri.Query)["code"];
if (string.IsNullOrEmpty(code)) return;
var res = await Http.PostAsJsonAsync(
$"https://api.axowl.com/api/public/apps/{clientKey}/auth/session/exchange", new { code });
if (res.IsSuccessStatusCode)
{
var body = await res.Content.ReadFromJsonAsync<ExchangeResponse>();
// keep body!.AccessToken for this session and send it to your API as a Bearer token
}
Nav.NavigateTo(uri.GetLeftPart(UriPartial.Path), replace: true); // drop the used code from the address bar
}
record ExchangeResponse(string AccessToken);
}

POST /api/public/apps/{clientKey}/auth/session/exchange with { "code": "…" } answers { "accessToken": "<end-user JWT>" }. An empty code answers 400; a used or expired code answers 401.

With @axowl/sdk-core the same step is await client.exchangeSessionCode(code), which also stores the token — see JavaScript core client.

For a page that an anonymous visitor has no business seeing, redirect to the hosted page when there is no token — and do it at most once per browser session. If sign-in cannot complete — the user cancelled, or the return URL isn’t an allowed Callback URL — redirecting again traps them in a loop with nothing on screen. Keep a marker (for example in sessionStorage) that a redirect was already attempted, and show a “sign-in required” message instead of redirecting a second time.

request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

The token is an RS256 JWT with an exp claim. Do not send an expired token — clear it and sign in again. On your API, Axowl.Sdk.Identity.Client rejects an expired or invalid token with 401.

Discarding the stored JWT signs the user out of your app only. To end the session at Axowl as well, call the logout endpoint with the token before you discard it:

POST https://api.axowl.com/api/public/apps/{clientKey}/auth/logout
Authorization: Bearer <end-user JWT>
Content-Type: application/json
{ "returnUrl": "https://yourapp.com/" }

returnUrl is optional. It must be listed under Applications → your app → Authentication → post-logout return URLs; the answer then carries redirectUrl for you to navigate to. A URL that is not listed answers 400 RETURN_URL_NOT_ALLOWED and the session is not revoked. With @axowl/sdk-core this is await client.logout({ returnUrl }).

“Log out” means four different things in this system.

CallWhat it endsYours?
Discarding the stored JWTYour app’s local session✅ always
POST /api/public/apps/{key}/auth/logoutThe end-user session at Axowl — the token is revoked there, and EndUserLoggedOutEvent is published when EndUser events are enabled on the App Group. Optional returnUrl (a listed post-logout return URL) → redirectUrl✅ recommended
GET /api/public/orgs/{slug}/end-sessionOIDC RP-initiated logout: takes id_token_hint, revokes that session, validates post_logout_redirect_uri against your Application’s registered list, then redirectsOIDC clients only
POST /api/auth/logoutThe Axowl dashboard cookie session❌ not your plane

An EndUser JWT is a bearer token that your API verifies against the org’s JWKS without calling Axowl. After the logout endpoint revokes it:

  • Axowl refuses it — its own endpoints reject the token, introspection reports it as inactive and CheckPermissionAsync denies.
  • A signature-only check still accepts it until exp, because that check never asks Axowl.

Add every URL you pass as redirect_url — exact match, including any trailing slash — under Applications → your app → Authentication → Callback URLs. Returning to two different pages means two entries:

https://yourapp.com/
https://yourapp.com/register

This is the most common first-integration failure, because the sign-in itself looks fine right up until the hosted page refuses to come back.