Security basics
Right now anyone can delete any post. Two things are needed. Authentication finds out who sent the request by checking a login and password. Authorization decides whether this user may do what they ask: for example, only the admin role may delete posts. In Quarkus the checks are described with the standard @RolesAllowed annotations, and the source of users is added with an extension.
Adding it
For learning we take the simplest source — users right in application.properties:
./mvnw quarkus:add-extension -Dextensions="elytron-security-properties-file"# src/main/resources/application.properties
quarkus.security.users.embedded.enabled=true
quarkus.security.users.embedded.plain-text=true
quarkus.security.users.embedded.users.anna=changeme
quarkus.security.users.embedded.roles.anna=admin
quarkus.security.users.embedded.users.oleg=changeme
quarkus.security.users.embedded.roles.oleg=user
quarkus.http.auth.basic=trueAnna has the admin role, Oleg has user. quarkus.http.auth.basic=true turns on Basic authentication: the client sends the login and password in the Authorization header of every request. Plain-text passwords are fine only for learning; on a server users are kept in a database with password hashes, or tokens are used (see the end of the lesson).
Protecting a method
// src/main/java/com/example/blog/PostResource.java
@DELETE
@Path("/{id}")
@Transactional
@RolesAllowed("admin")
public Response delete(@PathParam("id") long id) {
if (!Post.deleteById(id)) {
throw new PostNotFoundException(id);
}
return Response.noContent().build();
}@RolesAllowed("admin") (import jakarta.annotation.security.RolesAllowed) lets in only users with this role. The other methods of the resource stay open. Let's check three cases — without a password, as Oleg and as Anna:
curl -i -X DELETE http://localhost:8080/posts/1
curl -i -u oleg:changeme -X DELETE http://localhost:8080/posts/1
curl -i -u anna:changeme -X DELETE http://localhost:8080/posts/1HTTP/1.1 401 Unauthorized
www-authenticate: basic
HTTP/1.1 403 Forbidden
HTTP/1.1 204 No Content
The -u flag passes the login and password. 401 means "I don't know who you are" — you need to log in. 403 means "I know who you are, but you may not". A wrong password also gives 401.
Who made the request
The current user is available in code through SecurityIdentity:
// src/main/java/com/example/blog/MeResource.java
package com.example.blog;
import io.quarkus.security.Authenticated;
import io.quarkus.security.identity.SecurityIdentity;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import java.util.Set;
@Path("/me")
@Authenticated
public class MeResource {
@Inject
SecurityIdentity identity;
@GET
public Me me() {
return new Me(identity.getPrincipal().getName(), identity.getRoles());
}
public record Me(String name, Set<String> roles) {
}
}@Authenticated on the class lets in any logged-in user, whatever the role.
curl -u oleg:changeme http://localhost:8080/me{"name":"oleg","roles":["user"]}
Access annotations
| Annotation | Who gets in |
|---|---|
@PermitAll | Everyone, even without logging in |
@Authenticated | Any logged-in user |
@RolesAllowed("admin") | Only users with the role |
@DenyAll | Nobody |
What is used in practice
Real APIs usually use tokens: the oidc extension checks tokens from Keycloak or another login server, smallrye-jwt checks JWT tokens signed with your key. The @RolesAllowed and @Authenticated annotations stay the same — only the source of users changes.
Common mistake
Basic authentication sends the password almost in plain text: the Authorization header is just the login and password in Base64, which is decoded in a second. Without HTTPS anyone in the middle of the network can see it. Use Basic only locally or over HTTPS, and do not keep real passwords in application.properties — this file goes into Git and into the built application.