Vert.x Auth in Java: Asynchronous Authentication and Authorization

Vert.x Auth is a comprehensive authentication and authorization framework built for the Vert.x toolkit, providing asynchronous, non-blocking security solutions for reactive applications.

Vert.x Auth Overview

Vert.x Auth provides:

  • Multiple authentication providers (JWT, OAuth2, Shiro, JDBC, MongoDB)
  • Asynchronous, non-blocking operations
  • Role-Based Access Control (RBAC)
  • Permission-based authorization
  • Token-based authentication
  • Integration with Vert.x Web

Dependencies and Setup

Maven Configuration:

<dependencies>
<!-- Vert.x Core -->
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
<version>4.4.6</version>
</dependency>
<!-- Vert.x Web -->
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web</artifactId>
<version>4.4.6</version>
</dependency>
<!-- Vert.x Auth -->
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-auth-common</artifactId>
<version>4.4.6</version>
</dependency>
<!-- JWT Auth -->
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-auth-jwt</artifactId>
<version>4.4.6</version>
</dependency>
<!-- OAuth2 -->
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-auth-oauth2</artifactId>
<version>4.4.6</version>
</dependency>
<!-- JDBC Auth -->
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-auth-jdbc</artifactId>
<version>4.4.6</version>
</dependency>
<!-- MongoDB Auth -->
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-auth-mongo</artifactId>
<version>4.4.6</version>
</dependency>
<!-- PostgreSQL Client -->
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-pg-client</artifactId>
<version>4.4.6</version>
</dependency>
<!-- Config -->
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-config</artifactId>
<version>4.4.6</version>
</dependency>
</dependencies>

Configuration Management

AuthConfig.java:

public class AuthConfig {
private final JsonObject config;
public AuthConfig() {
this.config = loadConfig();
}
private JsonObject loadConfig() {
return new JsonObject()
.put("http", new JsonObject()
.put("port", 8080)
.put("host", "localhost"))
.put("auth", new JsonObject()
.put("jwt", new JsonObject()
.put("secret", "your-jwt-secret-key-here")
.put("algorithm", "HS256")
.put("expiresInMinutes", 60))
.put("oauth2", new JsonObject()
.put("clientId", "your-client-id")
.put("clientSecret", "your-client-secret")
.put("site", "https://oauth-provider.com"))
.put("database", new JsonObject()
.put("url", "jdbc:postgresql://localhost:5432/authdb")
.put("user", "auth_user")
.put("password", "auth_password")
.put("driver_class", "org.postgresql.Driver"))
.put("mongodb", new JsonObject()
.put("connection_string", "mongodb://localhost:27017")
.put("db_name", "authdb")));
}
public JsonObject getJwtConfig() {
return config.getJsonObject("auth").getJsonObject("jwt");
}
public JsonObject getOAuth2Config() {
return config.getJsonObject("auth").getJsonObject("oauth2");
}
public JsonObject getDatabaseConfig() {
return config.getJsonObject("auth").getJsonObject("database");
}
public JsonObject getMongoConfig() {
return config.getJsonObject("auth").getJsonObject("mongodb");
}
public int getHttpPort() {
return config.getJsonObject("http").getInteger("port");
}
public String getHttpHost() {
return config.getJsonObject("http").getString("host");
}
}

JWT Authentication Provider

JwtAuthProvider.java:

public class JwtAuthProvider {
private final JWTAuth jwtAuth;
private final AuthConfig config;
public JwtAuthProvider(Vertx vertx, AuthConfig config) {
this.config = config;
this.jwtAuth = createJwtAuth(vertx);
}
private JWTAuth createJwtAuth(Vertx vertx) {
JsonObject jwtConfig = new JsonObject()
.put("keyStore", new JsonObject()
.put("type", "secret")
.put("secret", config.getJwtConfig().getString("secret")));
return JWTAuth.create(vertx, jwtConfig);
}
public JWTAuth getProvider() {
return jwtAuth;
}
public String generateToken(User user, List<String> permissions, List<String> roles) {
JsonObject claims = new JsonObject()
.put("sub", user.getUsername())
.put("iss", "vertx-auth-service")
.put("exp", System.currentTimeMillis() / 1000 + 
config.getJwtConfig().getInteger("expiresInMinutes") * 60)
.put("iat", System.currentTimeMillis() / 1000)
.put("permissions", new JsonArray(permissions))
.put("roles", new JsonArray(roles))
.put("userId", user.getId())
.put("email", user.getEmail());
return jwtAuth.generateToken(claims);
}
public Future<User> authenticate(String token) {
Promise<User> promise = Promise.promise();
jwtAuth.authenticate(new JsonObject().put("jwt", token))
.onSuccess(user -> {
// Convert to application User object
User appUser = convertToAppUser(user);
promise.complete(appUser);
})
.onFailure(promise::fail);
return promise.future();
}
private User convertToAppUser(io.vertx.ext.auth.User authUser) {
JsonObject principal = authUser.principal();
return new User(
principal.getString("userId"),
principal.getString("sub"),
principal.getString("email"),
principal.getJsonArray("permissions").getList(),
principal.getJsonArray("roles").getList()
);
}
public Future<Boolean> authorize(io.vertx.ext.auth.User user, String permission) {
Promise<Boolean> promise = Promise.promise();
user.isAuthorized(permission)
.onSuccess(promise::complete)
.onFailure(promise::fail);
return promise.future();
}
public Future<Boolean> hasRole(io.vertx.ext.auth.User user, String role) {
Promise<Boolean> promise = Promise.promise();
user.isAuthorized("role:" + role)
.onSuccess(promise::complete)
.onFailure(promise::fail);
return promise.future();
}
}

OAuth2 Authentication Provider

OAuth2AuthProvider.java:

public class OAuth2AuthProvider {
private final OAuth2Auth oauth2;
private final AuthConfig config;
public OAuth2AuthProvider(Vertx vertx, AuthConfig config) {
this.config = config;
this.oauth2 = createOAuth2Provider(vertx);
}
private OAuth2Auth createOAuth2Provider(Vertx vertx) {
OAuth2Options options = new OAuth2Options()
.setClientId(config.getOAuth2Config().getString("clientId"))
.setClientSecret(config.getOAuth2Config().getString("clientSecret"))
.setSite(config.getOAuth2Config().getString("site"))
.setTokenPath("/oauth/token")
.setAuthorizationPath("/oauth/authorize")
.setUserInfoPath("/userinfo")
.setScope("profile email");
return OAuth2Auth.create(vertx, options);
}
public String getAuthorizationUrl(String redirectUri, String state) {
return oauth2.authorizeURL(new JsonObject()
.put("redirect_uri", redirectUri)
.put("scope", "profile email")
.put("state", state));
}
public Future<OAuth2Token> getToken(String code, String redirectUri) {
JsonObject tokenConfig = new JsonObject()
.put("code", code)
.put("redirect_uri", redirectUri);
return oauth2.authenticate(tokenConfig);
}
public Future<User> getUserInfo(OAuth2Token token) {
Promise<User> promise = Promise.promise();
oauth2.userInfo(token)
.onSuccess(userInfo -> {
User user = convertOAuth2User(userInfo);
promise.complete(user);
})
.onFailure(promise::fail);
return promise.future();
}
private User convertOAuth2User(JsonObject userInfo) {
return new User(
userInfo.getString("sub"),
userInfo.getString("name", userInfo.getString("preferred_username")),
userInfo.getString("email"),
new ArrayList<>(),
new ArrayList<>()
);
}
public OAuth2Auth getProvider() {
return oauth2;
}
}

JDBC Authentication Provider

JdbcAuthProvider.java:

public class JdbcAuthProvider {
private final JDBCAuth jdbcAuth;
private final PostgreSQLPool client;
public JdbcAuthProvider(Vertx vertx, AuthConfig config) {
this.client = createPgPool(vertx, config);
this.jdbcAuth = createJdbcAuth(vertx);
}
private PostgreSQLPool createPgPool(Vertx vertx, AuthConfig config) {
JsonObject dbConfig = config.getDatabaseConfig();
PoolOptions poolOptions = new PoolOptions()
.setMaxSize(10);
PostgreSQLConnectOptions connectOptions = new PostgreSQLConnectOptions()
.setPort(5432)
.setHost("localhost")
.setDatabase("authdb")
.setUser(dbConfig.getString("user"))
.setPassword(dbConfig.getString("password"));
return PostgreSQLPool.pool(vertx, connectOptions, poolOptions);
}
private JDBCAuth createJdbcAuth(Vertx vertx) {
JDBCAuth auth = JDBCAuth.create(vertx, client);
// Define authentication query strategy
HashStrategy strategy = HashStrategy.load();
// Set authentication query
auth.setAuthenticationQuery(
"SELECT password, salt FROM users WHERE username = ?");
// Set permissions query
auth.setPermissionsQuery(
"SELECT permission FROM user_permissions WHERE username = ?");
// Set roles query
auth.setRolesQuery(
"SELECT role FROM user_roles WHERE username = ?");
return auth;
}
public Future<io.vertx.ext.auth.User> authenticate(String username, String password) {
JsonObject authInfo = new JsonObject()
.put("username", username)
.put("password", password);
return jdbcAuth.authenticate(authInfo);
}
public Future<Void> createUser(User user, String password) {
Promise<Void> promise = Promise.promise();
HashStrategy strategy = HashStrategy.load();
// Generate salt and hash password
String salt = strategy.generateSalt();
String hashedPassword = strategy.computeHash(password, salt);
// Insert user
client.preparedQuery(
"INSERT INTO users (id, username, email, password, salt, created_at) " +
"VALUES ($1, $2, $3, $4, $5, $6)")
.execute(Tuple.of(
user.getId(),
user.getUsername(),
user.getEmail(),
hashedPassword,
salt,
java.time.Instant.now()))
.onSuccess(result -> promise.complete())
.onFailure(promise::fail);
return promise.future();
}
public Future<Void> addUserPermission(String username, String permission) {
return client.preparedQuery(
"INSERT INTO user_permissions (username, permission) VALUES ($1, $2)")
.execute(Tuple.of(username, permission))
.mapEmpty();
}
public Future<Void> addUserRole(String username, String role) {
return client.preparedQuery(
"INSERT INTO user_roles (username, role) VALUES ($1, $2)")
.execute(Tuple.of(username, role))
.mapEmpty();
}
public JDBCAuth getProvider() {
return jdbcAuth;
}
public PostgreSQLPool getClient() {
return client;
}
}

MongoDB Authentication Provider

MongoAuthProvider.java:

public class MongoAuthProvider {
private final MongoAuth mongoAuth;
private final MongoClient mongoClient;
public MongoAuthProvider(Vertx vertx, AuthConfig config) {
this.mongoClient = createMongoClient(vertx, config);
this.mongoAuth = createMongoAuth(vertx);
}
private MongoClient createMongoClient(Vertx vertx, AuthConfig config) {
JsonObject mongoConfig = config.getMongoConfig();
return MongoClient.create(vertx, mongoConfig);
}
private MongoAuth createMongoAuth(Vertx vertx) {
JsonObject authProperties = new JsonObject()
.put("collection_name", "users")
.put("username_field", "username")
.put("password_field", "password")
.put("role_field", "roles")
.put("permission_field", "permissions")
.put("username_credential_field", "username")
.put("password_credential_field", "password");
return MongoAuth.create(vertx, mongoClient, authProperties);
}
public Future<io.vertx.ext.auth.User> authenticate(String username, String password) {
JsonObject authInfo = new JsonObject()
.put("username", username)
.put("password", password);
return mongoAuth.authenticate(authInfo);
}
public Future<String> createUser(User user, String password) {
JsonObject userDocument = new JsonObject()
.put("_id", user.getId())
.put("username", user.getUsername())
.put("email", user.getEmail())
.put("password", password)
.put("roles", new JsonArray(user.getRoles()))
.put("permissions", new JsonArray(user.getPermissions()))
.put("createdAt", java.time.Instant.now().toString())
.put("enabled", true);
return mongoClient.save("users", userDocument)
.map(user.getId());
}
public Future<Void> addUserRole(String userId, String role) {
JsonObject query = new JsonObject().put("_id", userId);
JsonObject update = new JsonObject()
.put("$push", new JsonObject().put("roles", role));
return mongoClient.updateCollection("users", query, update)
.mapEmpty();
}
public Future<Void> addUserPermission(String userId, String permission) {
JsonObject query = new JsonObject().put("_id", userId);
JsonObject update = new JsonObject()
.put("$push", new JsonObject().put("permissions", permission));
return mongoClient.updateCollection("users", query, update)
.mapEmpty();
}
public MongoAuth getProvider() {
return mongoAuth;
}
}

Authentication Service

AuthService.java:

public class AuthService {
private final JwtAuthProvider jwtAuthProvider;
private final OAuth2AuthProvider oauth2Provider;
private final JdbcAuthProvider jdbcAuthProvider;
private final MongoAuthProvider mongoAuthProvider;
private final Vertx vertx;
public AuthService(Vertx vertx, AuthConfig config) {
this.vertx = vertx;
this.jwtAuthProvider = new JwtAuthProvider(vertx, config);
this.oauth2Provider = new OAuth2AuthProvider(vertx, config);
this.jdbcAuthProvider = new JdbcAuthProvider(vertx, config);
this.mongoAuthProvider = new MongoAuthProvider(vertx, config);
}
public Future<AuthResult> loginWithCredentials(String username, String password) {
Promise<AuthResult> promise = Promise.promise();
// Try JDBC authentication first
jdbcAuthProvider.authenticate(username, password)
.compose(user -> {
// Convert to application user and generate JWT
User appUser = extractUserInfo(user);
String token = jwtAuthProvider.generateToken(
appUser, 
appUser.getPermissions(), 
appUser.getRoles());
return Future.succeededFuture(new AuthResult(token, appUser));
})
.onFailure(jdbcFailure -> {
// Fallback to MongoDB authentication
mongoAuthProvider.authenticate(username, password)
.compose(user -> {
User appUser = extractUserInfo(user);
String token = jwtAuthProvider.generateToken(
appUser, 
appUser.getPermissions(), 
appUser.getRoles());
return Future.succeededFuture(new AuthResult(token, appUser));
})
.onSuccess(promise::complete)
.onFailure(promise::fail);
})
.onSuccess(promise::complete);
return promise.future();
}
public Future<AuthResult> loginWithOAuth2(String code, String redirectUri) {
return oauth2Provider.getToken(code, redirectUri)
.compose(token -> oauth2Provider.getUserInfo(token))
.compose(user -> {
// Create or update user in local database
return createOrUpdateUser(user)
.compose(updatedUser -> {
String jwtToken = jwtAuthProvider.generateToken(
updatedUser, 
updatedUser.getPermissions(), 
updatedUser.getRoles());
return Future.succeededFuture(new AuthResult(jwtToken, updatedUser));
});
});
}
public Future<AuthResult> validateToken(String token) {
return jwtAuthProvider.authenticate(token)
.map(user -> new AuthResult(token, user));
}
public Future<Boolean> authorize(String token, String permission) {
return jwtAuthProvider.authenticate(token)
.compose(user -> jwtAuthProvider.authorize(
convertToAuthUser(user), permission));
}
public Future<Boolean> hasRole(String token, String role) {
return jwtAuthProvider.authenticate(token)
.compose(user -> jwtAuthProvider.hasRole(
convertToAuthUser(user), role));
}
public Future<User> createUser(User user, String password, AuthProvider provider) {
switch (provider) {
case JDBC:
return jdbcAuthProvider.createUser(user, password)
.map(v -> user);
case MONGODB:
return mongoAuthProvider.createUser(user, password)
.map(id -> user);
default:
return Future.failedFuture("Unsupported provider: " + provider);
}
}
private User extractUserInfo(io.vertx.ext.auth.User authUser) {
JsonObject principal = authUser.principal();
List<String> permissions = principal.getJsonArray("permissions", new JsonArray())
.stream()
.map(Object::toString)
.collect(Collectors.toList());
List<String> roles = principal.getJsonArray("roles", new JsonArray())
.stream()
.map(Object::toString)
.collect(Collectors.toList());
return new User(
principal.getString("userId", principal.getString("sub")),
principal.getString("username", principal.getString("sub")),
principal.getString("email"),
permissions,
roles
);
}
private io.vertx.ext.auth.User convertToAuthUser(User user) {
JsonObject principal = new JsonObject()
.put("sub", user.getUsername())
.put("userId", user.getId())
.put("email", user.getEmail())
.put("permissions", new JsonArray(user.getPermissions()))
.put("roles", new JsonArray(user.getRoles()));
return io.vertx.ext.auth.User.create(principal);
}
private Future<User> createOrUpdateUser(User oauthUser) {
// Check if user exists
return jdbcAuthProvider.getClient()
.preparedQuery("SELECT id FROM users WHERE id = $1")
.execute(Tuple.of(oauthUser.getId()))
.compose(result -> {
if (result.size() == 0) {
// Create new user
return jdbcAuthProvider.createUser(oauthUser, UUID.randomUUID().toString())
.map(v -> oauthUser);
} else {
// Update existing user
return Future.succeededFuture(oauthUser);
}
});
}
public static class AuthResult {
private final String token;
private final User user;
public AuthResult(String token, User user) {
this.token = token;
this.user = user;
}
public String getToken() { return token; }
public User getUser() { return user; }
}
public enum AuthProvider {
JDBC, MONGODB, JWT, OAUTH2
}
}

Vert.x Web Router with Auth

AuthRouter.java:

public class AuthRouter {
private final Router router;
private final AuthService authService;
private final JwtAuthProvider jwtAuthProvider;
public AuthRouter(Vertx vertx, AuthService authService, JwtAuthProvider jwtAuthProvider) {
this.router = Router.router(vertx);
this.authService = authService;
this.jwtAuthProvider = jwtAuthProvider;
setupRoutes();
}
private void setupRoutes() {
// JWT Auth handler
AuthHandler jwtAuthHandler = JWTAuthHandler.create(jwtAuthProvider.getProvider());
// Public routes
router.post("/api/auth/login").handler(this::handleLogin);
router.post("/api/auth/register").handler(this::handleRegister);
router.get("/api/auth/oauth2/authorize").handler(this::handleOAuth2Authorize);
router.get("/api/auth/oauth2/callback").handler(this::handleOAuth2Callback);
router.post("/api/auth/refresh").handler(this::handleTokenRefresh);
// Protected routes
router.route("/api/protected/*").handler(jwtAuthHandler);
router.get("/api/protected/profile").handler(this::handleGetProfile);
router.put("/api/protected/profile").handler(this::handleUpdateProfile);
router.get("/api/protected/users").handler(this::handleGetUsers);
// Role-based routes
router.route("/api/admin/*").handler(jwtAuthHandler);
router.route("/api/admin/*").handler(this::requireRole("ADMIN"));
router.get("/api/admin/users").handler(this::handleAdminGetUsers);
router.post("/api/admin/users").handler(this::handleAdminCreateUser);
// Permission-based routes
router.route("/api/secure/*").handler(jwtAuthHandler);
router.route("/api/secure/*").handler(this::requirePermission("SECURE_ACCESS"));
}
private void handleLogin(RoutingContext ctx) {
JsonObject body = ctx.getBodyAsJson();
String username = body.getString("username");
String password = body.getString("password");
authService.loginWithCredentials(username, password)
.onSuccess(authResult -> {
JsonObject response = new JsonObject()
.put("token", authResult.getToken())
.put("user", toJson(authResult.getUser()))
.put("expiresIn", 3600);
ctx.response()
.putHeader("Content-Type", "application/json")
.end(response.encode());
})
.onFailure(err -> {
ctx.response()
.setStatusCode(401)
.end(new JsonObject().put("error", "Invalid credentials").encode());
});
}
private void handleRegister(RoutingContext ctx) {
JsonObject body = ctx.getBodyAsJson();
String username = body.getString("username");
String email = body.getString("email");
String password = body.getString("password");
User user = new User(
UUID.randomUUID().toString(),
username,
email,
Arrays.asList("user:read", "user:write:self"),
Arrays.asList("USER")
);
authService.createUser(user, password, AuthService.AuthProvider.JDBC)
.onSuccess(createdUser -> {
ctx.response()
.setStatusCode(201)
.end(new JsonObject().put("message", "User created successfully").encode());
})
.onFailure(err -> {
ctx.response()
.setStatusCode(400)
.end(new JsonObject().put("error", err.getMessage()).encode());
});
}
private void handleOAuth2Authorize(RoutingContext ctx) {
String redirectUri = ctx.request().getParam("redirect_uri");
String state = UUID.randomUUID().toString();
String authUrl = authService.oauth2Provider.getAuthorizationUrl(redirectUri, state);
ctx.response()
.putHeader("Location", authUrl)
.setStatusCode(302)
.end();
}
private void handleOAuth2Callback(RoutingContext ctx) {
String code = ctx.request().getParam("code");
String state = ctx.request().getParam("state");
String redirectUri = ctx.request().getParam("redirect_uri");
authService.loginWithOAuth2(code, redirectUri)
.onSuccess(authResult -> {
// Redirect to frontend with token
String frontendUrl = redirectUri + "?token=" + authResult.getToken();
ctx.response()
.putHeader("Location", frontendUrl)
.setStatusCode(302)
.end();
})
.onFailure(err -> {
ctx.response()
.setStatusCode(400)
.end(new JsonObject().put("error", "OAuth2 authentication failed").encode());
});
}
private void handleTokenRefresh(RoutingContext ctx) {
String authHeader = ctx.request().getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring(7);
authService.validateToken(token)
.compose(authResult -> {
// Generate new token with extended expiry
User user = authResult.getUser();
String newToken = jwtAuthProvider.generateToken(
user, user.getPermissions(), user.getRoles());
return Future.succeededFuture(newToken);
})
.onSuccess(newToken -> {
JsonObject response = new JsonObject()
.put("token", newToken)
.put("expiresIn", 3600);
ctx.response()
.putHeader("Content-Type", "application/json")
.end(response.encode());
})
.onFailure(err -> {
ctx.response()
.setStatusCode(401)
.end(new JsonObject().put("error", "Invalid token").encode());
});
} else {
ctx.response()
.setStatusCode(401)
.end(new JsonObject().put("error", "Authorization header required").encode());
}
}
private void handleGetProfile(RoutingContext ctx) {
User user = extractUserFromContext(ctx);
JsonObject profile = new JsonObject()
.put("id", user.getId())
.put("username", user.getUsername())
.put("email", user.getEmail())
.put("roles", new JsonArray(user.getRoles()))
.put("permissions", new JsonArray(user.getPermissions()));
ctx.response()
.putHeader("Content-Type", "application/json")
.end(profile.encode());
}
private void handleUpdateProfile(RoutingContext ctx) {
User user = extractUserFromContext(ctx);
JsonObject body = ctx.getBodyAsJson();
// Update user logic here
ctx.response()
.end(new JsonObject().put("message", "Profile updated").encode());
}
private void handleGetUsers(RoutingContext ctx) {
// Only return users if user has permission
String token = extractToken(ctx);
authService.authorize(token, "user:read:all")
.onSuccess(authorized -> {
if (authorized) {
// Return all users
JsonArray users = new JsonArray()
.add(new JsonObject().put("id", "1").put("username", "user1"))
.add(new JsonObject().put("id", "2").put("username", "user2"));
ctx.response()
.putHeader("Content-Type", "application/json")
.end(users.encode());
} else {
ctx.response()
.setStatusCode(403)
.end(new JsonObject().put("error", "Insufficient permissions").encode());
}
})
.onFailure(err -> {
ctx.response()
.setStatusCode(403)
.end(new JsonObject().put("error", "Authorization failed").encode());
});
}
private void handleAdminGetUsers(RoutingContext ctx) {
// Admin-only endpoint
JsonArray users = new JsonArray()
.add(new JsonObject().put("id", "1").put("username", "admin"))
.add(new JsonObject().put("id", "2").put("username", "user"));
ctx.response()
.putHeader("Content-Type", "application/json")
.end(users.encode());
}
private void handleAdminCreateUser(RoutingContext ctx) {
JsonObject body = ctx.getBodyAsJson();
// Admin create user logic
ctx.response()
.setStatusCode(201)
.end(new JsonObject().put("message", "User created by admin").encode());
}
private Handler<RoutingContext> requireRole(String role) {
return ctx -> {
String token = extractToken(ctx);
authService.hasRole(token, role)
.onSuccess(hasRole -> {
if (hasRole) {
ctx.next();
} else {
ctx.response()
.setStatusCode(403)
.end(new JsonObject().put("error", "Insufficient role").encode());
}
})
.onFailure(err -> {
ctx.response()
.setStatusCode(403)
.end(new JsonObject().put("error", "Role check failed").encode());
});
};
}
private Handler<RoutingContext> requirePermission(String permission) {
return ctx -> {
String token = extractToken(ctx);
authService.authorize(token, permission)
.onSuccess(authorized -> {
if (authorized) {
ctx.next();
} else {
ctx.response()
.setStatusCode(403)
.end(new JsonObject().put("error", "Insufficient permissions").encode());
}
})
.onFailure(err -> {
ctx.response()
.setStatusCode(403)
.end(new JsonObject().put("error", "Permission check failed").encode());
});
};
}
private User extractUserFromContext(RoutingContext ctx) {
io.vertx.ext.auth.User authUser = ctx.user();
JsonObject principal = authUser.principal();
return new User(
principal.getString("userId"),
principal.getString("sub"),
principal.getString("email"),
principal.getJsonArray("permissions").getList(),
principal.getJsonArray("roles").getList()
);
}
private String extractToken(RoutingContext ctx) {
String authHeader = ctx.request().getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
return authHeader.substring(7);
}
return null;
}
private JsonObject toJson(User user) {
return new JsonObject()
.put("id", user.getId())
.put("username", user.getUsername())
.put("email", user.getEmail())
.put("roles", new JsonArray(user.getRoles()))
.put("permissions", new JsonArray(user.getPermissions()));
}
public Router getRouter() {
return router;
}
}

Main Verticle

AuthVerticle.java:

public class AuthVerticle extends AbstractVerticle {
private AuthService authService;
private JwtAuthProvider jwtAuthProvider;
private HttpServer server;
@Override
public void start(Promise<Void> startPromise) {
try {
AuthConfig config = new AuthConfig();
// Initialize auth providers
this.jwtAuthProvider = new JwtAuthProvider(vertx, config);
this.authService = new AuthService(vertx, config);
// Create router
AuthRouter authRouter = new AuthRouter(vertx, authService, jwtAuthProvider);
// Create HTTP server
this.server = vertx.createHttpServer();
server.requestHandler(authRouter.getRouter())
.listen(config.getHttpPort(), config.getHttpHost())
.onSuccess(http -> {
log.info("HTTP server started on port {}", config.getHttpPort());
startPromise.complete();
})
.onFailure(startPromise::fail);
} catch (Exception e) {
startPromise.fail(e);
}
}
@Override
public void stop(Promise<Void> stopPromise) {
if (server != null) {
server.close()
.onSuccess(v -> log.info("HTTP server stopped"))
.onFailure(stopPromise::fail)
.onComplete(v -> stopPromise.complete());
} else {
stopPromise.complete();
}
}
private static final Logger log = LoggerFactory.getLogger(AuthVerticle.class);
}

User Model

User.java:

public class User {
private final String id;
private final String username;
private final String email;
private final List<String> permissions;
private final List<String> roles;
public User(String id, String username, String email, 
List<String> permissions, List<String> roles) {
this.id = id;
this.username = username;
this.email = email;
this.permissions = permissions != null ? permissions : new ArrayList<>();
this.roles = roles != null ? roles : new ArrayList<>();
}
// Getters
public String getId() { return id; }
public String getUsername() { return username; }
public String getEmail() { return email; }
public List<String> getPermissions() { return permissions; }
public List<String> getRoles() { return roles; }
public void addPermission(String permission) {
this.permissions.add(permission);
}
public void addRole(String role) {
this.roles.add(role);
}
public boolean hasPermission(String permission) {
return permissions.contains(permission);
}
public boolean hasRole(String role) {
return roles.contains(role);
}
}

Application Runner

Application.java:

public class Application {
public static void main(String[] args) {
Vertx vertx = Vertx.vertx(new VertxOptions()
.setEventLoopPoolSize(4)
.setWorkerPoolSize(20));
// Deploy auth verticle
vertx.deployVerticle(new AuthVerticle())
.onSuccess(deploymentId -> {
System.out.println("Auth verticle deployed with ID: " + deploymentId);
})
.onFailure(err -> {
System.err.println("Failed to deploy auth verticle: " + err.getMessage());
err.printStackTrace();
});
// Add shutdown hook
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
vertx.close();
}));
}
}

Best Practices

  1. Use environment variables for sensitive configuration
  2. Implement rate limiting on authentication endpoints
  3. Use secure password hashing with proper salts
  4. Implement token blacklisting for logout functionality
  5. Use HTTPS in production environments
  6. Monitor authentication attempts for security auditing
  7. Implement proper error handling without leaking sensitive information

Conclusion

Vert.x Auth provides a comprehensive, asynchronous authentication and authorization framework that:

  • Supports multiple authentication providers (JWT, OAuth2, JDBC, MongoDB)
  • Provides role-based and permission-based authorization
  • Offers non-blocking, reactive operations
  • Integrates seamlessly with Vert.x Web
  • Supports token-based authentication with JWT
  • Enables OAuth2 integration for social login

This implementation demonstrates how to build a secure, scalable authentication service using Vert.x that can handle various authentication scenarios while maintaining high performance and reliability.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper