Authentication
Architecture
Section titled “Architecture”Crate’s auth system has three layers:
- Core (
crate.auth.core) — protocol-agnostic token extraction and validation - Policies — protocol-specific response handling (HTTP 401 vs JSON-RPC error)
- Middleware — per-operation auth rules via UDAs (
@getItem,@create, etc.)
Token Extraction
Section titled “Token Extraction”The core module extracts tokens from two sources:
Bearer Header
Section titled “Bearer Header”Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...extractBearerToken strips the “Bearer ” prefix and returns the token string.
Ember Cookie
Section titled “Ember Cookie”Cookie: ember_simple_auth-session={"authenticated":{"access_token":"abc123"}}extractEmberToken parses the JSON cookie and extracts the token value.
Both produce the same AuthenticationResult:
struct AuthenticationResult { AuthResult status; // success, unauthorized, invalidToken TokenSource source; // bearerHeader, emberCookie, none User user; // The authenticated user (if successful) string token; string errorMessage;}Policy-Based Response Handling
Section titled “Policy-Based Response Handling”The Authenticator class delegates response handling to a policy struct:
class Authenticator { AuthenticationResult authenticate(Policy)( ref Policy policy, HTTPServerRequest req, HTTPServerResponse res, AuthMode mode);}A policy struct defines what happens on success, failure, or invalid tokens:
struct HttpAuthPolicy { void onSuccess(AuthenticationResult result, HTTPServerRequest req, HTTPServerResponse res) { // Set request context with user info req.username = result.user.id; req.context["email"] = result.user.email; req.context["user"] = result.user.toJson; }
void onUnauthorized(AuthenticationResult result, HTTPServerRequest req, HTTPServerResponse res) { respondUnauthorized(res); // HTTP 401 }
void onInvalidToken(AuthenticationResult result, HTTPServerRequest req, HTTPServerResponse res) { respondUnauthorized(res, "Invalid token.", 400); // HTTP 400 }}This design means the same authentication logic works for HTTP REST (returns 401 status codes) and MCP JSON-RPC (returns error objects) — only the policy changes.
Auth Modes
Section titled “Auth Modes”enum AuthMode { mandatory, // Request MUST be authenticated or it's rejected permissive // Authentication is optional — unauthenticated requests pass through}With mandatory, unauthenticated requests get a 401 immediately. With permissive, the request continues but req.context["email"] won’t be set.
Built-In Middleware Classes
Section titled “Built-In Middleware Classes”Crate ships five auth middleware classes. Each applies different auth rules to different CRUD operations using UDAs:
PrivateDataMiddleware
Section titled “PrivateDataMiddleware”Everything requires authentication.
class PrivateDataMiddleware : AuthMiddleware { @any void mandatory(HTTPServerRequest req, HTTPServerResponse res) { mandatoryAuth(req, res); }}PublicDataMiddleware
Section titled “PublicDataMiddleware”Read operations are public, write operations require auth.
class PublicDataMiddleware : AuthMiddleware { @getItem @getList void permissive(HTTPServerRequest req, HTTPServerResponse res) { permissiveAuth(req, res); }
@create @replace @patch @delete_ void mandatory(HTTPServerRequest req, HTTPServerResponse res) { mandatoryAuth(req, res); }}PublicContributionMiddleware
Section titled “PublicContributionMiddleware”Read and create are public, edit and delete require auth.
class PublicContributionMiddleware : AuthMiddleware { @getItem @getList @create void permissive(HTTPServerRequest req, HTTPServerResponse res) { permissiveAuth(req, res); }
@replace @patch @delete_ void mandatory(HTTPServerRequest req, HTTPServerResponse res) { mandatoryAuth(req, res); }}ContributionMiddleware
Section titled “ContributionMiddleware”Only create is public, everything else requires auth.
class ContributionMiddleware : AuthMiddleware { @create void permissive(HTTPServerRequest req, HTTPServerResponse res) { permissiveAuth(req, res); }
@getItem @getList @replace @patch @delete_ void mandatory(HTTPServerRequest req, HTTPServerResponse res) { mandatoryAuth(req, res); }}IdentifiableContributionMiddleware
Section titled “IdentifiableContributionMiddleware”Like PublicContributionMiddleware, but GET operations use identifiableAuth — it identifies the user if a token is present without failing if it’s missing or invalid.
class IdentifiableContributionMiddleware : AuthMiddleware { @getItem @getList void identify(HTTPServerRequest req, HTTPServerResponse res) { identifiableAuth(req, res); }
@create void permissive(HTTPServerRequest req, HTTPServerResponse res) { permissiveAuth(req, res); }
@replace @patch @delete_ void mandatory(HTTPServerRequest req, HTTPServerResponse res) { mandatoryAuth(req, res); }}Request Context After Auth
Section titled “Request Context After Auth”After successful authentication, the request context contains:
| Key | Value |
|---|---|
req.username | User ID |
req.context["email"] | User email address |
req.context["user"] | Full user object as JSON |
Downstream middleware can check if ("email" in req.context) to determine whether the request is authenticated.
OAuth2 Integration
Section titled “OAuth2 Integration”CrateRouter.enable() registers OAuth2 globally:
auto crateRouter = router.crateSetup!RestApi;crateRouter.enable(auth); // Adds auth.tokenHandlers to URLRouterThis runs before the CrateRouter’s own handler, so tokens are validated on every request. Individual models then use the middleware classes above to control which operations require authentication.
UserCrateCollection
Section titled “UserCrateCollection”Crate provides UserCrateCollection, a UserCollection implementation backed by a Crate!User:
class UserCrateCollection : UserCollection { Token createToken(string email, SysTime expire, string[] scopes = []); void revoke(string token); User opIndex(string email); // Get by email User byToken(string token); // Get by token User byId(string id); // Get by ID bool contains(string email); void empower(string email, string access); // Grant scope void disempower(string email, string access); // Revoke scope}Features a built-in LRU cache (2048 users, 10-second TTL) to avoid hitting the database on every token validation.