Neo4j 4: Drivers and Authorization

Michal Trnka Luanne Misquitta

by Michal Trnka, Luanne Misquitta

Neo4j 4.0 has just been released with a key feature: graph and sub-graph access control. Access to certain labels or relationship types or properties can now be handled at the database level, resulting in developers not having to deal with complex security logic in their code, and also providing a more consistent and performant solution.

Users connecting directly to Neo4j with their Neo4j user credentials either via the browser, or standalone visualisation tools will only have access to the sub-graph as permitted by the role(s) assigned to them. But what about applications that usually abstract away the database user credentials and connect to Neo4j by supplying pre-configured Neo4j user credentials to the driver? Typically, a single database user is configured, but this won’t work if application users all have different privileges. Since authentication is part of the driver set up, drivers need to know which application user is querying the graph to be able to enforce graph security.

We explored some options that we briefly examine in this blog post. Sample code is available in our GitHub repository.

Drivers Provider

Driver-per-request

In this approach, a new driver is instantiated for every user request. Whilst this is straightforward and works, it is not recommended. The creation and destruction of the driver appears to add a significant amount of time, slowing down requests, up to a second on our developer machines. Drivers must also be closed properly to prevent exhaustion of the connection limit faster than expected.

Cached Drivers

This approach better encapsulates the driver assignment logic, we cache the driver instead of creating and destroying it for every request. If the driver exists, it is re-used, and if not, a new one is created using the credentials of the user.

In our example using Spring, we configure a bean factory that will provide a request scoped Driver bean created using the DriverProvider.

    @Bean(destroyMethod = "", name = "provider")
    @RequestScope
    public Driver createDriver(DriverProvider driverProvider) {
        return driverProvider.getDriver();
    }

The Driver is then auto-wired as usual. The DriverProvider chooses the right instance of the Driver to return based on some contextual value, for example, a username stored in the SecurityContext. Note that we also disabled the destroy method to prevent Spring from closing the driver.

This logic could be extended easily to group application users by their roles, and hence use a mapping of application users to a fixed set of database users.

One thing to keep in mind is that the drivers may need to be purged from the cache and destroyed after some period of time, to avoid hitting the connection limit. Also, care should be taken that the caching store is thread-safe.

AbstractRoutingDriver

For a more Springy approach, we can implement the Driver interface, much like the AbstractRoutingDataSource class to have a wrapping Driver that would dynamically select the target driver and return a Session. This is a non-invasive approach that would require no change, apart from configuration, to existing code such as repositories.

Here as well, one must make sure to implement close methods correctly.

Conclusion

For applications in highly secure spaces such as law enforcement and intelligence to take advantage of the new security model in Neo4j 4, we’ve worked out a couple of potential solutions which involve unifying database and application users.

Reach out to us if you have other interesting ways to deal with this!

Share this blog post:

comments powered by Disqus