.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.
What you use
Section titled “What you use”| Where | What |
|---|---|
| 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]. |
Send the user to sign in
Section titled “Send the user to sign in”@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).
Complete the round trip
Section titled “Complete the round trip”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.
Gate a page behind sign-in
Section titled “Gate a page behind sign-in”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.
Call your API
Section titled “Call your API”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.
Sign out
Section titled “Sign out”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/logoutAuthorization: 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 }).
Four different logouts
Section titled “Four different logouts”“Log out” means four different things in this system.
| Call | What it ends | Yours? |
|---|---|---|
| Discarding the stored JWT | Your app’s local session | ✅ always |
POST /api/public/apps/{key}/auth/logout | The 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-session | OIDC RP-initiated logout: takes id_token_hint, revokes that session, validates post_logout_redirect_uri against your Application’s registered list, then redirects | OIDC clients only |
POST /api/auth/logout | The Axowl dashboard cookie session | ❌ not your plane |
What revocation reaches
Section titled “What revocation reaches”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
CheckPermissionAsyncdenies. - A signature-only check still accepts it until
exp, because that check never asks Axowl.
Register your Callback URLs
Section titled “Register your Callback URLs”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/registerThis is the most common first-integration failure, because the sign-in itself looks fine right up until the hosted page refuses to come back.
Related
Section titled “Related”- Hosted login — the URL, the two hosts and the checklist.
- JavaScript core client —
exchangeSessionCode,getTokenManager,logout. - Identity SDK — verifying the token and checking permissions on your server.