@pre_authorize

Expression-based method security. The most powerful and flexible security macro.

Syntax Options

// Simple checks
#[pre_authorize(authenticated)]               // Any authenticated user
#[pre_authorize(role = "ADMIN")]              // Single role
#[pre_authorize(authority = "users:write")]   // Single authority
#[pre_authorize(authorities = ["a", "b"])]    // Multiple authorities (OR)

// Expression syntax
#[pre_authorize("hasRole('ADMIN')")]
#[pre_authorize("hasRole('USER') AND hasAuthority('posts:write')")]
#[pre_authorize("hasAnyRole('ADMIN', 'MANAGER') OR hasAuthority('reports:view')")]

Simple Checks

Authenticated Only

#[pre_authorize(authenticated)]
#[get("/profile")]
async fn profile(user: AuthenticatedUser) -> impl Responder {
    HttpResponse::Ok().body(format!("Hello, {}!", user.get_username()))
}

Single Role

#[pre_authorize(role = "ADMIN")]
#[get("/admin")]
async fn admin(user: AuthenticatedUser) -> impl Responder {
    HttpResponse::Ok().body("Admin panel")
}

Single Authority

#[pre_authorize(authority = "users:delete")]
#[delete("/users/{id}")]
async fn delete_user(user: AuthenticatedUser, path: web::Path<i64>) -> impl Responder {
    HttpResponse::Ok().body(format!("Deleted user {}", path.into_inner()))
}

Multiple Authorities (OR)

#[pre_authorize(authorities = ["users:read", "users:write"])]
#[get("/users")]
async fn list_users(user: AuthenticatedUser) -> impl Responder {
    HttpResponse::Ok().json(vec!["user1", "user2"])
}

Expression Syntax

For complex authorization rules, use the expression syntax:

#[pre_authorize("expression")]

Available Functions

FunctionDescription
hasRole('ROLE')User has the specified role
hasAnyRole('R1', 'R2')User has any of the roles
hasAuthority('auth')User has the authority
hasAnyAuthority('a1', 'a2')User has any of the authorities
isAuthenticated()User is authenticated
permitAll()Always allow
denyAll()Always deny

Operators

OperatorDescription
ANDBoth conditions must be true
OREither condition can be true
NOTNegates the condition
( )Groups expressions

Expression Examples

Basic Expressions

// Role check
#[pre_authorize("hasRole('ADMIN')")]

// Authority check
#[pre_authorize("hasAuthority('posts:write')")]

// Any of multiple roles
#[pre_authorize("hasAnyRole('ADMIN', 'MANAGER', 'SUPERVISOR')")]

// Any of multiple authorities
#[pre_authorize("hasAnyAuthority('posts:read', 'posts:write')")]

Combining with AND

// Must have role AND authority
#[pre_authorize("hasRole('USER') AND hasAuthority('posts:write')")]
#[post("/posts")]
async fn create_post(user: AuthenticatedUser) -> impl Responder {
    HttpResponse::Created().body("Post created")
}

Combining with OR

// Either admin role OR specific authority
#[pre_authorize("hasRole('ADMIN') OR hasAuthority('users:write')")]
#[put("/users/{id}")]
async fn update_user(user: AuthenticatedUser) -> impl Responder {
    HttpResponse::Ok().body("User updated")
}

Using NOT

// Anyone except guests
#[pre_authorize("NOT hasRole('GUEST')")]
#[get("/premium")]
async fn premium(user: AuthenticatedUser) -> impl Responder {
    HttpResponse::Ok().body("Premium content")
}

Complex Expressions

// Admin OR (User with write permission)
#[pre_authorize("hasRole('ADMIN') OR (hasRole('USER') AND hasAuthority('posts:write'))")]
#[post("/posts")]
async fn create_post(user: AuthenticatedUser) -> impl Responder {
    HttpResponse::Created().body("Post created")
}

// Multiple conditions
#[pre_authorize("(hasAnyRole('ADMIN', 'MANAGER')) AND hasAuthority('reports:export')")]
#[get("/reports/export")]
async fn export_reports(user: AuthenticatedUser) -> impl Responder {
    HttpResponse::Ok().body("Exported")
}

Permit All / Deny All

#[pre_authorize("permitAll()")]
#[get("/public")]
async fn public_info(user: AuthenticatedUser) -> impl Responder {
    HttpResponse::Ok().body("Public")
}

#[pre_authorize("denyAll()")]
#[get("/disabled")]
async fn disabled(_user: AuthenticatedUser) -> impl Responder {
    HttpResponse::Ok().body("Never reached")
}

Compile-Time Validation

Expressions are parsed and validated at compile time:

// ✓ Valid - compiles successfully
#[pre_authorize("hasRole('ADMIN') AND hasAuthority('users:write')")]

// ✗ Invalid - compile error: unexpected token
#[pre_authorize("hasRole('ADMIN') && hasAuthority('users:write')")]

// ✗ Invalid - compile error: unmatched parenthesis
#[pre_authorize("hasRole('ADMIN'")]

// ✗ Invalid - compile error: unknown function
#[pre_authorize("hasPermission('admin')")]

Error Response

When access is denied:

HTTP/1.1 403 Forbidden
Content-Length: 0

Spring Security Comparison

Spring Security:

@PreAuthorize("hasRole('ADMIN')")
public void adminOnly() {}

@PreAuthorize("hasRole('USER') and hasAuthority('posts:write')")
public void createPost() {}

@PreAuthorize("hasAnyRole('ADMIN', 'MANAGER') or hasAuthority('reports:view')")
public void viewReports() {}

@PreAuthorize("isAuthenticated()")
public void authenticated() {}

Actix Security:

#[pre_authorize("hasRole('ADMIN')")]
async fn admin_only() {}

#[pre_authorize("hasRole('USER') AND hasAuthority('posts:write')")]
async fn create_post() {}

#[pre_authorize("hasAnyRole('ADMIN', 'MANAGER') OR hasAuthority('reports:view')")]
async fn view_reports() {}

#[pre_authorize("isAuthenticated()")]
async fn authenticated() {}

Key differences:

  • Use AND/OR instead of and/or (case-insensitive but uppercase is conventional)
  • Use single quotes for strings: 'ADMIN' not "ADMIN"

When to Use

Use #[pre_authorize] when:

  • You need authority checks
  • You need AND/OR/NOT logic
  • You need complex expressions
  • You want Spring Security-like syntax

Use #[secured] instead when:

  • You only need simple role checks
  • OR logic is sufficient