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
- Use environment variables for sensitive configuration
- Implement rate limiting on authentication endpoints
- Use secure password hashing with proper salts
- Implement token blacklisting for logout functionality
- Use HTTPS in production environments
- Monitor authentication attempts for security auditing
- 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.