diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/JWTWebServiceController.java b/gms/src/main/java/it/inaf/ia2/gms/controller/JWTWebServiceController.java
index 4f632772d2a040ef538775e086e0272136c574b8..7a56d7b84a457279c146eada6ebf876af00a2926 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/controller/JWTWebServiceController.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/controller/JWTWebServiceController.java
@@ -14,12 +14,14 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import javax.servlet.http.HttpServletResponse;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
@@ -64,6 +66,62 @@ public class JWTWebServiceController {
         }
     }
 
+    /**
+     * This endpoint is compliant with the IVOA GMS standard. Warning: for
+     * supporting the groups of groups (with dots inside) the path variable must
+     * be defined adding ".+", otherwise Spring will think it is a file
+     * extension (thanks https://stackoverflow.com/a/16333149/771431)
+     */
+    @GetMapping(value = "/search/{group:.+}", produces = MediaType.TEXT_PLAIN_VALUE)
+    public void isMemberOf(@PathVariable("group") String group, Principal principal, HttpServletResponse response) throws IOException {
+
+        String userId = principal.getName();
+
+        List<String> groupNames = extractGroupNames(group);
+
+        boolean isMemeber = false;
+        String parentPath = ""; // starting from ROOT
+        for (String groupName : groupNames) {
+            Optional<GroupEntity> optionalGroup = groupsDAO.findGroupByParentAndName(parentPath, groupName);
+            if (optionalGroup.isPresent()) {
+                GroupEntity groupEntity = optionalGroup.get();
+                parentPath = groupEntity.getPath();
+                isMemeber = membershipsDAO.isMemberOf(userId, groupEntity.getId());
+                if (isMemeber) {
+                    break;
+                }
+            } else {
+                break;
+            }
+        }
+
+        if (isMemeber) {
+            try (PrintWriter pw = new PrintWriter(response.getOutputStream())) {
+                pw.println(group);
+            }
+        }
+        // else: empty response (as defined by GMS standard)
+    }
+
+    private List<String> extractGroupNames(String groupStr) {
+
+        List<String> names = new ArrayList<>();
+        String currentName = "";
+        for (int i = 0; i < groupStr.length(); i++) {
+            char c = groupStr.charAt(i);
+            // dot is the group separator and it must be escaped if used inside
+            // group names
+            if (c == '.' && groupStr.charAt(i - 1) != '\\') {
+                names.add(currentName.replace("\\.", "."));
+                currentName = "";
+            } else {
+                currentName += c;
+            }
+        }
+        names.add(currentName);
+        return names;
+    }
+
     private Set<String> getAllIdentifiers(List<GroupEntity> groups) {
 
         Set<String> allIdentifiers = new HashSet<>();
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/LoggingDAO.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/LoggingDAO.java
index 5c05f1015cfc391349af49eeb372cdb894612eaa..ae23d57b9127831a17322cc876f22f2f929d24f3 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/persistence/LoggingDAO.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/LoggingDAO.java
@@ -53,16 +53,20 @@ public class LoggingDAO {
     }
 
     public void logAction(String action) {
-        String sql = "INSERT INTO audit_log (\"user\", action, ip_address) VALUES (?, ?, ?)";
+        try {
+            String sql = "INSERT INTO audit_log (\"user\", action, ip_address) VALUES (?, ?, ?)";
 
-        jdbcTemplate.update(conn -> {
-            PreparedStatement ps = conn.prepareStatement(sql);
-            int i = 0;
-            ps.setString(++i, getUser());
-            ps.setString(++i, action);
-            ps.setString(++i, getIPAddress());
-            return ps;
-        });
+            jdbcTemplate.update(conn -> {
+                PreparedStatement ps = conn.prepareStatement(sql);
+                int i = 0;
+                ps.setString(++i, getUser());
+                ps.setString(++i, action);
+                ps.setString(++i, getIPAddress());
+                return ps;
+            });
+        } catch (Throwable t) {
+            LOG.error("Exception while trying to save audit log", t);
+        }
     }
 
     private String getIPAddress() {
diff --git a/gms/src/test/java/it/inaf/ia2/gms/controller/JWTWebServiceControllerTest.java b/gms/src/test/java/it/inaf/ia2/gms/controller/JWTWebServiceControllerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..918cbe3979f96f47408e5ff1eb74759a84c9757a
--- /dev/null
+++ b/gms/src/test/java/it/inaf/ia2/gms/controller/JWTWebServiceControllerTest.java
@@ -0,0 +1,82 @@
+package it.inaf.ia2.gms.controller;
+
+import it.inaf.ia2.gms.persistence.GroupsDAO;
+import it.inaf.ia2.gms.persistence.MembershipsDAO;
+import it.inaf.ia2.gms.persistence.model.GroupEntity;
+import java.security.Principal;
+import java.util.Optional;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import static org.mockito.Mockito.when;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.web.servlet.MockMvc;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+@RunWith(MockitoJUnitRunner.class)
+public class JWTWebServiceControllerTest {
+
+    @Mock
+    private MembershipsDAO membershipsDAO;
+
+    @Mock
+    private GroupsDAO groupsDAO;
+
+    @InjectMocks
+    private JWTWebServiceController controller;
+
+    private final Principal principal = new Principal() {
+        @Override
+        public String getName() {
+            return "TEST_PRINCIPAL";
+        }
+    };
+
+    private MockMvc mockMvc;
+
+    @Before
+    public void init() {
+        mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
+    }
+
+    @Test
+    public void testIsMemberOf() throws Exception {
+
+        GroupEntity group1 = new GroupEntity();
+        group1.setId("1");
+        group1.setPath("1");
+        group1.setName("group.1");
+
+        GroupEntity group2 = new GroupEntity();
+        group2.setId("2");
+        group2.setPath("1.2");
+        group2.setName("subgroup");
+
+        GroupEntity group3 = new GroupEntity();
+        group3.setId("3");
+        group3.setPath("1.2.3");
+        group3.setName("subsubgroup");
+
+        when(groupsDAO.findGroupByParentAndName(eq(""), eq("group.1")))
+                .thenReturn(Optional.of(group1));
+        when(groupsDAO.findGroupByParentAndName(eq("1"), eq("subgroup")))
+                .thenReturn(Optional.of(group2));
+        when(groupsDAO.findGroupByParentAndName(eq("1.2"), eq("subsubgroup")))
+                .thenReturn(Optional.of(group3));
+
+        when(membershipsDAO.isMemberOf(anyString(), eq(group3.getId()))).thenReturn(true);
+
+        String group = "group\\.1.subgroup.subsubgroup";
+
+        mockMvc.perform(get("/ws/jwt/search/" + group).principal(principal))
+                .andExpect(status().isOk())
+                .andExpect(content().string(group + "\n"));
+    }
+}