Skip to content

Authentication

Crate’s auth system has three layers:

  1. Core (crate.auth.core) — protocol-agnostic token extraction and validation
  2. Policies — protocol-specific response handling (HTTP 401 vs JSON-RPC error)
  3. Middleware — per-operation auth rules via UDAs (@getItem, @create, etc.)

The core module extracts tokens from two sources:

Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...

extractBearerToken strips the “Bearer ” prefix and returns the token string.

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;
}

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.

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.

Crate ships five auth middleware classes. Each applies different auth rules to different CRUD operations using UDAs:

Everything requires authentication.

class PrivateDataMiddleware : AuthMiddleware {
@any
void mandatory(HTTPServerRequest req, HTTPServerResponse res) {
mandatoryAuth(req, res);
}
}

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);
}
}

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);
}
}

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);
}
}

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);
}
}

After successful authentication, the request context contains:

KeyValue
req.usernameUser 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.

CrateRouter.enable() registers OAuth2 globally:

auto crateRouter = router.crateSetup!RestApi;
crateRouter.enable(auth); // Adds auth.tokenHandlers to URLRouter

This 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.

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.