This Vert.x service provides authentication and authorisation functionality and the concept of a login session for use in your Vert.x applications
The auth service provides a common interface that can be backed by different auth providers.
Vert.x ships with a
default implementation that uses Apache Shiro but you can provide your own implementation
by implementing the AuthProvider
interface.
The AuthProvider
interface can also be used directory in your applications if you just want
raw authentication and authorisation functionality without the concept of a login session.
The Vert.x Apache Shiro implementation currently allows user/role/permission information to be accessed from simple properties files or LDAP servers.
Basic concepts
Authentication (aka log in) means verifying the identity of a user.
Authorisation means verifying a user has the correct access rights for some resource.
The service uses a familiar user/role/permission model that you will probably know already:
Users can have zero or more roles, e.g. "manager", "developer".
Roles can have zero or more permissions, e.g. a manager might have permission "approve expenses", "conduct_reviews", and a developer might have a permission "commit_code".
Setting up the service
As with other services you can use the service either by deploying it as a verticle somewhere on your network and interacting with it over the event bus, either directly by sending messages, or using a service proxy, e.g.
Somewhere you deploy it:
// Deploy service - can be anywhere on your network
var options = {
"config" : config
};
vertx.deployVerticle("service:io.vertx.shiro-auth-service", options, function (res, res_err) {
if (res_err == null) {
// Deployed ok
} else {
// Failed to deploy
};
});
Now you can either send messages to it directly over the event bus, or you can create a proxy to the service from wherever you are and just use that:
var AuthService = require("vertx-auth-js/auth_service");
var proxy = AuthService.createEventBusProxy(vertx, "vertx.auth");
// Now do stuff with it:
var principal = {
"username" : "tim"
};
var credentials = {
"password" : "sausages"
};
proxy.login(principal, credentials, function (res, res_err) {
// etc
});
Alternatively you can create an instance of the service directly and just use that locally:
var ShiroAuthService = require("vertx-auth-js/shiro_auth_service");
var config = {
};
config."properties_path" = "classpath:test-auth.properties";
var authService = ShiroAuthService.create(vertx, 'PROPERTIES', config);
authService.start();
// Now do stuff with it:
var principal = {
"username" : "tim"
};
var credentials = {
"password" : "sausages"
};
authService.login(principal, credentials, function (res, res_err) {
// etc
});
If you create an instance this way you should make sure you start it with start
before you use it.
However you do it, once you’ve got your service you can start using it.
TODO these docs need to be refactored to talk about AuthProvider and AuthService as different ways of using it
Shiro Auth Service
As previously mentioned we provide an implementation of the Auth service that uses Apache Shiro to perform the actual auth.
To use this, you should use ShiroAuthService
.
This currently supports properties file based user/role/permission information and using LDAP, and you can also pass
in a pre-existing Shiro Realm
instance or implement ShiroAuthRealm
to implement
a different method of auth using Shiro.
Shiro Auth Service Verticle
As with most services you can deploy the service somewhere on your network and interact with it via a proxy, here’s an example of deploying a Shiro auth service verticle:
vertx.deployVerticle("service:io.vertx.shiro-auth-service");
Properties auth realm
The properties auth realm gets user/role/permission information from a properties file.
Here’s an example that uses the out of the box properties auth realm:
var ShiroAuthService = require("vertx-auth-js/shiro_auth_service");
var config = {
};
config."properties_path" = "classpath:test-auth.properties";
var authService = ShiroAuthService.create(vertx, 'PROPERTIES', config);
authService.start();
// Now do stuff with it:
var principal = {
"username" : "tim"
};
var credentials = {
"password" : "sausages"
};
authService.login(principal, credentials, function (res, res_err) {
// etc
});
The properties auth realm will, by default, look for a file called vertx-users.properties
on the classpath.
If you want to change this, you can use the properties_path
configuration element to define how the properties
file is found.
The default value is classpath:vertx-users.properties
.
If the value is prefixed with classpath:
then the classpath will be searched for a properties file of that name.
If the value is prefixed with file:
then it specifies a file on the file system.
If the value is prefixed with url:
then it specifies a URL from where to load the properties.
The properties file should have the following structure:
Each line should either contain the username, password and roles for a user or the permissions in a role.
For a user line it should be of the form:
user.{username}={password},{roleName1},{roleName2},...,{roleNameN}
For a role line it should be of the form:
role.{roleName}={permissionName1},{permissionName2},...,{permissionNameN}
Here’s an example:
user.tim = mypassword,administrator,developer user.bob = hispassword,developer user.joe = anotherpassword,manager role.administrator=* role.manager=play_golf,say_buzzwords role.developer=do_actual_work
When describing roles a wildcard *
can be used to indicate that the role has all permissions
LDAP auth realm
The LDAP auth realm gets user/role/permission information from an LDAP server.
The following configuration properties are used to configure the LDAP realm:
ldap-user-dn-template
-
this is used to determine the actual lookup to use when looking up a user with a particular id. An example is
uid={0},ou=users,dc=foo,dc=com
- the element{0}
is substituted with the user id to create the actual lookup. This setting is mandatory. ldap_url
-
the url to the LDAP server. The url must start with
ldap://
and a port must be specified. An example isldap:://myldapserver.mycompany.com:10389
ldap-authentication-mechanism
-
TODO
ldap-context-factory-class-name
-
TODO
ldap-pooling-enabled
-
TODO
ldap-referral
-
TODO
ldap-system-username
-
TODO
ldap-system-password
-
TODO
Using non Shiro Auth implementations
If you want to use a different auth provider with the Auth service, you should implement AuthProvider
.
You can then create a local instance of the AuthService with:
var AuthService = require("vertx-auth-js/auth_service");
var authService = AuthService.create(vertx, myAuthProvider);
authService.start();
Or to to deploy an verticle instance:
var config = {
};
config."provider_class_name" = "com.mycompany.myproject.MyAuthProviderClass";
config."your_config_property" = "blah";
var options = {
"config" : config
};
vertx.deployVerticle("service:io.vertx.auth-service", options);
Using the API
The auth service API is described with AuthService
.
It contains method to login and check roles and permissions.
Authentication - login / logout
You use login
to login a user. The arguments to log-in are a JsonObject
representing the principal (principal is a fancy name for a unique id, e.g. username representing the user), and
another JsonObject
representing the credentials (e.g. password) of the user.
Often the principal will just contain a username
string field - the value containing the username and this is what is
expected by the out of the box Apache Shiro provider, but other providers might represent principals in other ways.
Similarly, the credentials will often just be a password
string field - the value containing a password but other
providers might use other data for credentials that’s why we keep it as a general JSON object.
The result of the login is returned in the result handler. If the login is successful a string login-ID will be returned as the result. This is a unique secure UUID that identifies the login session. The login ID should be used if you later want to authorise the user, i.e. check whether they have permissions or roles.
Here’s an example of a login:
var principal = {
"username" : "tim"
};
var credentials = {
"password" : "sausages"
};
authService.login(principal, credentials, function (res, res_err) {
if (res_err == null) {
// Login successful!
// The login ID is needed if you later want to authorise a user
var loginID = res;
} else {
// Login failed.
var reason = res_err.getMessage();
};
});
The login session ID provided at login will be valid as long as the login hasn’t timed out or been explicitly logged out.
The default time it remains valid is 30 minutes. If you want to use a different value of timeout you can specify that
by calling loginWithTimeout
.
To prevent a login timing out, you can call refreshLoginSession
specifying
the login ID. The login will timeout if it remains unrefreshed for greater than the timeout period.
authService.refreshLoginSession(loginID, function (res, res_err) {
if (res_err == null) {
// Refreshed ok
} else {
// Not refreshed ok - probably the login has already timed out or doesn't exist.
};
});
You can explicitly logout a user with logout
specifying the login ID:
authService.logout(loginID, function (res, res_err) {
if (res_err == null) {
// Logged out ok
} else {
// Failed to logout - probably the login has already timed out or doesn't exist.
};
});
Authorisation
Authorisation means checking whether the user has the right roles or permissions.
In order to check roles or permissions the user must first be logged-in and you must have a valid login session ID as described in the previous section.
To check if a user has a specific role you use hasRole
specifying the login ID
and the role.
The result of the check is returned in the handler. If the check didn’t occur - e.g. the login ID is not valid, a failure will be returned in the handler, otherwise it will return a boolean - true if the user has the role or false if they don’t have the role.
authService.hasRole(loginID, "manager", function (res, res_err) {
if (res_err == null) {
var hasRole = res;
if (hasRole) {
// do something
} else {
// do something else
};
} else {
// Something went wrong - maybe the user is not logged in?
};
});
You can also check multiple roles at the same time with hasRoles
. In this
case you will return a true result only if the user has all the specified roles.
In the same way as checking roles, you can check permissions too. To this you use
hasPermission
and
hasPermissions
in the exact same way as roles.
Authorisations are cached for the length of the login. This means that the first time you do authorisation for a user it will go the auth provider, but the second time you do it with the same roles and permissions it will not call the auth provider but will return the cached value.
This allows better performance but bear in mind that if the roles or permissions for a user change in the provider while the login session is valid and when they have already been cached in the auth service, then the auth service won’t see the changes in the provider until a new login session is started.