Basic authentication is an HTTP scheme in which the client sends a user name and a password in the Authorization header of every request, and the server accepts or rejects the request before any application code runs. In Dropwizard, the dropwizard-auth module implements it. The module reads the header, checks the credentials in an Authenticator and passes the logged-in user to resource methods annotated with @Auth. An Authorizer adds role checks for @RolesAllowed, so the same setup answers 401 for bad credentials and 403 for a missing role.
We use basic auth for internal APIs, admin tools and service-to-service calls over HTTPS, where a full OAuth 2 setup is not needed. The following example registers the auth filter in run() of an invoice service.
environment.jersey().register(new AuthDynamicFeature(
new BasicCredentialAuthFilter.Builder<User>()
.setAuthenticator(cachingAuthenticator)
.setAuthorizer(new RoleAuthorizer())
.setRealm("invoices")
.buildAuthFilter()));
environment.jersey().register(RolesAllowedDynamicFeature.class);
environment.jersey().register(new AuthValueFactoryProvider.Binder<>(User.class));
In this article, we build the authenticator with BCrypt password hashes, add roles, call the API with curl and test 401 and 403 with JUnit 6, on Dropwizard 5.0.2 and Java 25. All Dropwizard articles are on the Dropwizard tutorials page.
1. How Basic Authentication Works in Dropwizard
With basic auth, the client sends Authorization: Basic followed by the Base64 encoding of user:password on every request. Base64 is not encryption, so basic auth is only safe over HTTPS.

In Dropwizard, BasicCredentialAuthFilter decodes the header and calls our Authenticator. When it returns a user, RolesAllowedDynamicFeature checks @RolesAllowed through our Authorizer, and AuthValueFactoryProvider injects the user into @Auth parameters.
2. Dependencies and the User Principal
The dropwizard-auth module comes from the Dropwizard BOM, which the Dropwizard tutorial sets up. For password hashing, the example uses the at.favre.lib:bcrypt library.
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-auth</artifactId>
</dependency>
<dependency>
<groupId>at.favre.lib</groupId>
<artifactId>bcrypt</artifactId>
<version>0.10.2</version>
</dependency>
The authenticated user must implement java.security.Principal. A record with a name and a set of roles is enough.
public record User(String name, Set<String> roles) implements Principal {
@Override
public String getName() {
return name;
}
}
3. Users With Hashed Passwords in config.yml
Never store plain passwords, not even in a configuration file. The example stores BCrypt hashes, and a small custom command prints the hash for a new password.
users:
- username: lokesh
passwordHash: "$2a$12$TFyDsjEsxGV.rboyCbDmkObjPOblpAcuPYuyUhK4Iy2SbiR9Rnjz6"
roles: [USER]
- username: alex
passwordHash: "$2a$12$Z6h05J6r3wL7vyU0xIP8AOQCYXqLihBsbr8MvnnA9QXYZr1a2598y"
roles: [USER, ADMIN]
authenticationCachePolicy: maximumSize=1000, expireAfterAccess=10m
$ java -jar target/dropwizard-basic-auth-1.0.0.jar hash-password (password typed: user-demo-pass)
$2a$12$TFyDsjEsxGV.rboyCbDmkObjPOblpAcuPYuyUhK4Iy2SbiR9Rnjz6
In a real service, the users come from a database or a directory, and the same authenticator reads the hash from there.
4. The Authenticator
The authenticator returns the user when the password matches the hash and an empty Optional otherwise. For an unknown user name, it still runs one BCrypt check against a dummy hash, so an attacker cannot find valid user names by measuring the response time.
@Override
public Optional<User> authenticate(BasicCredentials credentials) {
UserAccount account = accounts.get(credentials.getUsername());
String hash = account != null ? account.passwordHash() : DUMMY_HASH;
boolean matches = BCrypt.verifyer()
.verify(credentials.getPassword().toCharArray(), hash)
.verified;
if (account == null || !matches) {
return Optional.empty();
}
return Optional.of(new User(account.username(), account.roles()));
}
BCrypt is slow on purpose, so checking it on every request costs CPU time. CachingAuthenticator keeps successful logins in a cache with the policy from config.yml and publishes cache metrics.
BCryptAuthenticator authenticator = new BCryptAuthenticator(configuration.getUsers());
CachingAuthenticator<BasicCredentials, User> cachingAuthenticator = new CachingAuthenticator<>(
environment.metrics(), authenticator, configuration.getAuthenticationCachePolicy());
5. Roles With an Authorizer
The authorizer decides whether a user has a role. Our version checks the roles from the configuration.
@Override
public boolean authorize(User user, String role, ContainerRequestContext requestContext) {
return user.roles().contains(role);
}
In the resource, @PermitAll allows every authenticated user, @RolesAllowed(“ADMIN”) limits a method to admins, and the @Auth parameter gives the method the logged-in user.
@DELETE
@Path("/{id}")
@RolesAllowed("ADMIN")
public Response deleteInvoice(@PathParam("id") long id, @Auth User admin) {
if (invoices.remove(id) == null) {
throw new NotFoundException("Invoice " + id + " not found");
}
LOG.info("Invoice {} deleted by {}", id, admin.getName());
return Response.noContent().build();
}
Methods without @Auth or a security annotation stay public, which our /welcome endpoint uses.
6. Call the API With curl
A request without credentials, with a wrong password or for an unknown user gets the same 401 and the WWW-Authenticate header, which makes browsers show a login dialog.
$ curl -s -i http://localhost:18094/invoices
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="invoices"
Credentials are required to access this resource.
A valid user reads the invoices, but deleting one needs the ADMIN role, so the regular user gets 403 and the admin gets 204.
$ curl -s -u lokesh:user-demo-pass http://localhost:18094/invoices
[{"id":1,"customer":"Asha","amount":120.00},{"id":2,"customer":"Ben","amount":75.50}]
$ curl -s -i -u lokesh:user-demo-pass -X DELETE http://localhost:18094/invoices/2
HTTP/1.1 403 Forbidden
{"code":403,"message":"User not authorized."}
$ curl -s -i -u alex:admin-demo-pass -X DELETE http://localhost:18094/invoices/2
HTTP/1.1 204 No Content
The demo passwords are only for the example. Serve the API over HTTPS with an https connector, because the header can be decoded by anyone who sees the traffic.
7. Test Authentication With JUnit 6
The example tests the resource with ResourceExtension and the full app with DropwizardAppExtension, both from dropwizard-testing. The tests send real Authorization headers and assert 200, 401, 403 and 204, and all nine pass with mvn test. To check that the service itself is up, add a Dropwizard health check, which also removes the startup warning about missing health checks.
8. Dropwizard Basic Auth FAQs
Most questions about Dropwizard auth are about the difference between 401 and 403 and about other credential types.
8.1. What Is the Difference Between 401 and 403 in Dropwizard Auth?
401 means the request has no valid credentials, so the authenticator returned no user. 403 means the user was authenticated, but the authorizer said the user lacks the role in @RolesAllowed.
8.2. Can We Use OAuth Bearer Tokens Instead of Basic Auth?
Yes. The dropwizard-auth module has OAuthCredentialAuthFilter for bearer tokens. The authenticator receives the token string instead of BasicCredentials, and the rest of the setup, including @Auth and roles, stays the same.
8.3. Why Do We Need the CachingAuthenticator?
BCrypt takes many milliseconds per check on purpose, and basic auth sends the password with every request. The cache skips the BCrypt check for recent successful logins and keeps the API fast.
9. Conclusion
Dropwizard basic auth needs an Authenticator, an optional Authorizer and three registrations in run(). With hashed passwords, a caching authenticator and HTTPS, it fits internal and admin APIs.
The filter returns 401 and 403 before our code runs, so resource methods only deal with valid users. To call other services from the same app, see the Dropwizard client article.
10. References
- Dropwizard Authentication Manual
- RFC 7617, The Basic HTTP Authentication Scheme
- BCrypt library at.favre.lib:bcrypt
Happy Learning !!