Authentication & Authorization
Configure authentication strategies and entity-level authorization in your Spiderly application.
Authentication Strategies
Spiderly supports two authentication strategies. Both use JWT tokens under the hood — the difference is how tokens are transported between client and server.
Token-Based (Default)
Tokens are returned in the response body and sent via the Authorization header. The client is responsible for storing and managing tokens.
Angular methods:
| Method | Returns | Description |
|---|---|---|
login(request) | AuthResult | Login with email verification token |
loginExternal(provider) | AuthResult | Login with external provider (e.g., Google) |
logout(browserId) | — | Invalidate tokens |
refreshTokenWithHeaders(request) | AuthResult | Refresh an expired access token |
AuthResult includes accessToken, refreshToken, userId, and email.
Cookie-Based
Tokens are stored in HttpOnly cookies — the browser handles storage and sends them automatically. No token management needed on the client side.
Angular methods:
| Method | Returns | Description |
|---|---|---|
loginWithCookies(request) | AuthResultWithCookies | Login with email verification token |
loginExternalWithCookies(provider) | AuthResultWithCookies | Login with external provider (e.g., Google) |
logoutWithCookies(browserId) | — | Clear auth cookies |
refreshTokenWithCookies(browserId) | AuthResultWithCookies | Refresh via cookie |
AuthResultWithCookies includes userId, email, and accessTokenExpiresAt — no tokens in the response body.
Cookie-based authentication requires CSRF protection. All state-changing
requests (POST, PUT, DELETE, PATCH) must include the X-CSRF: "1" header.
Choosing a Strategy
| Token-Based | Cookie-Based | |
|---|---|---|
| Best for | Mobile apps, cross-domain APIs, SPAs with explicit token handling | Same-domain/subdomain setups, simpler frontend code |
| Token storage | Client manages (localStorage, memory) | Browser manages (HttpOnly cookies) |
| CSRF protection | Not needed (tokens in headers) | Required (X-CSRF header) |
| Cross-subdomain | Works out of the box | Requires CookieDomain and CookieSameSite configuration |
Overview
By default, all entities require authorization for Create, Read, Update, and Delete operations. If your code encounters an authorization issue (e.g., an UnauthorizedException - You don't have the necessary rights to perform the operation.), it means the current user lacks the necessary permissions.
If your entity requires access control, follow the steps in this tutorial to configure proper authorization.
Registration
Authentication and authorization are turned on by a single call inside the AddSpiderly builder in your Startup.cs. The spiderly init template scaffolds it for you — you rarely write it by hand:
services.AddSpiderly<ApplicationDbContext>(Configuration, spiderly =>
{
spiderly.UsePostgreSQL();
spiderly.AddSecurity<User, UserExternalLogin, AuthorizationService>();
});AddSecurity<TUser, TUserExternalLogin, TAuthorizationService> registers the whole auth core as one unit: the email-login and JWT services, the User principal kind, your AuthorizationService, and — crucially — the permission handler that forwards to it. These pieces are co-required: an app with some but not all of them is silently broken, so AddSecurity bundles them all-or-nothing. A single-principal app needs nothing more.
Optional add-ons opt in through a sub-builder. API keys, for example, register a second principal kind and its auth scheme:
spiderly.AddSecurity<User, UserExternalLogin, AuthorizationService>(s => s.AddApiKeys<ApiKey>());If you replace the scaffolded AddSecurity call with hand-rolled registration and forget the
permission handler, the app fails at startup: "Spiderly authentication is enabled, so
[AuthGuard("Code")] materializes a PermissionRequirement policy, but no IAuthorizationHandler that
satisfies PermissionRequirement is registered." Fix it by calling
services.AddSpiderlyAuthorization<AuthorizationServiceGenerated>() — or simply keep using
AddSecurity, which registers the handler for you. It's a fail-loud boot guard, never a silent 403.
Authorization Attributes
[AuthGuard]
The single attribute that protects an endpoint. Bare, it requires an authenticated principal. With a permission code, it additionally requires the principal — a logged-in user or an API key — to hold that permission, or the request is rejected with 403 Forbidden.
[SpiderlyController]
[ApiController]
[Route("/api/[controller]/[action]")]
public class ReportController : ReportBaseController
{
[HttpPost]
[AuthGuard] // Any authenticated principal
public async Task<IActionResult> RunExport() { /* ... */ }
[HttpPost]
[AuthGuard("RunSalesReport")] // ...and must hold RunSalesReport
public async Task<IActionResult> RunSalesReport() { /* ... */ }
}The permission code must be seeded and assigned to the caller's role.
Use it on endpoints you write yourself; entity CRUD already enforces the default Read{Entity} / Insert{Entity} / Update{Entity} / Delete{Entity} permissions with no attribute needed.
[AuthGuard] derives from ASP.NET Core's [Authorize], so it composes the way you would expect: put it on the controller class to apply one guard to every action, add it to an action to require both, and use [AllowAnonymous] to opt a single action out.
Earlier versions split this across two attributes — [AuthGuard] for authentication and
[HasPermission("Code")] for authorization. They were required together but easy to write apart,
and the failure was silent: an endpoint with only [AuthGuard] accepts any logged-in account,
including an ordinary customer on a public storefront. Replace [HasPermission("Code")] with
[AuthGuard("Code")] and delete the now-redundant [AuthGuard] line above it.
One permission per attribute. Stack two to require both. "Either A or B" needs a real composite policy and is deliberately not modelled here.
[DoNotAuthorize]
Disables authorization checks for all CRUD operations on the decorated entity. By default, every entity requires the user to hold the relevant Read{Entity} / Insert{Entity} / Update{Entity} / Delete{Entity} permission.
[DoNotAuthorize] bypasses security checks entirely and is intended primarily for testing or for
entities that are truly public. Avoid it on entities that contain user data or any value worth
protecting in production.
[SpiderlyEntity]
[DoNotAuthorize] // Public catalog — no permissions required
public class PublicAnnouncement : BusinessObject<long>
{
[DisplayName]
public string Title { get; set; }
}CSRF Protection
Cookie authentication is ambient: the browser attaches the cookie to any request a page makes, including one triggered from another site. Spiderly rejects such a request unless it carries an X-CSRF header — the value doesn't matter, only its presence, because a custom header cannot be set cross-origin without passing a CORS preflight first.
Register the middleware after UseRouting():
app.UseRouting();
app.UseSpiderlyCsrf();
app.UseAuthentication();
app.UseAuthorization();Protection is global and opt-out, matching ASP.NET Core's own antiforgery guidance: with per-endpoint opt-in, an endpoint eventually gets left unprotected by mistake. Exempt an endpoint that a browser never reaches with the user's cookies — a payment-gateway callback, a partner webhook — with [IgnoreCsrf].
Not challenged, by design: requests authenticated via an Authorization: Bearer header (a token is sent deliberately, not ambiently), and GET / HEAD / OPTIONS (they must not change state to begin with).
This defense rests on your CORS origin allow-list: it works because a cross-origin caller
cannot add a custom header without a preflight your policy must approve. An app that allows
arbitrary origins with credentials (SetIsOriginAllowed(_ => true)) makes it a no-op. ASP.NET
already blocks the common form of this mistake — AllowAnyOrigin() and AllowCredentials() are
mutually exclusive — so keep the explicit escape hatch out of a cookie-authenticated app.
This check used to live inside [AuthGuard], which made it opt-in per endpoint: anything
reachable with cookies but annotated differently — or not at all — had no CSRF protection,
silently. If the middleware is missing while authentication is enabled, the app now fails at
startup rather than serving unprotected writes. Register it on the main pipeline: a
registration inside an app.Map(...) branch protects only that branch, and the startup guard
treats it as missing.
Per-action permissions
For finer-grained control — e.g. requiring an extra permission on top of the default Insert{Entity} / Update{Entity} permission — see [UIAdditionalPermissionCodeForInsert] and [UIAdditionalPermissionCodeForUpdate] in UI Customization — Per-Action Permissions.
Add Permissions to Your Application
Navigate to your ApplicationDbContext.cs file:
Backend\{your-app-name}.Infrastructure\{your-app-name}ApplicationDbContext.csIn the SeedData method, add your entity permissions to the permissions array. Replace YourEntityName with your actual entity name:
private static void SeedData(ModelBuilder modelBuilder)
{
Permission[] permissions =
[
// ... existing permissions ...
// Add your new entity permissions
new Permission { Id = 13, Name = "View YourEntityName", Code = "ReadYourEntityName" },
new Permission { Id = 14, Name = "Edit existing YourEntityName", Code = "UpdateYourEntityName" },
new Permission { Id = 15, Name = "Add new YourEntityName", Code = "InsertYourEntityName" },
new Permission { Id = 16, Name = "Delete YourEntityName", Code = "DeleteYourEntityName" },
];
modelBuilder.Entity<Permission>().HasData(permissions);
// ... rest of seed data ...
}Important: Make sure to use sequential IDs that don't conflict with existing permissions.
After adding the permissions, create and apply a migration:
spiderly add-migration AddYourEntityNamePermissions
spiderly update-databaseAssign Permissions to a Role
In the application UI:
- Navigate to Administration → Roles.
- Select the role you want to modify.
- In the Permissions control, add the newly created permissions.
This ensures users assigned to this role will have access to the specified entity operations.
See Also
- UI Customization — Per-Action Permissions — require an extra permission on top of the default
Insert{Entity}/Update{Entity}permission - Backend Customization — Authorization Service — override authorization logic in code (e.g., row-level checks)
- Deployment — Cookie Configuration —
CookieDomainandCookieSameSitesettings for cookie-based authentication across subdomains
File Storage
Configure file uploads in Spiderly — built-in storage adapters, custom adapters, validation, processing hooks, and automatic cleanup.
API Keys
Give machines, partners, and AI agents access to your REST API with per-key authentication, where each key is a first-class principal carrying its own roles.