diff --git a/gms/src/main/java/it/inaf/ia2/gms/authn/SecurityConfig.java b/gms/src/main/java/it/inaf/ia2/gms/authn/SecurityConfig.java
index c1b4c6a54f6cf8b42b984e0f92cef3b5059b235f..8da99f12dcf06195ad4d7493309530cfaf19f9b3 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/authn/SecurityConfig.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/authn/SecurityConfig.java
@@ -12,6 +12,7 @@ import org.springframework.core.Ordered;
 import org.springframework.core.env.Environment;
 import org.springframework.http.HttpMethod;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.builders.WebSecurity;
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
 import org.springframework.web.cors.CorsConfiguration;
 import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
@@ -32,6 +33,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
         super.configure(http);
 
+        // CORS are necessary only for development (API access from npm server)
         if (Arrays.asList(env.getActiveProfiles()).contains("dev")) {
             http.authorizeRequests()
                     .antMatchers(HttpMethod.OPTIONS, "/**").permitAll();
@@ -40,6 +42,31 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
         http.csrf().disable();
     }
 
+    /**
+     * The authentication is ignored for these endpoints. The "/ws" endpoints
+     * (web service API for programmatic access) are protected by the custom
+     * WebServiceAuthorizationFilter that checks BasicAuth for GMS clients.
+     */
+    @Override
+    public void configure(WebSecurity web) throws Exception {
+        web.ignoring().antMatchers("/ws/**", "/error");
+    }
+
+    /**
+     * Checks the BasicAuth for GMS clients.
+     */
+    @Bean
+    public FilterRegistrationBean webServiceAuthorizationFilter() {
+        FilterRegistrationBean bean = new FilterRegistrationBean();
+        bean.setFilter(new WebServiceAuthorizationFilter());
+        bean.addUrlPatterns("/ws/*");
+        bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
+        return bean;
+    }
+
+    /**
+     * CORS are necessary only for development (API access from npm server).
+     */
     @Bean
     @Profile("dev")
     public FilterRegistrationBean corsFilter() {
diff --git a/gms/src/main/java/it/inaf/ia2/gms/authn/WebServiceAuthorizationFilter.java b/gms/src/main/java/it/inaf/ia2/gms/authn/WebServiceAuthorizationFilter.java
new file mode 100644
index 0000000000000000000000000000000000000000..cb9118519be15bd46acfffcfaf2aa6d1d72a8746
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/authn/WebServiceAuthorizationFilter.java
@@ -0,0 +1,94 @@
+package it.inaf.ia2.gms.authn;
+
+import it.inaf.ia2.gms.exception.UnauthorizedException;
+import it.inaf.ia2.gms.persistence.ClientsDAO;
+import it.inaf.ia2.gms.persistence.model.ClientEntity;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Base64;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.xml.bind.DatatypeConverter;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.web.context.WebApplicationContext;
+import org.springframework.web.context.support.WebApplicationContextUtils;
+
+public class WebServiceAuthorizationFilter implements Filter {
+
+    @Override
+    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
+
+        HttpServletRequest request = (HttpServletRequest) req;
+
+        if (request.getServletPath().startsWith("/ws/")) {
+            try {
+                validateBasicAuth(request);
+            } catch (UnauthorizedException ex) {
+                ((HttpServletResponse) res).sendError(HttpServletResponse.SC_UNAUTHORIZED, ex.getMessage());
+                return;
+            }
+        }
+
+        chain.doFilter(req, res);
+    }
+
+    private void validateBasicAuth(HttpServletRequest request) {
+
+        String token = getBasicAuthToken(request);
+
+        int delim = token.indexOf(":");
+
+        if (delim == -1) {
+            throw new BadCredentialsException("Invalid basic authentication token");
+        }
+
+        String clientId = token.substring(0, delim);
+        String clientSecret = token.substring(delim + 1);
+
+        ClientsDAO clientsDAO = getClientsDAO(request);
+
+        ClientEntity client = clientsDAO.findClientById(clientId)
+                .orElseThrow(() -> new BadCredentialsException("Client " + clientId + " not found"));
+
+        String shaSecret = getSha256(clientSecret);
+        if (!shaSecret.equals(client.getSecret())) {
+            throw new UnauthorizedException("Wrong secret");
+        }
+    }
+
+    private String getBasicAuthToken(HttpServletRequest request) {
+
+        String header = request.getHeader("Authorization");
+
+        if (header == null || !header.toLowerCase().startsWith("basic ")) {
+            throw new UnauthorizedException("Missing Authorization header");
+        }
+
+        byte[] base64Token = header.substring(6).getBytes(StandardCharsets.UTF_8);
+        byte[] decoded = Base64.getDecoder().decode(base64Token);
+
+        return new String(decoded, StandardCharsets.UTF_8);
+    }
+
+    protected ClientsDAO getClientsDAO(HttpServletRequest request) {
+        WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());
+        return webApplicationContext.getBean(ClientsDAO.class);
+    }
+
+    private static String getSha256(String secret) {
+        try {
+            MessageDigest md = MessageDigest.getInstance("SHA-256");
+            byte[] sha = md.digest(secret.getBytes(StandardCharsets.UTF_8));
+            return DatatypeConverter.printHexBinary(sha).toLowerCase();
+        } catch (NoSuchAlgorithmException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsController.java b/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsController.java
index 8eb6f6469056335110af38d961de532da5d60067..11b05b0fd4492be14f7c2fd4aa507d57338e29f0 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsController.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsController.java
@@ -1,17 +1,20 @@
 package it.inaf.ia2.gms.controller;
 
 import it.inaf.ia2.gms.authn.SessionData;
+import it.inaf.ia2.gms.exception.UnauthorizedException;
 import it.inaf.ia2.gms.model.CreateGroupRequest;
 import it.inaf.ia2.gms.model.GroupNode;
 import it.inaf.ia2.gms.model.GroupsModelRequest;
 import it.inaf.ia2.gms.model.GroupsModelResponse;
 import it.inaf.ia2.gms.model.PaginatedData;
 import it.inaf.ia2.gms.model.PaginatedModelRequest;
+import it.inaf.ia2.gms.model.Permission;
 import it.inaf.ia2.gms.model.RenameGroupRequest;
 import it.inaf.ia2.gms.persistence.model.GroupEntity;
 import it.inaf.ia2.gms.service.GroupsModelBuilder;
 import it.inaf.ia2.gms.service.GroupsService;
 import it.inaf.ia2.gms.service.GroupsTreeBuilder;
+import it.inaf.ia2.gms.service.PermissionsService;
 import javax.validation.Valid;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
@@ -31,6 +34,9 @@ public class GroupsController {
     @Autowired
     private SessionData session;
 
+    @Autowired
+    private PermissionsService permissionsService;
+
     @Autowired
     private GroupsService groupsService;
 
@@ -48,7 +54,13 @@ public class GroupsController {
     @PostMapping(value = "/group", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
     public ResponseEntity<PaginatedData<GroupNode>> createGroup(@Valid @RequestBody CreateGroupRequest request) {
 
-        GroupEntity newGroup = groupsService.addGroup(request.getParentGroupId(), request.getNewGroupName(), session.getUserId());
+        GroupEntity parent = groupsService.getGroupById(request.getParentGroupId());
+
+        if (permissionsService.getGroupPermission(parent, session.getUserId()) != Permission.ADMIN) {
+            throw new UnauthorizedException("Missing admin privileges");
+        }
+
+        GroupEntity newGroup = groupsService.addGroup(parent, request.getNewGroupName());
 
         PaginatedData<GroupNode> groupsPanel = getGroupsPanel(request.getParentGroupId(), request);
 
diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/WebServiceController.java b/gms/src/main/java/it/inaf/ia2/gms/controller/WebServiceController.java
new file mode 100644
index 0000000000000000000000000000000000000000..a6ff8c83e3d6758c542ccec4d434595d0b4ba31c
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/controller/WebServiceController.java
@@ -0,0 +1,66 @@
+package it.inaf.ia2.gms.controller;
+
+import it.inaf.ia2.gms.persistence.model.GroupEntity;
+import it.inaf.ia2.gms.service.GroupsService;
+import java.util.List;
+import java.util.Optional;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/ws")
+public class WebServiceController {
+
+    @Autowired
+    private GroupsService groupsService;
+
+    /**
+     * Creates a group and its ancestors if they are missing. It doesn't fail if
+     * the last group already exists.
+     */
+    @PostMapping("/group")
+    public ResponseEntity<GroupEntity> createGroup(@RequestBody List<String> names) {
+
+        GroupEntity group = groupsService.getRoot();
+
+        for (String name : names) {
+            Optional<GroupEntity> optGroup = groupsService.findGroupByParentAndName(group, name);
+            if (optGroup.isPresent()) {
+                group = optGroup.get();
+            } else {
+                group = groupsService.addGroup(group, name);
+            }
+        }
+
+        return new ResponseEntity<>(group, HttpStatus.CREATED);
+    }
+
+    public void deleteGroup() {
+
+    }
+
+    public void addMember() {
+
+    }
+
+    public void removeMember() {
+
+    }
+
+    public void addPrivilege() {
+
+    }
+
+    public void deletePrivilege() {
+
+    }
+
+    public void prepareToJoin() {
+
+    }
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/ClientsDAO.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/ClientsDAO.java
new file mode 100644
index 0000000000000000000000000000000000000000..0ceb032665fcd9b14a0f9ff2719f71f0fd3e9e4e
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/ClientsDAO.java
@@ -0,0 +1,58 @@
+package it.inaf.ia2.gms.persistence;
+
+import it.inaf.ia2.gms.persistence.model.ClientEntity;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import javax.sql.DataSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Component;
+
+@Component
+public class ClientsDAO {
+
+    private final JdbcTemplate jdbcTemplate;
+
+    @Autowired
+    public ClientsDAO(DataSource dataSource) {
+        jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    public Optional<ClientEntity> findClientById(String clientId) {
+
+        String sql = "SELECT client_secret, allowed_actions, ip_filter FROM gms_client WHERE client_id = ?";
+
+        return jdbcTemplate.query(conn -> {
+            PreparedStatement ps = conn.prepareStatement(sql);
+            ps.setString(1, clientId);
+            return ps;
+        }, resultSet -> {
+            if (resultSet.next()) {
+                ClientEntity client = new ClientEntity();
+                client.setId(clientId);
+                client.setSecret(resultSet.getString("client_secret"));
+                client.setAllowedActions(getAllowedActions(resultSet));
+                client.setIpFilter(resultSet.getString("ip_filter"));
+                return Optional.of(client);
+            }
+            return Optional.empty();
+        });
+    }
+
+    private List<String> getAllowedActions(ResultSet resultSet) throws SQLException {
+
+        List<String> actions = new ArrayList<>();
+
+        ResultSet items = resultSet.getArray("allowed_actions").getResultSet();
+        while (items.next()) {
+            String action = items.getString(1);
+            actions.add(action);
+        }
+
+        return actions;
+    }
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/GroupsDAO.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/GroupsDAO.java
index 88f85098ffd628212f9d6db6bfee2be091c9791f..9f84eaed7a9a47cd1ffc4f6f1b02ff6f58891eb6 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/persistence/GroupsDAO.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/GroupsDAO.java
@@ -101,6 +101,27 @@ public class GroupsDAO {
         });
     }
 
+    public Optional<GroupEntity> findGroupByParentAndName(String parentPath, String childName) {
+
+        String sql = "SELECT id, path from gms_group WHERE name = ? AND path ~ ?";
+
+        return jdbcTemplate.query(conn -> {
+            PreparedStatement ps = conn.prepareStatement(sql);
+            ps.setString(1, childName);
+            ps.setObject(2, getSubGroupsPath(parentPath), Types.OTHER);
+            return ps;
+        }, resultSet -> {
+            if (resultSet.next()) {
+                GroupEntity group = new GroupEntity();
+                group.setId(resultSet.getString("id"));
+                group.setName(childName);
+                group.setPath(resultSet.getString("path"));
+                return Optional.of(group);
+            }
+            return Optional.empty();
+        });
+    }
+
     public List<GroupEntity> listSubGroups(String path) {
 
         String sql = "SELECT id, name, path from gms_group WHERE path ~ ? ORDER BY name";
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/ClientEntity.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/ClientEntity.java
new file mode 100644
index 0000000000000000000000000000000000000000..eb00aa98d8c87699dae2179af96e4e7ca965f3a7
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/ClientEntity.java
@@ -0,0 +1,43 @@
+package it.inaf.ia2.gms.persistence.model;
+
+import java.util.List;
+
+public class ClientEntity {
+
+    private String id;
+    private String secret;
+    private List<String> allowedActions;
+    private String ipFilter;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getSecret() {
+        return secret;
+    }
+
+    public void setSecret(String secret) {
+        this.secret = secret;
+    }
+
+    public List<String> getAllowedActions() {
+        return allowedActions;
+    }
+
+    public void setAllowedActions(List<String> allowedActions) {
+        this.allowedActions = allowedActions;
+    }
+
+    public String getIpFilter() {
+        return ipFilter;
+    }
+
+    public void setIpFilter(String ipFilter) {
+        this.ipFilter = ipFilter;
+    }
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/service/GroupsService.java b/gms/src/main/java/it/inaf/ia2/gms/service/GroupsService.java
index e37a4b6427674cc744314ad6e21c8891727d93de..12aa21656a099857ef6f1a67fa7c42e56de97876 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/service/GroupsService.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/service/GroupsService.java
@@ -9,6 +9,7 @@ import it.inaf.ia2.gms.model.Permission;
 import it.inaf.ia2.gms.persistence.GroupsDAO;
 import it.inaf.ia2.gms.persistence.model.GroupEntity;
 import java.util.List;
+import java.util.Optional;
 import java.util.UUID;
 
 @Service
@@ -37,13 +38,7 @@ public class GroupsService {
         }
     }
 
-    public GroupEntity addGroup(String parentId, String groupName, String userId) {
-
-        GroupEntity parent = getGroupById(parentId);
-
-        if (permissionsService.getGroupPermission(parent, userId) != Permission.ADMIN) {
-            throw new UnauthorizedException("Missing admin privileges");
-        }
+    public GroupEntity addGroup(GroupEntity parent, String groupName) {
 
         if (groupsDAO.listSubGroups(parent.getPath()).stream()
                 .anyMatch(g -> g.getName().equals(groupName))) {
@@ -107,6 +102,10 @@ public class GroupsService {
         return parent;
     }
 
+    public GroupEntity getRoot() {
+        return getGroupById(ROOT);
+    }
+
     public GroupEntity getGroupById(String groupId) {
         return groupsDAO.findGroupById(groupId)
                 .orElseThrow(() -> new BadRequestException("Group " + groupId + " not found"));
@@ -120,4 +119,8 @@ public class GroupsService {
     public List<GroupBreadcrumb> getBreadcrumbs(String path) {
         return groupsDAO.getBreadcrumbs(path);
     }
+
+    public Optional<GroupEntity> findGroupByParentAndName(GroupEntity parent, String childName) {
+        return groupsDAO.findGroupByParentAndName(parent.getPath(), childName);
+    }
 }
diff --git a/gms/src/main/resources/sql/init.sql b/gms/src/main/resources/sql/init.sql
index 737c319d7d89f20011eef0c176823e2d2f689a1b..55d7def759ac75b3b19d3eafbfd635ff2632868e 100644
--- a/gms/src/main/resources/sql/init.sql
+++ b/gms/src/main/resources/sql/init.sql
@@ -29,3 +29,11 @@ CREATE TABLE gms_permission (
   foreign key (group_id) references gms_group(id),
   foreign key (group_path) references gms_group(path)
 );
+
+CREATE TABLE gms_client (
+  client_id varchar NOT NULL,
+  client_secret varchar NOT NULL,
+  allowed_actions text[] NOT NULL,
+  ip_filter text NULL,
+  primary key (client_id)
+);
\ No newline at end of file
diff --git a/gms/src/test/java/it/inaf/ia2/gms/authn/WebServiceAuthorizationFilterTest.java b/gms/src/test/java/it/inaf/ia2/gms/authn/WebServiceAuthorizationFilterTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..e3ff8469c4f487033a8cb4b5b6eb85296b185a20
--- /dev/null
+++ b/gms/src/test/java/it/inaf/ia2/gms/authn/WebServiceAuthorizationFilterTest.java
@@ -0,0 +1,98 @@
+package it.inaf.ia2.gms.authn;
+
+import it.inaf.ia2.gms.persistence.ClientsDAO;
+import it.inaf.ia2.gms.persistence.model.ClientEntity;
+import java.util.Collections;
+import java.util.Optional;
+import javax.servlet.FilterChain;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(JUnit4.class)
+public class WebServiceAuthorizationFilterTest {
+
+    private WebServiceAuthorizationFilter filter;
+
+    private HttpServletRequest request;
+    private HttpServletResponse response;
+    private FilterChain chain;
+
+    @Before
+    public void setUp() {
+
+        ClientsDAO clientsDAO = mock(ClientsDAO.class);
+
+        ClientEntity client = new ClientEntity();
+        client.setId("test");
+        client.setSecret("5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"); // sha256 of "password"
+        client.setAllowedActions(Collections.singletonList("*"));
+
+        when(clientsDAO.findClientById("test")).thenReturn(Optional.of(client));
+
+        filter = spy(new WebServiceAuthorizationFilter());
+        doReturn(clientsDAO).when(filter).getClientsDAO(any());
+
+        request = mock(HttpServletRequest.class);
+        response = mock(HttpServletResponse.class);
+        chain = mock(FilterChain.class);
+    }
+
+    @Test
+    public void testValidCredentials() throws Exception {
+
+        when(request.getServletPath()).thenReturn("/ws/group");
+        when(request.getHeader("Authorization")).thenReturn("Basic dGVzdDpwYXNzd29yZA=="); // test:password
+
+        filter.doFilter(request, response, chain);
+
+        verify(chain, times(1)).doFilter(any(), any());
+    }
+
+    @Test
+    public void testInvalidCredentials() throws Exception {
+
+        when(request.getServletPath()).thenReturn("/ws/group");
+        when(request.getHeader("Authorization")).thenReturn("Basic dGVzdDp0ZXN0"); // test:test
+
+        filter.doFilter(request, response, chain);
+
+        verify(response, times(1)).sendError(eq(HttpServletResponse.SC_UNAUTHORIZED), any());
+        verify(chain, never()).doFilter(any(), any());
+    }
+
+    @Test
+    public void testMissingHeader() throws Exception {
+
+        when(request.getServletPath()).thenReturn("/ws/group");
+
+        filter.doFilter(request, response, chain);
+
+        verify(response, times(1)).sendError(eq(HttpServletResponse.SC_UNAUTHORIZED), any());
+        verify(chain, never()).doFilter(any(), any());
+    }
+
+    @Test
+    public void testOutsidePath() throws Exception {
+
+        when(request.getServletPath()).thenReturn("/other/path");
+
+        filter.doFilter(request, response, chain);
+
+        verify(response, never()).sendError(anyInt(), any());
+        verify(chain, times(1)).doFilter(any(), any());
+    }
+}
diff --git a/gms/src/test/java/it/inaf/ia2/gms/persistence/GroupsDAOTest.java b/gms/src/test/java/it/inaf/ia2/gms/persistence/GroupsDAOTest.java
index 9d5ad1aa45da89c863eca21885e10d3d24b922d4..ae10ed1ef8f51555b6e7e356ae2737b835c919ca 100644
--- a/gms/src/test/java/it/inaf/ia2/gms/persistence/GroupsDAOTest.java
+++ b/gms/src/test/java/it/inaf/ia2/gms/persistence/GroupsDAOTest.java
@@ -88,6 +88,15 @@ public class GroupsDAOTest {
         assertEquals(1, groups.size());
         assertEquals("INAF", groups.get(0).getName());
 
+        // Group by parent and name
+        Optional<GroupEntity> optGroup = dao.findGroupByParentAndName(root.getPath(), lbt.getName());
+        assertTrue(optGroup.isPresent());
+        assertEquals(lbt.getId(), optGroup.get().getId());
+
+        optGroup = dao.findGroupByParentAndName(lbt.getPath(), lbtInaf.getName());
+        assertTrue(optGroup.isPresent());
+        assertEquals(lbtInaf.getId(), optGroup.get().getId());
+
         // Children map
         Map<String, Boolean> childrenMap = dao.getHasChildrenMap(Sets.newSet(root.getId()));
         assertEquals(1, childrenMap.size());
diff --git a/gms/src/test/java/it/inaf/ia2/gms/persistence/NestedGroupsIntegrationTest.java b/gms/src/test/java/it/inaf/ia2/gms/persistence/NestedGroupsIntegrationTest.java
index 398cd3d97aeda2fc2dbdb3bf5d9aee3872ecb1a9..0a90226cd21fc2097aaefc54c39de43277c5c58f 100644
--- a/gms/src/test/java/it/inaf/ia2/gms/persistence/NestedGroupsIntegrationTest.java
+++ b/gms/src/test/java/it/inaf/ia2/gms/persistence/NestedGroupsIntegrationTest.java
@@ -49,13 +49,13 @@ public class NestedGroupsIntegrationTest {
         permissionsDAO.createPermission(superAdminPermission);
 
         // Setup groups
-        GroupEntity root = groupsService.getGroupById(GroupsService.ROOT);
-        GroupEntity lbt = groupsService.addGroup(GroupsService.ROOT, "LBT", userId);
-        GroupEntity tng = groupsService.addGroup(GroupsService.ROOT, "TNG", userId);
-        GroupEntity radio = groupsService.addGroup(GroupsService.ROOT, "Radio", userId);
-        GroupEntity lbtInaf = groupsService.addGroup(lbt.getId(), "INAF", userId);
-        GroupEntity lbtInafProgram1 = groupsService.addGroup(lbtInaf.getId(), "P1", userId);
-        GroupEntity lbtInafProgram2 = groupsService.addGroup(lbtInaf.getId(), "P2", userId);
+        GroupEntity root = groupsService.getRoot();
+        GroupEntity lbt = groupsService.addGroup(root, "LBT");
+        GroupEntity tng = groupsService.addGroup(root, "TNG");
+        GroupEntity radio = groupsService.addGroup(root, "Radio");
+        GroupEntity lbtInaf = groupsService.addGroup(lbt, "INAF");
+        GroupEntity lbtInafProgram1 = groupsService.addGroup(lbtInaf, "P1");
+        GroupEntity lbtInafProgram2 = groupsService.addGroup(lbtInaf, "P2");
 
         PaginatedModelRequest request = new PaginatedModelRequest();
         request.setPaginatorPage(1);