Skip to content

Authorization

bekov0004 edited this page Sep 13, 2026 · 1 revision

Authorization

By default the Folio UI is open to anyone who can reach the route. There are two ways to gate it, depending on whether your app already has authentication.

Scope note: both approaches below only gate the Folio UI. Neither protects the underlying OpenAPI document (SpecUrl, typically served by MapOpenApi() or Swashbuckle) or your actual API endpoints — anyone who requests that JSON directly still gets the full endpoint list and schemas. Protect those the same way you'd protect any other endpoint (e.g. .RequireAuthorization() on the spec endpoint).

Option 1 — integrate with your existing auth

If your app already has authentication (cookie, JWT bearer, ASP.NET Core Identity, an external provider, ...), point FolioOptions.AuthorizationPolicy at a policy name. Folio checks it via the app's own IAuthorizationService and challenges/forbids exactly like a normal [Authorize] endpoint would — it doesn't add a second auth system, it just asks yours.

builder.Services.AddAuthorization(options =>
    options.AddPolicy("FolioAccess", policy => policy.RequireAuthenticatedUser()));

var app = builder.Build();

// Authentication/authorization must run before UseFolio so
// HttpContext.User is populated when it checks AuthorizationPolicy.
app.UseAuthentication();
app.UseAuthorization();

app.UseFolio(options =>
{
    options.SpecUrl = "/openapi/v1.json";
    options.AuthorizationPolicy = "FolioAccess";
    options.LogoutUrl = "/account/logout"; // wherever your app signs out
});

An unauthenticated request gets challenged (e.g. redirected to your login page for cookie auth, or a 401 for JWT bearer); an authenticated-but-unauthorized request gets a 403.

Option 2 — self-contained login (no existing auth needed)

If you have no auth system yet and just want a login in front of the docs, AddFolioAuth()/UseFolioAuth() handles everything: cookie issuance, a login page styled to match Folio's own dark theme, and the login/logout routes — then wires itself into UseFolio(...) automatically (no need to set AuthorizationPolicy/LogoutUrl yourself).

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddFolioAuth(options =>
{
    options.Users = builder.Configuration.GetSection("Users").Get<List<FolioUser>>() ?? [];
});

var app = builder.Build();

app.UseFolioAuth();
app.UseFolio(options => options.SpecUrl = "/openapi/v1.json");

app.Run();
// appsettings.json
{
  "Users": [
    { "Username": "admin", "Password": "correct-horse-battery-staple" }
  ]
}

FolioAuthOptions reference

Property Default Description
Users [] Accounts accepted by the login page. Empty by default — set this or nobody can sign in.
LoginPath "/folio/login" Path the login page is served at. Independent of RoutePrefix — if you change that, set this to match.
LogoutPath "/folio/logout" Path that signs the user out and redirects back to LoginPath.
CookieName "Folio.Auth" Name of the authentication cookie.
Title null Brand text on the login page. Defaults to "Folio" — a separate setting from FolioOptions.Title.

Security notes

  • Credentials are submitted via POST, not the query string.
  • Users is a plain in-memory list, compared with a constant-time check — fine for gating an internal docs UI behind a shared login, not a substitute for a real identity system. Passwords are stored as plain text in configuration; don't reuse a password that matters elsewhere.
  • The cookie scheme registered by AddFolioAuth() is never made your app's default authentication scheme, so it can't interfere with any other auth you add later — Folio always authenticates against its own scheme by name.
  • ReturnUrl (used to send you back to the page you were trying to reach) is validated as a same-app relative path, rejecting absolute and protocol-relative (//host/...) values.

Order of calls

Calling UseFolioAuth() before UseFolio(...) is the clearer read, but the order doesn't actually matter — UseFolio's gate always lets the login/logout paths through to whichever middleware owns them, so getting the order backwards won't cause a redirect loop.

Clone this wiki locally