Rules DSL¶
Pattern-based authorization for HTTP and WebSocket endpoints lives on a single
SecurityRules
table at security.rules. Verb methods mirror Javalin's route API; apiBuilder
groups rules under path prefixes. Authentication, fallbacks, and CORS preflight stay on
security.http / security.ws — see those pages
for config cheatsheets.
If you followed Secure endpoints, you already use this DSL — this page is the full reference.
Verb methods¶
Each call registers one entry. Repeated calls accumulate; order matters (first match wins).
| Method | Matches | Example |
|---|---|---|
get |
GET (also governs HEAD) |
rules.get("/api/*", Rules.authenticated()) |
post |
POST |
rules.post("/api/*", Rules.authenticated()) |
put |
PUT |
rules.put("/items/{id}", Rules.hasRole(Role.ADMIN)) |
patch |
PATCH |
rules.patch("/items/{id}", Rules.authenticated()) |
delete |
DELETE |
rules.delete("/items/{id}", Rules.hasRole(Role.ADMIN)) |
head |
HEAD only |
rules.head("/health", Rules.allow()) |
options |
OPTIONS |
rules.options("/api/*", Rules.allow()) |
any |
Any HTTP method | rules.any("/admin/*", Rules.hasRole(Role.ADMIN)) |
ws |
WebSocket upgrade (path only) | rules.ws("/ws/chat", Rules.authenticated()) |
A GET rule also governs HEAD on the same path — you rarely need a separate head entry.
Use any when method does not matter. WebSocket rules match on path only (no HTTP method).
apiBuilder¶
Group rules under shared prefixes, analogous to Javalin's routes.apiBuilder:
```kotlin
import io.github.mzlnk.javalin.security.authorization.Rules import io.github.mzlnk.javalin.security.apibuilder.SecurityApiBuilder.*
security.rules.apiBuilder {
path("/api") {
get("/*", Rules.authenticated())
post("/*", Rules.authenticated())
path("/admin") {
any("/*", Rules.hasRole(Role.ADMIN))
}
}
ws("/events", Rules.authenticated())
}
```
import io.github.mzlnk.javalin.security.authorization.Rules;
import static io.github.mzlnk.javalin.security.apibuilder.SecurityApiBuilder.*;
security.rules.apiBuilder(() -> {
path("/api", () -> {
get("/*", Rules.authenticated());
post("/*", Rules.authenticated());
path("/admin", () -> any("/*", Rules.hasRole(Role.ADMIN)));
});
ws("/events", Rules.authenticated());
});
path accepts paths with or without a leading slash ("/api" and "api" both work). Nested
path blocks concatenate prefixes (path("/a") { path("b") { get("/c", …) } } → /a/b/c).
A lone * segment stays attached to the prefix (path("/api") { get("*", …) } → /api*).
Static SecurityApiBuilder methods (get, post, path, …) are only valid inside
apiBuilder { } — calling them elsewhere throws at runtime.
Matching and fallbacks¶
Entries are evaluated top-to-bottom; first match wins per protocol (HTTP entries do not compete with WS entries). Route-declared RouteRoles are checked before the rule table — if a route declares any role, the table (including fallbacks) is skipped for that route.
When nothing matches, the fallback on the relevant config decides:
| Config | Fallback field | Default when unset |
|---|---|---|
| HTTP | http.fallback |
deny |
| WS | ws.fallback |
deny |
CORS preflight bypass is configured separately via http.allowCorsPreflight — see
CORS and preflight.
Path patterns use Javalin route syntax (*, {param}, <param>). Ant-style ** and ?
are rejected at startup. Patterns match the path without the context-path prefix.
Built-in rules¶
| Rule | Grants when… |
|---|---|
allow |
Always (including anonymous). |
deny |
Never. |
authenticated |
Caller is logged in. |
hasRole(role) |
Caller holds that role. |
hasAnyRole(…) |
Caller holds at least one of the listed roles. |
Call as Rules.allow(), Rules.deny(), Rules.authenticated(), Rules.hasRole(…), etc.
from both Kotlin and Java.
Anonymous denials return 401; authenticated-but-forbidden calls return 403. See Authorization.
Custom rules¶
Implement Rule
for request-time checks (IP allowlists, tenant ownership, …). The same type works for HTTP and
WebSocket entries.
import io.github.mzlnk.javalin.security.authorization.Rules
import io.github.mzlnk.javalin.security.security
val fromTrustedIp = Rule { _, ctx -> ctx.ip() in setOf("10.0.0.10", "10.0.0.11") }
config.security { security ->
security.rules.any("/admin/*", fromTrustedIp)
security.http.authentication = myStrategy
security.http.fallback = Rules.deny()
}
import io.github.mzlnk.javalin.security.authorization.Rule;
import io.github.mzlnk.javalin.security.authorization.Rules;
Rule fromTrustedIp = (auth, ctx) -> Set.of("10.0.0.10", "10.0.0.11").contains(ctx.ip());
config.registerPlugin(new JavalinSecurityPlugin(security -> {
security.rules.any("/admin/*", fromTrustedIp);
security.http.authentication = myStrategy;
security.http.fallback = Rules.deny();
}));
Never throw from a rule — return false instead. See
Custom authorization rules for patterns and testing.
Full example¶
import io.github.mzlnk.javalin.security.apibuilder.SecurityApiBuilder.*
import io.github.mzlnk.javalin.security.authorization.Rules
import io.github.mzlnk.javalin.security.security
import io.javalin.Javalin
Javalin.create { config ->
config.security { security ->
security.rules.get("/health", Rules.allow())
security.rules.any("/admin/*", Rules.hasRole(Role.ADMIN))
security.rules.ws("/ws/chat", Rules.authenticated())
security.rules.apiBuilder {
path("/api") {
get("/*", Rules.authenticated())
post("/*", Rules.authenticated())
}
}
security.http.authentication = myStrategy
security.http.fallback = Rules.deny()
security.http.allowCorsPreflight = true
security.ws.authentication = myWsStrategy
security.ws.allowedOrigins = listOf("https://app.example.com")
security.ws.fallback = Rules.deny()
}
config.routes.get("/health") { it.result("UP") }
}
import io.github.mzlnk.javalin.security.JavalinSecurityPlugin;
import io.github.mzlnk.javalin.security.authorization.Rules;
import io.javalin.Javalin;
import static io.github.mzlnk.javalin.security.apibuilder.SecurityApiBuilder.*;
Javalin.create(config -> {
config.registerPlugin(new JavalinSecurityPlugin(security -> {
security.rules.get("/health", Rules.allow());
security.rules.any("/admin/*", Rules.hasRole(Role.ADMIN));
security.rules.ws("/ws/chat", Rules.authenticated());
security.rules.apiBuilder(() -> {
path("/api", () -> {
get("/*", Rules.authenticated());
post("/*", Rules.authenticated());
});
});
security.http.authentication = myStrategy;
security.http.fallback = Rules.deny();
security.http.allowCorsPreflight = true;
security.ws.authentication = myWsStrategy;
security.ws.allowedOrigins = List.of("https://app.example.com");
security.ws.fallback = Rules.deny();
}));
config.routes.get("/health", ctx -> ctx.result("UP"));
});
Related¶
- HTTP security — authentication, fallback, CORS preflight.
- WebSocket security — authentication, allowedOrigins, fallback.
- Authorization — route roles, deny-by-default, path patterns.
- Custom authorization rules — advanced
Rulepatterns.