diff --git a/fda-authentication-service/Dockerfile b/fda-authentication-service/Dockerfile
index 2dd80a74938d54c01ba65c954ab80a4179913caf..3cbf6b92959593544bda157bb14f481eca5aadbb 100644
--- a/fda-authentication-service/Dockerfile
+++ b/fda-authentication-service/Dockerfile
@@ -43,7 +43,7 @@ ENV METADATA_USERNAME=root
 ENV METADATA_PASSWORD=dbrepo
 
 ENV KC_DB=mariadb
-ENV KC_DB_URL=jdbc:mariadb://fda-metadata-db/keycloak
+ENV KC_DB_URL=jdbc:mariadb://fda-metadata-db/fda
 ENV KC_DB_USERNAME=${METADATA_USERNAME}
 ENV KC_DB_PASSWORD=${METADATA_PASSWORD}
 ENV KC_HOSTNAME=localhost
diff --git a/fda-authentication-service/dbrepo-realm.json b/fda-authentication-service/dbrepo-realm.json
index 461d2cb8a773ca071f1c0e2308e7de1d7225573c..628fe47aa796139e344bd53abb143bad2aad49dd 100644
--- a/fda-authentication-service/dbrepo-realm.json
+++ b/fda-authentication-service/dbrepo-realm.json
@@ -117,6 +117,17 @@
       "clientRole" : false,
       "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0",
       "attributes" : { }
+    }, {
+      "id" : "3a801b48-f3c2-4bc6-aa25-c7a91d5b32a7",
+      "name" : "default-researcher-roles",
+      "description" : "${default-researcher-roles}",
+      "composite" : true,
+      "composites" : {
+        "realm" : [ "default-table-handling", "default-container-handling", "default-query-handling", "default-database-handling", "default-identifier-handling" ]
+      },
+      "clientRole" : false,
+      "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0",
+      "attributes" : { }
     }, {
       "id" : "3d8104fb-8307-40f0-b4b2-c3e518957110",
       "name" : "view-table-data",
@@ -249,7 +260,7 @@
       "description" : "${role_default-roles}",
       "composite" : true,
       "composites" : {
-        "realm" : [ "default-table-handling", "default-roles-dbrepo", "default-container-handling", "default-query-handling", "offline_access", "default-database-handling", "uma_authorization", "default-identifier-handling" ]
+        "realm" : [ "default-researcher-roles" ]
       },
       "clientRole" : false,
       "containerId" : "82c39861-d877-4667-a0f3-4daa2ee230e0",
@@ -831,7 +842,7 @@
   "otpPolicyLookAheadWindow" : 1,
   "otpPolicyPeriod" : 30,
   "otpPolicyCodeReusable" : false,
-  "otpSupportedApplications" : [ "totpAppGoogleName", "totpAppMicrosoftAuthenticatorName", "totpAppFreeOTPName" ],
+  "otpSupportedApplications" : [ "totpAppMicrosoftAuthenticatorName", "totpAppGoogleName", "totpAppFreeOTPName" ],
   "webAuthnPolicyRpEntityName" : "keycloak",
   "webAuthnPolicySignatureAlgorithms" : [ "ES256" ],
   "webAuthnPolicyRpId" : "",
@@ -1727,7 +1738,7 @@
       "subType" : "authenticated",
       "subComponents" : { },
       "config" : {
-        "allowed-protocol-mapper-types" : [ "oidc-usermodel-attribute-mapper", "oidc-address-mapper", "saml-role-list-mapper", "oidc-usermodel-property-mapper", "oidc-full-name-mapper", "saml-user-property-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-user-attribute-mapper" ]
+        "allowed-protocol-mapper-types" : [ "oidc-usermodel-attribute-mapper", "oidc-usermodel-property-mapper", "saml-user-attribute-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-role-list-mapper", "saml-user-property-mapper", "oidc-address-mapper", "oidc-full-name-mapper" ]
       }
     }, {
       "id" : "3ab11d74-5e76-408a-b85a-26bf8950f979",
@@ -1736,7 +1747,7 @@
       "subType" : "anonymous",
       "subComponents" : { },
       "config" : {
-        "allowed-protocol-mapper-types" : [ "saml-user-attribute-mapper", "saml-user-property-mapper", "oidc-usermodel-property-mapper", "saml-role-list-mapper", "oidc-usermodel-attribute-mapper", "oidc-full-name-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-address-mapper" ]
+        "allowed-protocol-mapper-types" : [ "oidc-address-mapper", "saml-user-attribute-mapper", "saml-user-property-mapper", "oidc-usermodel-property-mapper", "saml-role-list-mapper", "oidc-usermodel-attribute-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-full-name-mapper" ]
       }
     } ],
     "org.keycloak.keys.KeyProvider" : [ {
@@ -1788,7 +1799,7 @@
   "internationalizationEnabled" : false,
   "supportedLocales" : [ ],
   "authenticationFlows" : [ {
-    "id" : "c5a3dd66-04ab-412f-a687-a86750997514",
+    "id" : "8dfb9b83-2bd4-4c87-871b-f29b56992215",
     "alias" : "Account verification options",
     "description" : "Method with which to verity the existing account",
     "providerId" : "basic-flow",
@@ -1810,7 +1821,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "77db7a9d-32ea-46ea-965c-7543585f0a8a",
+    "id" : "b88cbaec-5a8d-4425-bcef-19a6344e9276",
     "alias" : "Authentication Options",
     "description" : "Authentication options.",
     "providerId" : "basic-flow",
@@ -1839,7 +1850,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "d728bf4f-c26a-4a2f-8968-7bd190abedfe",
+    "id" : "77defea0-f083-4103-ab7b-a7d9165d7297",
     "alias" : "Browser - Conditional OTP",
     "description" : "Flow to determine if the OTP is required for the authentication",
     "providerId" : "basic-flow",
@@ -1861,7 +1872,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "a196acb5-833c-4a34-83c7-c37a45cb144f",
+    "id" : "7ff9f69f-527e-4f75-bdab-ed2b142b1cba",
     "alias" : "Direct Grant - Conditional OTP",
     "description" : "Flow to determine if the OTP is required for the authentication",
     "providerId" : "basic-flow",
@@ -1883,7 +1894,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "73bb1748-899a-4160-b66b-d587f88ca23f",
+    "id" : "058c5fb2-3e97-4d22-aa5a-e3376bc72df4",
     "alias" : "First broker login - Conditional OTP",
     "description" : "Flow to determine if the OTP is required for the authentication",
     "providerId" : "basic-flow",
@@ -1905,7 +1916,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "e4fd72f0-e9c6-4100-99d8-fa3dc6f475c0",
+    "id" : "3d152b4b-0598-45ce-9b06-7e576deeefc4",
     "alias" : "Handle Existing Account",
     "description" : "Handle what to do if there is existing account with same email/username like authenticated identity provider",
     "providerId" : "basic-flow",
@@ -1927,7 +1938,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "cfae0db9-63b8-43b3-9e35-a5980edcfb92",
+    "id" : "08eab043-42e9-41bf-a9ec-adb00027c557",
     "alias" : "Reset - Conditional OTP",
     "description" : "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.",
     "providerId" : "basic-flow",
@@ -1949,7 +1960,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "623d89f1-4fa7-4d75-a7b0-92aea5d39c9e",
+    "id" : "31b88f44-e960-48d3-af15-00aed87e0d0f",
     "alias" : "User creation or linking",
     "description" : "Flow for the existing/non-existing user alternatives",
     "providerId" : "basic-flow",
@@ -1972,7 +1983,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "c9977d2b-dfa5-4d71-9822-953d9b25a6b6",
+    "id" : "454e1da2-eb82-439a-9cac-9e77a42ca69b",
     "alias" : "Verify Existing Account by Re-authentication",
     "description" : "Reauthentication of existing account",
     "providerId" : "basic-flow",
@@ -1994,7 +2005,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "6d3c2734-7f38-4d56-afd5-de813f0b7557",
+    "id" : "2eb0ccb2-9ef4-4829-aba8-597ec6bf6437",
     "alias" : "browser",
     "description" : "browser based authentication",
     "providerId" : "basic-flow",
@@ -2030,7 +2041,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "ba8e84c8-cc24-4830-b7d0-ab8974d3877e",
+    "id" : "9c9c98b8-6e60-45cb-96c1-8feb6d63a08f",
     "alias" : "clients",
     "description" : "Base authentication for clients",
     "providerId" : "client-flow",
@@ -2066,7 +2077,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "7e5174e7-c796-47e5-878d-c9843e79b20d",
+    "id" : "cfcdd3c8-435f-4f29-95d6-57abe3fb30e7",
     "alias" : "direct grant",
     "description" : "OpenID Connect Resource Owner Grant",
     "providerId" : "basic-flow",
@@ -2095,7 +2106,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "5f501679-10bd-484e-bd5e-851ff4086942",
+    "id" : "6bba0ee6-e128-46ae-bb71-21e2a15b7c2e",
     "alias" : "docker auth",
     "description" : "Used by Docker clients to authenticate against the IDP",
     "providerId" : "basic-flow",
@@ -2110,7 +2121,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "fd11f60f-ee22-4eb9-8721-c2feee85f36f",
+    "id" : "00636f61-55eb-4910-9ee4-e8a7d8c0e531",
     "alias" : "first broker login",
     "description" : "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
     "providerId" : "basic-flow",
@@ -2133,7 +2144,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "b7b5a91d-3d5a-40e6-b705-3bc9aa06c5af",
+    "id" : "35d38b7f-2881-4b5b-b114-e207e836ea51",
     "alias" : "forms",
     "description" : "Username, password, otp and other auth forms.",
     "providerId" : "basic-flow",
@@ -2155,7 +2166,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "22d18eb6-1172-4651-b39e-1b31884e728a",
+    "id" : "cefd13b8-ff6d-4d1d-b3dc-3cd9d02c3314",
     "alias" : "http challenge",
     "description" : "An authentication flow based on challenge-response HTTP Authentication Schemes",
     "providerId" : "basic-flow",
@@ -2177,7 +2188,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "90eb8d81-b244-4d02-b513-3519cb44c5cf",
+    "id" : "54354e5c-fc51-434a-b452-d1abbd1cafac",
     "alias" : "registration",
     "description" : "registration flow",
     "providerId" : "basic-flow",
@@ -2193,7 +2204,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "937bff8d-128a-42bf-9437-598541744fa1",
+    "id" : "8c7e832c-5874-4914-957a-deed6a182473",
     "alias" : "registration form",
     "description" : "registration form",
     "providerId" : "form-flow",
@@ -2229,7 +2240,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "ce4981b4-a9c8-4389-b039-8c94517753b0",
+    "id" : "881146e6-4fbf-4155-88bf-275912cf6f22",
     "alias" : "reset credentials",
     "description" : "Reset credentials for a user if they forgot their password or something",
     "providerId" : "basic-flow",
@@ -2265,7 +2276,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "45bd9132-b308-4971-841b-ade951fcd58b",
+    "id" : "3d8bda77-c045-400c-86bb-97a9497234ea",
     "alias" : "saml ecp",
     "description" : "SAML ECP Profile Authentication Flow",
     "providerId" : "basic-flow",
@@ -2281,13 +2292,13 @@
     } ]
   } ],
   "authenticatorConfig" : [ {
-    "id" : "d243532c-fb83-4523-9aec-32dcb0d5119a",
+    "id" : "e5216582-408a-4892-a969-8eada7331caa",
     "alias" : "create unique user config",
     "config" : {
       "require.password.update.after.registration" : "false"
     }
   }, {
-    "id" : "29dc7168-78ae-407a-b974-15dfb79dc5c0",
+    "id" : "9ac544c2-0b2b-4df3-b1d6-9565caac9cbd",
     "alias" : "review profile config",
     "config" : {
       "update.profile.on.first.login" : "missing"
diff --git a/fda-container-service/rest-service/src/main/java/at/tuwien/endpoints/ContainerEndpoint.java b/fda-container-service/rest-service/src/main/java/at/tuwien/endpoints/ContainerEndpoint.java
index 10455f9f964dd5385f1a3a84b0b40f57d0e6c0c5..cdec045020c3b5cc554e4fc81d6bfae8087e2a01 100644
--- a/fda-container-service/rest-service/src/main/java/at/tuwien/endpoints/ContainerEndpoint.java
+++ b/fda-container-service/rest-service/src/main/java/at/tuwien/endpoints/ContainerEndpoint.java
@@ -91,7 +91,7 @@ public class ContainerEndpoint {
     @PutMapping("/{id}")
     @Transactional
     @Timed(value = "container.modify", description = "Time needed to modify the container state")
-    @PreAuthorize("hasRole('ROLE_RESEARCHER') or hasRole('ROLE_DEVELOPER')")
+    @PreAuthorize("hasAuthority('modify-container-state')")
     @Operation(summary = "Modify some container", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<ContainerBriefDto> modify(@NotNull @PathVariable("id") Long containerId,
                                                     @Valid @RequestBody ContainerChangeDto changeDto,
@@ -101,7 +101,7 @@ public class ContainerEndpoint {
         log.debug("endpoint modify container, containerId={}, changeDto={}, principal={}", containerId, changeDto, principal);
         final User user = userService.findByUsername(principal.getName());
         final Container container = containerService.find(containerId);
-        if (!(container.getCreator().getId().equals(user.getId()) || user.getRoles().stream().anyMatch(r -> r.name().equals("ROLE_DEVELOPER")))) {
+        if (!(container.getCreator().getId().equals(user.getId()))) {
             log.error("Failed to modify container because it is not owned '{}' by the current user {} or is not developer", container.getCreator().getUsername(), user.getUsername());
             throw new NotAllowedException("Failed to modify container because it is not owned by the current user or is not developer");
         }
@@ -122,7 +122,7 @@ public class ContainerEndpoint {
     @DeleteMapping("/{id}")
     @Transactional
     @Timed(value = "container.delete", description = "Time needed to delete the container")
-    @PreAuthorize("hasRole('ROLE_DEVELOPER')")
+    @PreAuthorize("hasAuthority('delete-container')")
     @Operation(summary = "Delete some container", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<?> delete(@NotNull @PathVariable("id") Long containerId,
                                     @NotNull Principal principal) throws ContainerNotFoundException,
diff --git a/fda-container-service/rest-service/src/main/java/at/tuwien/endpoints/ImageEndpoint.java b/fda-container-service/rest-service/src/main/java/at/tuwien/endpoints/ImageEndpoint.java
index bbb5d2ecba97a1acd2314ce9a9cbb9b16de68e82..97a1ab755cdaf7a89471f4a7394ce35cd5bbfba6 100644
--- a/fda-container-service/rest-service/src/main/java/at/tuwien/endpoints/ImageEndpoint.java
+++ b/fda-container-service/rest-service/src/main/java/at/tuwien/endpoints/ImageEndpoint.java
@@ -57,7 +57,7 @@ public class ImageEndpoint {
     @PostMapping
     @Transactional
     @Timed(value = "image.create", description = "Time needed to create a container image")
-    @PreAuthorize("hasRole('ROLE_DEVELOPER')")
+    @PreAuthorize("hasAuthority('create-image')")
     @Operation(summary = "Create image", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<ImageDto> create(@Valid @RequestBody ImageCreateDto data,
                                            @NotNull Principal principal) throws ImageNotFoundException,
@@ -91,7 +91,7 @@ public class ImageEndpoint {
     @PutMapping("/{id}")
     @Transactional
     @Timed(value = "image.update", description = "Time needed to update a container image")
-    @PreAuthorize("hasRole('DEVELOPER')")
+    @PreAuthorize("hasAuthority('modify-image')")
     @Operation(summary = "Update some image", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<ImageDto> update(@NotNull @PathVariable Long id,
                                            @RequestBody @Valid ImageChangeDto changeDto,
@@ -108,7 +108,7 @@ public class ImageEndpoint {
     @DeleteMapping("/{id}")
     @Transactional
     @Timed(value = "image.delete", description = "Time needed to delete a container image")
-    @PreAuthorize("hasRole('DEVELOPER')")
+    @PreAuthorize("hasAuthority('delete-image')")
     @Operation(summary = "Delete some image", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<?> delete(@NotNull @PathVariable Long imageId,
                                     @NotNull Principal principal) throws ImageNotFoundException {
diff --git a/fda-container-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java b/fda-container-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java
index 5fd75bb0250fededa0da2b1a2401f56cbbbc725b..6dc27c2260a57820cbedf9516124bbb0283f1eba 100644
--- a/fda-container-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java
+++ b/fda-container-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java
@@ -9,9 +9,7 @@ import at.tuwien.entities.container.Container;
 import at.tuwien.entities.container.image.ContainerImage;
 import at.tuwien.entities.container.image.ContainerImageEnvironmentItem;
 import at.tuwien.entities.container.image.ContainerImageEnvironmentItemType;
-import at.tuwien.entities.user.RoleType;
 import at.tuwien.entities.user.User;
-import com.github.dockerjava.api.command.InspectContainerResponse;
 import com.github.dockerjava.api.model.HealthCheck;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.GrantedAuthority;
@@ -49,11 +47,7 @@ public abstract class BaseUnitTest {
             .username(USER_1_USERNAME)
             .email(USER_1_EMAIL)
             .emailVerified(USER_1_EMAIL_VERIFIED)
-            .affiliation(USER_1_AFFILIATION)
-            .themeDark(USER_1_THEME_DARK)
-            .password(USER_1_PASSWORD)
             .databasePassword(USER_1_DATABASE_PASSWORD)
-            .roles(List.of(RoleType.ROLE_RESEARCHER))
             .build();
 
     public final static UserDto USER_1_DTO = UserDto.builder()
@@ -61,11 +55,6 @@ public abstract class BaseUnitTest {
             .username(USER_1_USERNAME)
             .email(USER_1_EMAIL)
             .emailVerified(USER_1_EMAIL_VERIFIED)
-            .affiliation(USER_1_AFFILIATION)
-            .themeDark(USER_1_THEME_DARK)
-            .password(USER_1_PASSWORD)
-            .roles(List.of("ROLE_RESEARCHER"))
-            .authorities(List.of(RESEARCHER_AUTHORITY_DTO))
             .build();
 
     public final static UserDetails USER_1_DETAILS = UserDetailsDto.builder()
@@ -96,7 +85,6 @@ public abstract class BaseUnitTest {
     public final static Boolean USER_2_THEME_DARK = false;
     public final static String USER_2_PASSWORD = "p455w0rdh45";
     public final static String USER_2_DATABASE_PASSWORD = "*A8C67ABBEAE837AABCF49680A157D85D44A117E9";
-    public final static RoleType USER_2_ROLE_TYPE = RoleType.ROLE_DEVELOPER;
     public final static GrantedAuthority USER_2_AUTHORITY = new SimpleGrantedAuthority("ROLE_DEVELOPER");
 
     public final static User USER_2 = User.builder()
@@ -104,10 +92,7 @@ public abstract class BaseUnitTest {
             .username(USER_2_USERNAME)
             .email(USER_2_EMAIL)
             .emailVerified(USER_2_EMAIL_VERIFIED)
-            .themeDark(USER_2_THEME_DARK)
-            .password(USER_2_PASSWORD)
             .databasePassword(USER_2_DATABASE_PASSWORD)
-            .roles(List.of(USER_2_ROLE_TYPE))
             .build();
 
     public final static UserDetails USER_2_DETAILS = UserDetailsDto.builder()
@@ -133,7 +118,6 @@ public abstract class BaseUnitTest {
     public final static Boolean USER_3_THEME_DARK = false;
     public final static String USER_3_PASSWORD = "p455w0rdh45";
     public final static String USER_3_DATABASE_PASSWORD = "*A8C67ABBEAE837AABCF49680A157D85D44A117E9";
-    public final static RoleType USER_3_ROLE_TYPE = RoleType.ROLE_DATA_STEWARD;
     public final static GrantedAuthority USER_3_AUTHORITY = new SimpleGrantedAuthority("ROLE_DATA_STEWARD");
 
     public final static User USER_3 = User.builder()
@@ -141,10 +125,7 @@ public abstract class BaseUnitTest {
             .username(USER_3_USERNAME)
             .email(USER_3_EMAIL)
             .emailVerified(USER_3_EMAIL_VERIFIED)
-            .themeDark(USER_3_THEME_DARK)
-            .password(USER_3_PASSWORD)
             .databasePassword(USER_3_DATABASE_PASSWORD)
-            .roles(List.of(USER_3_ROLE_TYPE))
             .build();
 
     public final static UserDetails USER_3_DETAILS = UserDetailsDto.builder()
@@ -176,10 +157,7 @@ public abstract class BaseUnitTest {
             .username(USER_4_USERNAME)
             .email(USER_4_EMAIL)
             .emailVerified(USER_4_EMAIL_VERIFIED)
-            .themeDark(USER_4_THEME_DARK)
-            .password(USER_4_PASSWORD)
             .databasePassword(USER_4_DATABASE_PASSWORD)
-            .roles(List.of())
             .build();
 
     public final static String USER_5_ID = "d2f3a8f4-c7fe-49e8-9d14-6dad0f6b9406";
diff --git a/fda-database-service/rest-service/src/main/java/at/tuwien/endpoints/AbstractEndpoint.java b/fda-database-service/rest-service/src/main/java/at/tuwien/endpoints/AbstractEndpoint.java
deleted file mode 100644
index a88cfd05ac78072cde63205b2a1fcec6f35ae60b..0000000000000000000000000000000000000000
--- a/fda-database-service/rest-service/src/main/java/at/tuwien/endpoints/AbstractEndpoint.java
+++ /dev/null
@@ -1,147 +0,0 @@
-package at.tuwien.endpoints;
-
-import at.tuwien.entities.container.Container;
-import at.tuwien.entities.database.AccessType;
-import at.tuwien.entities.database.Database;
-import at.tuwien.entities.database.DatabaseAccess;
-import at.tuwien.exception.ContainerNotFoundException;
-import at.tuwien.exception.DatabaseNotFoundException;
-import at.tuwien.repository.jpa.DatabaseAccessRepository;
-import at.tuwien.service.ContainerService;
-import at.tuwien.service.DatabaseService;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.core.Authentication;
-
-import java.security.Principal;
-import java.util.List;
-import java.util.Optional;
-
-@Slf4j
-public abstract class AbstractEndpoint {
-
-    private final DatabaseService databaseService;
-    private final ContainerService containerService;
-    private final DatabaseAccessRepository databaseAccessRepository;
-
-    @Autowired
-    protected AbstractEndpoint(DatabaseService databaseService, ContainerService containerService,
-                               DatabaseAccessRepository databaseAccessRepository) {
-        this.databaseService = databaseService;
-        this.containerService = containerService;
-        this.databaseAccessRepository = databaseAccessRepository;
-    }
-
-    protected Boolean hasDatabasePermission(Long containerId, Long databaseId, String permissionCode,
-                                            Principal principal) {
-        log.trace("validate has database permission, containerId={}, databaseId={}, permissionCode={}, principal={}",
-                containerId, databaseId, permissionCode, principal);
-        final Database database;
-        try {
-            database = databaseService.findById(containerId, databaseId);
-        } catch (DatabaseNotFoundException e) {
-            log.error("Failed to find database");
-            return false;
-        }
-        /* view-only operations are allowed on public databases */
-        if (database.getIsPublic() && false) {
-            log.trace("grant permission {} because database is public", permissionCode);
-            return true;
-        }
-        if (principal == null) {
-            log.error("Failed to grant permission {} because principal is null", permissionCode);
-            return false;
-        }
-        /* view-only operations are allowed on all databases */
-        if (List.of("CHECK_ACCESS").contains(permissionCode)) {
-            log.debug("grant permission {} because of public/private database", permissionCode);
-            return true;
-        }
-        /* modification operations are limited to the owner */
-        if (database.getOwner().getUsername().equals(principal.getName())) {
-            log.trace("grant permission {} because user {} is owner {}", permissionCode, principal.getName(),
-                    database.getOwner().getUsername());
-            return true;
-        }
-        final Authentication authentication = (Authentication) principal /* with pre-authorization this always holds */;
-        if (List.of("VISIBILITY_DATABASE").contains(permissionCode) && isDeveloper(authentication)) {
-            log.debug("grant permission {} because user {} is developer", permissionCode, principal.getName());
-            return true;
-        }
-        final Optional<DatabaseAccess> optional = databaseAccessRepository.findByDatabaseIdAndUsername(databaseId, principal.getName());
-        if (optional.isEmpty()) {
-            log.error("Failed to grant permission {} because user {} does not have access", permissionCode, principal.getName());
-            return false;
-        }
-        final DatabaseAccess access = optional.get();
-        log.trace("access type: {}", access.getType());
-        if (List.of().contains(permissionCode) && hasAccess(access) && isResearcher(authentication)) {
-            log.debug("grant permission {} because user {} has access type {} and is researcher", permissionCode,
-                    principal.getName(), optional.get().getType());
-            return true;
-        }
-        log.error("Failed to grant permission {} because user {} does not have access", permissionCode, principal.getName());
-        return false;
-    }
-
-    protected Boolean hasContainerPermission(Long containerId, String permissionCode, Principal principal) {
-        log.trace("validate has container permission, containerId={}, permissionCode={}, principal={}",
-                containerId, permissionCode, principal);
-        final Container container;
-        try {
-            container = containerService.find(containerId);
-        } catch (ContainerNotFoundException e) {
-            return false;
-        }
-        /* view-only operations are allowed on public databases */
-        if (List.of().contains(permissionCode)) {
-            log.debug("grant permission {} because it does not require authentication", permissionCode);
-            return true;
-        }
-        if (principal == null) {
-            log.error("Failed to grant permission {} because principal is null", permissionCode);
-            return false;
-        }
-        /* modification operations are limited to the owner */
-        if (container.getOwner().getUsername().equals(principal.getName())) {
-            log.debug("grant permission {} because user {} is owner {}", permissionCode, principal.getName(),
-                    container.getOwner().getUsername());
-            return true;
-        }
-        final Authentication authentication = (Authentication) principal /* with pre-authorization this always holds */;
-        if (List.of("CREATE_DATABASE").contains(permissionCode) &&
-                authentication.getAuthorities().stream().anyMatch(a -> a.getAuthority().equals("ROLE_DEVELOPER"))) {
-            log.debug("grant permission {} because user {} is developer", permissionCode, principal.getName());
-            return true;
-        }
-        if (authentication.getAuthorities().stream().noneMatch(a -> a.getAuthority().equals("ROLE_RESEARCHER"))) {
-            log.error("Failed to grant permission {} because current user misses authority 'ROLE_RESEARCHER'",
-                    permissionCode);
-            return false;
-        }
-        log.error("Failed to grant permission {} because container is not owner by the current user", permissionCode);
-        return false;
-    }
-
-    protected boolean isResearcher(Authentication authentication) {
-        return authentication.getAuthorities().stream()
-                .anyMatch(a -> a.getAuthority().equals("ROLE_RESEARCHER"));
-    }
-
-    protected boolean isDeveloper(Authentication authentication) {
-        return authentication.getAuthorities().stream()
-                .anyMatch(a -> a.getAuthority().equals("ROLE_DEVELOPER"));
-    }
-
-    protected boolean hasReadAccess(DatabaseAccess access) {
-        return access.getType().equals(AccessType.READ);
-    }
-
-    protected boolean hasWriteAccess(DatabaseAccess access) {
-        return access.getType().equals(AccessType.WRITE_OWN) || access.getType().equals(AccessType.WRITE_ALL);
-    }
-
-    protected boolean hasAccess(DatabaseAccess access) {
-        return hasReadAccess(access) || hasWriteAccess(access);
-    }
-}
diff --git a/fda-database-service/rest-service/src/main/java/at/tuwien/endpoints/AccessEndpoint.java b/fda-database-service/rest-service/src/main/java/at/tuwien/endpoints/AccessEndpoint.java
index b8206f09e5b43b1f608da23c2796e0aa73f2ef7d..3c83982839537f9244f5eb1093b6072b292e208b 100644
--- a/fda-database-service/rest-service/src/main/java/at/tuwien/endpoints/AccessEndpoint.java
+++ b/fda-database-service/rest-service/src/main/java/at/tuwien/endpoints/AccessEndpoint.java
@@ -15,6 +15,7 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement;
 import lombok.extern.log4j.Log4j2;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
 
@@ -27,22 +28,20 @@ import java.security.Principal;
 @RestController
 @CrossOrigin(origins = "*")
 @RequestMapping("/api/container/{id}/database/{databaseId}/access")
-public class AccessEndpoint extends AbstractEndpoint {
+public class AccessEndpoint {
 
     private final AccessService accessService;
     private final DatabaseMapper databaseMapper;
 
     @Autowired
-    public AccessEndpoint(DatabaseAccessRepository databaseAccessRepository, DatabaseService databaseService,
-                          ContainerService containerService, AccessService accessService,
-                          DatabaseMapper databaseMapper) {
-        super(databaseService, containerService, databaseAccessRepository);
+    public AccessEndpoint(AccessService accessService, DatabaseMapper databaseMapper) {
         this.accessService = accessService;
         this.databaseMapper = databaseMapper;
     }
 
     @PostMapping
     @Transactional
+    @PreAuthorize("hasAuthority('create-access')")
     @Operation(summary = "Give access to some database", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<?> create(@NotBlank @PathVariable("id") Long containerId,
                                     @NotBlank @PathVariable("databaseId") Long databaseId,
@@ -52,10 +51,6 @@ public class AccessEndpoint extends AbstractEndpoint {
             DatabaseMalformedException {
         log.debug("endpoint give access to database, containerId={}, databaseId={}, accessDto={}, principal={}",
                 containerId, databaseId, accessDto, principal);
-        if (!hasDatabasePermission(containerId, databaseId, "GIVE_ACCESS", principal)) {
-            log.error("Missing give access permission");
-            throw new NotAllowedException("Missing give access permission");
-        }
         try {
             accessService.find(databaseId, accessDto.getUsername());
             log.error("Failed to give access to user with username {}, already has access", accessDto.getUsername());
@@ -70,6 +65,7 @@ public class AccessEndpoint extends AbstractEndpoint {
 
     @PutMapping("/{username}")
     @Transactional
+    @PreAuthorize("hasAuthority('modify-access')")
     @Operation(summary = "Modify access to some database", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<?> update(@NotBlank @PathVariable("id") Long containerId,
                                     @NotBlank @PathVariable("databaseId") Long databaseId,
@@ -80,10 +76,6 @@ public class AccessEndpoint extends AbstractEndpoint {
             QueryMalformedException, DatabaseMalformedException {
         log.debug("endpoint modify access to database, containerId={}, databaseId={}, username={}, accessDto={}, principal={}",
                 containerId, databaseId, username, accessDto, principal);
-        if (!hasDatabasePermission(containerId, databaseId, "MODIFY_ACCESS", principal)) {
-            log.error("Missing modify access permission");
-            throw new NotAllowedException("Missing modify access permission");
-        }
         accessService.find(databaseId, username);
         accessService.update(containerId, databaseId, username, accessDto);
         return ResponseEntity.accepted()
@@ -92,6 +84,7 @@ public class AccessEndpoint extends AbstractEndpoint {
 
     @GetMapping
     @Transactional
+    @PreAuthorize("hasAuthority('check-access')")
     @Operation(summary = "Check access to some database", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<DatabaseAccessDto> find(@NotBlank @PathVariable("id") Long containerId,
                                                   @NotBlank @PathVariable("databaseId") Long databaseId,
@@ -99,10 +92,6 @@ public class AccessEndpoint extends AbstractEndpoint {
             AccessDeniedException {
         log.debug("endpoint check access to database, containerId={}, databaseId={}, principal={}",
                 containerId, databaseId, principal);
-        if (!hasDatabasePermission(containerId, databaseId, "CHECK_ACCESS", principal)) {
-            log.error("Missing modify access permission");
-            throw new NotAllowedException("Missing modify access permission");
-        }
         final DatabaseAccess access = accessService.find(databaseId, principal.getName());
         final DatabaseAccessDto dto = databaseMapper.databaseAccessToDatabaseAccessDto(access);
         log.trace("check access resulted in dto {}", dto);
@@ -111,6 +100,7 @@ public class AccessEndpoint extends AbstractEndpoint {
 
     @DeleteMapping("/{username}")
     @Transactional
+    @PreAuthorize("hasAuthority('modify-access')")
     @Operation(summary = "Revoke access to some database", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<?> revoke(@NotBlank @PathVariable("id") Long containerId,
                                     @NotBlank @PathVariable("databaseId") Long databaseId,
@@ -120,10 +110,6 @@ public class AccessEndpoint extends AbstractEndpoint {
             QueryMalformedException, DatabaseMalformedException {
         log.debug("endpoint revoke access to database, containerId={}, databaseId={}, username={}, principal={}",
                 containerId, databaseId, username, principal);
-        if (!hasDatabasePermission(containerId, databaseId, "REVOKE_ACCESS", principal)) {
-            log.error("Missing revoke access permission");
-            throw new NotAllowedException("Missing revoke access permission");
-        }
         accessService.find(databaseId, username);
         accessService.delete(containerId, databaseId, username);
         return ResponseEntity.accepted()
diff --git a/fda-database-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java b/fda-database-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java
index bfeb98549b44ad1c38c2dbc84c399aa99f475cf6..11121c05c8123583d2f4b2b0c5eb5a4bbd43fdef 100644
--- a/fda-database-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java
+++ b/fda-database-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java
@@ -30,7 +30,7 @@ import java.util.stream.Collectors;
 @RestController
 @CrossOrigin(origins = "*")
 @RequestMapping("/api/container/{id}/database")
-public class DatabaseEndpoint extends AbstractEndpoint {
+public class DatabaseEndpoint {
 
     private final UserService userService;
     private final AccessService accessService;
@@ -41,11 +41,9 @@ public class DatabaseEndpoint extends AbstractEndpoint {
     private final DatabaseAccessRepository databaseAccessRepository;
 
     @Autowired
-    public DatabaseEndpoint(DatabaseMapper databaseMapper, ContainerService containerService,
-                            UserService userService, MariaDbServiceImpl databaseService, QueryStoreService queryStoreService,
-                            MessageQueueService messageQueueService, AccessService accessService,
-                            DatabaseAccessRepository databaseAccessRepository) {
-        super(databaseService, containerService, databaseAccessRepository);
+    public DatabaseEndpoint(DatabaseMapper databaseMapper, UserService userService, MariaDbServiceImpl databaseService,
+                            QueryStoreService queryStoreService, MessageQueueService messageQueueService,
+                            AccessService accessService, DatabaseAccessRepository databaseAccessRepository) {
         this.userService = userService;
         this.accessService = accessService;
         this.databaseMapper = databaseMapper;
@@ -72,7 +70,7 @@ public class DatabaseEndpoint extends AbstractEndpoint {
 
     @PostMapping
     @Transactional
-    @PreAuthorize("hasRole('ROLE_RESEARCHER') or hasRole('ROLE_DEVELOPER')")
+    @PreAuthorize("hasAuthority('create-database')")
     @Timed(value = "database.create", description = "Time needed to create a database")
     @Operation(summary = "Create database", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<DatabaseBriefDto> create(@NotNull @PathVariable("id") Long containerId,
@@ -85,10 +83,6 @@ public class DatabaseEndpoint extends AbstractEndpoint {
             BrokerVirtualHostGrantException {
         log.debug("endpoint create database, containerId={}, createDto={}, principal={}", containerId, createDto,
                 principal);
-        if (!hasContainerPermission(containerId, "CREATE_DATABASE", principal)) {
-            log.error("Missing database create permission");
-            throw new NotAllowedException("Missing database create permission");
-        }
         final Database database = databaseService.create(containerId, createDto, principal);
         final User user = userService.findByUsername(principal.getName());
         messageQueueService.createExchange(database, principal);
@@ -103,7 +97,7 @@ public class DatabaseEndpoint extends AbstractEndpoint {
 
     @PutMapping("/{databaseId}/visibility")
     @Transactional
-    @PreAuthorize("hasRole('ROLE_RESEARCHER') or hasRole('ROLE_DEVELOPER')")
+    @PreAuthorize("hasAuthority('modify-database')")
     @Timed(value = "database.visibility", description = "Time needed to modify a database visibility")
     @Operation(summary = "Update database", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<DatabaseDto> visibility(@NotNull @PathVariable("id") Long containerId,
@@ -113,10 +107,6 @@ public class DatabaseEndpoint extends AbstractEndpoint {
             throws DatabaseNotFoundException, NotAllowedException {
         log.debug("endpoint update database, containerId={}, databaseId={}, data={}, principal={}", containerId,
                 databaseId, data, principal);
-        if (!hasDatabasePermission(containerId, databaseId, "VISIBILITY_DATABASE", principal)) {
-            log.error("Missing database update visibility permission");
-            throw new NotAllowedException("Missing database update visibility permission");
-        }
         final Database database = databaseService.visibility(containerId, databaseId, data);
         final DatabaseDto dto = databaseMapper.databaseToDatabaseDto(database);
         log.trace("update database resulted in database {}", dto);
@@ -126,9 +116,9 @@ public class DatabaseEndpoint extends AbstractEndpoint {
 
     @PutMapping("/{databaseId}/transfer")
     @Transactional
-    @PreAuthorize("isAuthenticated()")
+    @PreAuthorize("hasAuthority('transfer-database')")
     @Timed(value = "database.transfer", description = "Time needed to transfer a database ownership")
-    @Operation(summary = "Update database", security = @SecurityRequirement(name = "bearerAuth"))
+    @Operation(summary = "Transfer database", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<DatabaseDto> transfer(@NotNull @PathVariable("id") Long containerId,
                                                 @NotNull @PathVariable Long databaseId,
                                                 @Valid @RequestBody DatabaseTransferDto transferDto,
@@ -136,10 +126,6 @@ public class DatabaseEndpoint extends AbstractEndpoint {
             throws DatabaseNotFoundException, NotAllowedException, UserNotFoundException {
         log.debug("endpoint update database, containerId={}, databaseId={}, transferDto={}, principal={}", containerId,
                 databaseId, transferDto, principal);
-        if (!hasDatabasePermission(containerId, databaseId, "TRANSFER_DATABASE", principal)) {
-            log.error("Missing database transfer ownership permission");
-            throw new NotAllowedException("Missing database transfer ownership permission");
-        }
         final Database database = databaseService.transfer(containerId, databaseId, transferDto);
         final DatabaseDto dto = databaseMapper.databaseToDatabaseDto(database);
         log.trace("update database resulted in database {}", dto);
@@ -171,7 +157,7 @@ public class DatabaseEndpoint extends AbstractEndpoint {
 
     @DeleteMapping("/{databaseId}")
     @Transactional
-    @PreAuthorize("hasRole('ROLE_DEVELOPER')")
+    @PreAuthorize("hasAuthority('delete-database')")
     @Timed(value = "database.delete", description = "Time needed to delete a database")
     @Operation(summary = "Delete some database", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<?> delete(@NotNull @PathVariable("id") Long containerId,
diff --git a/fda-database-service/rest-service/src/main/java/at/tuwien/endpoints/LicenseEndpoint.java b/fda-database-service/rest-service/src/main/java/at/tuwien/endpoints/LicenseEndpoint.java
index 8298ad461f9f275b884a2610f7380b65fde12a3b..9e8cb566d1d3c87abc5d53c913da792339c8ad7f 100644
--- a/fda-database-service/rest-service/src/main/java/at/tuwien/endpoints/LicenseEndpoint.java
+++ b/fda-database-service/rest-service/src/main/java/at/tuwien/endpoints/LicenseEndpoint.java
@@ -9,6 +9,7 @@ import lombok.extern.log4j.Log4j2;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
 
diff --git a/fda-database-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java b/fda-database-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java
index d19118589c651d4751587d5802f7fec8dadae332..e1d061dc6dfb42386926e233cd9a49330d1d7a40 100644
--- a/fda-database-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java
+++ b/fda-database-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java
@@ -90,8 +90,6 @@ public abstract class BaseUnitTest {
             .username(USER_1_USERNAME)
             .email(USER_1_EMAIL)
             .emailVerified(USER_1_VERIFIED)
-            .themeDark(USER_1_THEME)
-            .password(USER_1_PASSWORD)
             .databasePassword(USER_1_DATABASE_PASSWORD)
             .build();
 
@@ -99,7 +97,6 @@ public abstract class BaseUnitTest {
             .id(USER_1_ID)
             .username(USER_1_USERNAME)
             .emailVerified(USER_1_VERIFIED)
-            .themeDark(USER_1_THEME)
             .build();
 
     public final static UserDto USER_1_DTO = UserDto.builder()
@@ -107,10 +104,6 @@ public abstract class BaseUnitTest {
             .username(USER_1_USERNAME)
             .email(USER_1_EMAIL)
             .emailVerified(USER_1_VERIFIED)
-            .themeDark(USER_1_THEME)
-            .password(USER_1_PASSWORD)
-            .roles(List.of("ROLE_RESEARCHER"))
-            .authorities(List.of(new GrantedAuthorityDto("ROLE_RESEARCHER")))
             .build();
 
     public final static UserDetails USER_1_DETAILS = UserDetailsDto.builder()
@@ -139,8 +132,6 @@ public abstract class BaseUnitTest {
             .username(USER_2_USERNAME)
             .email(USER_2_EMAIL)
             .emailVerified(USER_2_VERIFIED)
-            .themeDark(USER_2_THEME)
-            .password(USER_2_PASSWORD)
             .databasePassword(USER_2_DATABASE_PASSWORD)
             .build();
 
@@ -149,17 +140,12 @@ public abstract class BaseUnitTest {
             .username(USER_2_USERNAME)
             .email(USER_2_EMAIL)
             .emailVerified(USER_2_VERIFIED)
-            .themeDark(USER_2_THEME)
-            .password(USER_2_PASSWORD)
-            .roles(List.of("ROLE_DATA_STEWARD"))
-            .authorities(List.of(new GrantedAuthorityDto("ROLE_DATA_STEWARD")))
             .build();
 
     public final static UserBriefDto USER_2_BRIEF_DTO = UserBriefDto.builder()
             .id(USER_2_ID)
             .username(USER_2_USERNAME)
             .emailVerified(USER_2_VERIFIED)
-            .themeDark(USER_2_THEME)
             .build();
 
     public final static UserDetails USER_2_DETAILS = UserDetailsDto.builder()
@@ -187,8 +173,6 @@ public abstract class BaseUnitTest {
             .username(USER_3_USERNAME)
             .email(USER_3_EMAIL)
             .emailVerified(USER_3_EMAIL_VERIFIED)
-            .themeDark(USER_3_THEME_DARK)
-            .password(USER_3_PASSWORD)
             .databasePassword(USER_3_DATABASE_PASSWORD)
             .build();
 
@@ -197,10 +181,6 @@ public abstract class BaseUnitTest {
             .username(USER_3_USERNAME)
             .email(USER_3_EMAIL)
             .emailVerified(USER_3_VERIFIED)
-            .themeDark(USER_3_THEME)
-            .password(USER_3_PASSWORD)
-            .roles(List.of("ROLE_DEVELOPER"))
-            .authorities(List.of(new GrantedAuthorityDto("ROLE_DEVELOPER")))
             .build();
 
     public final static UserDetails USER_3_DETAILS = UserDetailsDto.builder()
@@ -226,8 +206,6 @@ public abstract class BaseUnitTest {
             .username(USER_4_USERNAME)
             .email(USER_4_EMAIL)
             .emailVerified(USER_4_EMAIL_VERIFIED)
-            .themeDark(USER_4_THEME_DARK)
-            .password(USER_4_PASSWORD)
             .databasePassword(USER_4_DATABASE_PASSWORD)
             .build();
 
diff --git a/fda-database-service/rest-service/src/test/java/at/tuwien/mapper/DatabaseMapperTest.java b/fda-database-service/rest-service/src/test/java/at/tuwien/mapper/DatabaseMapperTest.java
index 48d94926281e6d2008816e9e196b631801e07e7c..5159e734eea655be0b3a447667d3bdda1cede67e 100644
--- a/fda-database-service/rest-service/src/test/java/at/tuwien/mapper/DatabaseMapperTest.java
+++ b/fda-database-service/rest-service/src/test/java/at/tuwien/mapper/DatabaseMapperTest.java
@@ -47,11 +47,9 @@ public class DatabaseMapperTest extends BaseUnitTest {
         final UserBriefDto creator = response.getCreator();
         assertEquals(USER_1_ID, creator.getId());
         assertEquals(USER_1_USERNAME, creator.getUsername());
-        assertEquals(USER_1_THEME, creator.getThemeDark());
         final UserBriefDto owner = response.getOwner();
         assertEquals(USER_1_ID, owner.getId());
         assertEquals(USER_1_USERNAME, owner.getUsername());
-        assertEquals(USER_1_THEME, owner.getThemeDark());
     }
 
 }
diff --git a/fda-database-service/services/src/main/java/at/tuwien/mapper/DatabaseMapper.java b/fda-database-service/services/src/main/java/at/tuwien/mapper/DatabaseMapper.java
index f986f7577245bb0161e9d5d046da92fd22e40012..51e9c382be0e9fdf6616f0489ad69d590f16dd5f 100644
--- a/fda-database-service/services/src/main/java/at/tuwien/mapper/DatabaseMapper.java
+++ b/fda-database-service/services/src/main/java/at/tuwien/mapper/DatabaseMapper.java
@@ -77,7 +77,6 @@ public interface DatabaseMapper {
                 .get(0);
         return User.builder()
                 .username(username)
-                .databasePassword(password)
                 .build();
     }
 
@@ -105,7 +104,6 @@ public interface DatabaseMapper {
         final StringBuilder statement = new StringBuilder("CREATE USER IF NOT EXISTS `")
                 .append(user.getUsername())
                 .append("`@`%` IDENTIFIED BY PASSWORD '")
-                .append(user.getDatabasePassword())
                 .append("';");
         log.trace("statement={}", statement);
         try {
diff --git a/fda-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/AbstractEndpoint.java b/fda-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/AbstractEndpoint.java
deleted file mode 100644
index 937e07c404b694916c7b25703af94f7700243dca..0000000000000000000000000000000000000000
--- a/fda-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/AbstractEndpoint.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package at.tuwien.endpoints;
-
-import at.tuwien.entities.database.Database;
-import at.tuwien.entities.user.User;
-import at.tuwien.exception.DatabaseNotFoundException;
-import at.tuwien.exception.UserNotFoundException;
-import at.tuwien.service.DatabaseService;
-import at.tuwien.service.UserService;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-
-import java.security.Principal;
-import java.util.List;
-
-@Slf4j
-public abstract class AbstractEndpoint {
-
-    private final UserService userService;
-    private final DatabaseService databaseService;
-
-    @Autowired
-    protected AbstractEndpoint(UserService userService, DatabaseService databaseService) {
-        this.userService = userService;
-        this.databaseService = databaseService;
-    }
-
-    protected Boolean hasDatabasePermission(Long containerId, Long databaseId, String permissionCode,
-                                            Principal principal) throws UserNotFoundException {
-        log.trace("validate has database permission, containerId={}, databaseId={}, permissionCode={}, principal={}",
-                containerId, databaseId, permissionCode, principal);
-        final Database database;
-        try {
-            database = databaseService.find(containerId, databaseId);
-        } catch (DatabaseNotFoundException e) {
-            log.error("Failed to find database with container id {} and database id {}", containerId, databaseId);
-            return false;
-        }
-        /* view-only operations are allowed on public databases */
-        if (principal == null) {
-            log.error("Failed to grant permission {} because principal is null", permissionCode);
-            return false;
-        }
-        final User user = userService.findByUsername(principal.getName());
-        /* data steward */
-        if (List.of("CREATE_IDENTIFIER").contains(permissionCode) && user.getRoles().stream().anyMatch(r -> r.name().equals("ROLE_DATA_STEWARD"))) {
-            log.debug("grant permission {} because of role data steward", permissionCode);
-            return true;
-        }
-        /* modification operations are limited to the creator */
-        if (database.getCreator().getUsername().equals(principal.getName())) {
-            log.trace("grant permission {} because user {} is creator {}", permissionCode, principal.getName(),
-                    database.getCreator().getUsername());
-            return true;
-        }
-        log.error("Failed to grant permission {} because database owner with id {} is not the current user with id {}", permissionCode, database.getCreator().getId(), user.getId());
-        return false;
-    }
-
-}
diff --git a/fda-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java b/fda-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java
index 1f98d9560c0f219e048b9531a8ae85605e713a96..d75aeb79eba1389df1ea154fe7202c505c7d95e5 100644
--- a/fda-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java
+++ b/fda-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java
@@ -6,9 +6,7 @@ import at.tuwien.api.identifier.IdentifierTypeDto;
 import at.tuwien.entities.identifier.Identifier;
 import at.tuwien.exception.*;
 import at.tuwien.mapper.IdentifierMapper;
-import at.tuwien.service.DatabaseService;
 import at.tuwien.service.IdentifierService;
-import at.tuwien.service.UserService;
 import io.micrometer.core.annotation.Timed;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.security.SecurityRequirement;
@@ -30,21 +28,20 @@ import java.util.stream.Collectors;
 @CrossOrigin(origins = "*")
 @RestController
 @RequestMapping("/api/identifier")
-public class IdentifierEndpoint extends AbstractEndpoint {
+public class IdentifierEndpoint {
 
     private final IdentifierMapper identifierMapper;
     private final IdentifierService identifierService;
 
     @Autowired
-    public IdentifierEndpoint(IdentifierMapper identifierMapper, IdentifierService identifierService,
-                              DatabaseService databaseService, UserService userService) {
-        super(userService, databaseService);
+    public IdentifierEndpoint(IdentifierMapper identifierMapper, IdentifierService identifierService) {
         this.identifierMapper = identifierMapper;
         this.identifierService = identifierService;
     }
 
     @GetMapping
     @Transactional(readOnly = true)
+    @PreAuthorize("hasAuthority('find-identifiers')")
     @Timed(value = "identifier.list", description = "Time needed to list the identifiers")
     @Operation(summary = "Find identifiers")
     public ResponseEntity<List<IdentifierDto>> list(@RequestParam(required = false) Long dbid,
@@ -70,19 +67,14 @@ public class IdentifierEndpoint extends AbstractEndpoint {
     @PostMapping
     @Transactional
     @Timed(value = "identifier.create", description = "Time needed to create an identifier")
-    @PreAuthorize("hasRole('ROLE_RESEARCHER') or hasRole('ROLE_DATA_STEWARD')")
+    @PreAuthorize("hasAuthority('create-identifier')")
     @Operation(summary = "Create identifier", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<IdentifierDto> create(@NotNull @Valid @RequestBody IdentifierCreateDto data,
                                                 @NotNull @RequestHeader(name = "Authorization") String authorization,
                                                 @NotNull Principal principal)
             throws IdentifierAlreadyExistsException, QueryNotFoundException, IdentifierPublishingNotAllowedException,
-            RemoteUnavailableException, UserNotFoundException, DatabaseNotFoundException, IdentifierRequestException,
-            NotAllowedException {
+            RemoteUnavailableException, UserNotFoundException, DatabaseNotFoundException, IdentifierRequestException {
         log.debug("endpoint create identifier, data={}, authorization={}, principal={}", data, authorization, principal);
-        if (!hasDatabasePermission(data.getCid(), data.getDbid(), "CREATE_IDENTIFIER", principal)) {
-            log.error("Missing identifier create permission");
-            throw new NotAllowedException("Missing identifier create permission");
-        }
         if (data.getType().equals(IdentifierTypeDto.SUBSET) && data.getQid() == null) {
             log.error("Identifier of type subset need to have a qid present");
             throw new IdentifierRequestException("Identifier of type subset need to have a qid present");
@@ -98,7 +90,7 @@ public class IdentifierEndpoint extends AbstractEndpoint {
     @PutMapping("/{id}")
     @Transactional
     @Timed(value = "identifier.update", description = "Time needed to update an identifier")
-    @PreAuthorize("hasRole('ROLE_DATA_STEWARD')")
+    @PreAuthorize("hasAuthority('update-identifier')")
     @Operation(summary = "Update some identifier", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<IdentifierDto> update(@NotNull @PathVariable("id") Long id,
                                                 @NotNull @Valid @RequestBody IdentifierDto data)
@@ -112,7 +104,7 @@ public class IdentifierEndpoint extends AbstractEndpoint {
     @DeleteMapping("/{id}")
     @Transactional
     @Timed(value = "identifier.delete", description = "Time needed to delete an identifier")
-    @PreAuthorize("hasRole('ROLE_DATA_STEWARD')")
+    @PreAuthorize("hasAuthority('delete-identifier')")
     @Operation(summary = "Delete some identifier", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<?> delete(@NotNull @PathVariable("id") Long id)
             throws IdentifierNotFoundException {
diff --git a/fda-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/PersistenceEndpoint.java b/fda-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/PersistenceEndpoint.java
index 278f074a3ba5261baf134559490e924b0786d7a6..a540a807e741ca0df2e495197ad4a061562c8852 100644
--- a/fda-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/PersistenceEndpoint.java
+++ b/fda-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/PersistenceEndpoint.java
@@ -9,9 +9,7 @@ import at.tuwien.exception.IdentifierRequestException;
 import at.tuwien.exception.QueryNotFoundException;
 import at.tuwien.exception.RemoteUnavailableException;
 import at.tuwien.mapper.IdentifierMapper;
-import at.tuwien.service.DatabaseService;
 import at.tuwien.service.IdentifierService;
-import at.tuwien.service.UserService;
 import io.micrometer.core.annotation.Timed;
 import io.swagger.v3.oas.annotations.Operation;
 import lombok.extern.log4j.Log4j2;
@@ -20,6 +18,7 @@ import org.springframework.core.io.InputStreamResource;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
 
@@ -31,7 +30,7 @@ import java.util.regex.Pattern;
 @CrossOrigin(origins = "*")
 @RestController
 @RequestMapping("/api/pid")
-public class PersistenceEndpoint extends AbstractEndpoint {
+public class PersistenceEndpoint {
 
     private final EndpointConfig endpointConfig;
     private final IdentifierMapper identifierMapper;
@@ -39,9 +38,7 @@ public class PersistenceEndpoint extends AbstractEndpoint {
 
     @Autowired
     public PersistenceEndpoint(EndpointConfig endpointConfig, IdentifierMapper identifierMapper,
-                               IdentifierService identifierService, DatabaseService databaseService,
-                               UserService userService) {
-        super(userService, databaseService);
+                               IdentifierService identifierService) {
         this.endpointConfig = endpointConfig;
         this.identifierMapper = identifierMapper;
         this.identifierService = identifierService;
@@ -49,6 +46,7 @@ public class PersistenceEndpoint extends AbstractEndpoint {
 
     @GetMapping("/{pid}")
     @Transactional(readOnly = true)
+    @PreAuthorize("hasAuthority('find-identifier')")
     @Timed(value = "pid.find", description = "Time needed to find a persisted identifier")
     @Operation(summary = "Find some identifier")
     public ResponseEntity<?> find(@Valid @PathVariable("pid") Long pid,
diff --git a/fda-identifier-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java b/fda-identifier-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java
index b02f63c19a919f4fd5680aeaf5412f3c0eb4a016..29893e0027e264c0e0129c1516c613b09bfe6529 100644
--- a/fda-identifier-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java
+++ b/fda-identifier-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java
@@ -58,24 +58,16 @@ public abstract class BaseUnitTest {
     public final static User USER_1 = User.builder()
             .id(USER_1_ID)
             .username(USER_1_USERNAME)
-            .password(USER_1_PASSWORD)
             .databasePassword(USER_1_DATABASE_PASSWORD)
             .email(USER_1_EMAIL)
             .emailVerified(USER_1_EMAIL_VERIFIED)
-            .themeDark(USER_1_THEME_DARK)
-            .created(USER_1_CREATED)
-            .lastModified(USER_1_LAST_MODIFIED)
             .build();
 
     public final static UserDto USER_1_DTO = UserDto.builder()
             .id(USER_1_ID)
             .username(USER_1_USERNAME)
-            .password(USER_1_PASSWORD)
             .email(USER_1_EMAIL)
-            .authorities(List.of(AUTHORITY_RESEARCHER_DTO))
-            .roles(List.of("ROLE_RESEARCHER"))
             .emailVerified(USER_1_EMAIL_VERIFIED)
-            .themeDark(USER_1_THEME_DARK)
             .build();
 
     public final static UserDetails USER_1_DETAILS = UserDetailsDto.builder()
@@ -102,24 +94,16 @@ public abstract class BaseUnitTest {
     public final static User USER_2 = User.builder()
             .id(USER_2_ID)
             .username(USER_2_USERNAME)
-            .password(USER_2_PASSWORD)
             .databasePassword(USER_2_DATABASE_PASSWORD)
             .email(USER_2_EMAIL)
             .emailVerified(USER_2_EMAIL_VERIFIED)
-            .themeDark(USER_2_THEME_DARK)
-            .created(USER_2_CREATED)
-            .lastModified(USER_2_LAST_MODIFIED)
             .build();
 
     public final static UserDto USER_2_DTO = UserDto.builder()
             .id(USER_2_ID)
             .username(USER_2_USERNAME)
-            .password(USER_2_PASSWORD)
             .email(USER_2_EMAIL)
-            .authorities(List.of(AUTHORITY_RESEARCHER_DTO))
-            .roles(List.of("ROLE_RESEARCHER"))
             .emailVerified(USER_2_EMAIL_VERIFIED)
-            .themeDark(USER_2_THEME_DARK)
             .build();
 
     public final static UserDetails USER_2_DETAILS = UserDetailsDto.builder()
@@ -146,13 +130,9 @@ public abstract class BaseUnitTest {
 
     public final static User USER_3 = User.builder()
             .username(USER_3_USERNAME)
-            .password(USER_3_PASSWORD)
             .databasePassword(USER_3_DATABASE_PASSWORD)
             .email(USER_3_EMAIL)
             .emailVerified(USER_3_EMAIL_VERIFIED)
-            .themeDark(USER_3_THEME_DARK)
-            .created(USER_3_CREATED)
-            .lastModified(USER_3_LAST_MODIFIED)
             .build();
 
     public final static UserDetails USER_3_DETAILS = UserDetailsDto.builder()
diff --git a/fda-metadata-db/51-dbrepo.cnf b/fda-metadata-db/51-dbrepo.cnf
new file mode 100644
index 0000000000000000000000000000000000000000..ed1bf6617fd4513a4a122170a6b24edc72a51330
--- /dev/null
+++ b/fda-metadata-db/51-dbrepo.cnf
@@ -0,0 +1,2 @@
+[mysqld]
+lower_case_table_names=1
\ No newline at end of file
diff --git a/fda-metadata-db/Dockerfile b/fda-metadata-db/Dockerfile
index 9e86f075b2f86cf07c8cafaf220a2efbea5c76e9..aa268acd8548df9d848a82e2851cd4347888fa8a 100644
--- a/fda-metadata-db/Dockerfile
+++ b/fda-metadata-db/Dockerfile
@@ -21,6 +21,7 @@ RUN mvn -q clean install > /dev/null
 ###### SECOND STAGE ######
 FROM mariadb:10.5 as runtime
 
+# install curl for prometheus metrics
 RUN apt-get update && apt-get install -y curl
 RUN cd /tmp && curl -LO https://github.com/prometheus/node_exporter/releases/download/v0.18.1/node_exporter-0.18.1.linux-amd64.tar.gz && tar -xvf node_exporter-0.18.1.linux-amd64.tar.gz && mv node_exporter-0.18.1.linux-amd64/node_exporter /usr/local/bin/
 
@@ -30,6 +31,9 @@ ENV METADATA_PASSWORD=dbrepo
 ENV MARIADB_DATABASE="${METADATA_DB}"
 ENV MARIADB_ROOT_PASSWORD="${METADATA_PASSWORD}"
 
+# force lower-case table names
+COPY ./51-dbrepo.cnf /etc/mysql/mariadb.conf.d/51-dbrepo.cnf
+
 # Scripts are copied to /docker-entrypoint-initdb.d/ in docker-compose from analyze service
 HEALTHCHECK --interval=10s --timeout=5s --retries=12 CMD mysqladmin ping --user="$METADATA_USERNAME" --password="$METADATA_PASSWORD" --silent
 
diff --git a/fda-metadata-db/api/src/main/java/at/tuwien/api/user/UserBriefDto.java b/fda-metadata-db/api/src/main/java/at/tuwien/api/user/UserBriefDto.java
index db7b84038c5018395849a532c8926a315aebd9d8..fd4c54484fe1a7f755e71041899de9ffeaf547c2 100644
--- a/fda-metadata-db/api/src/main/java/at/tuwien/api/user/UserBriefDto.java
+++ b/fda-metadata-db/api/src/main/java/at/tuwien/api/user/UserBriefDto.java
@@ -22,10 +22,6 @@ public class UserBriefDto {
     @JsonProperty("sub")
     private String id;
 
-    @ToString.Exclude
-    @org.springframework.data.annotation.Transient
-    private List<GrantedAuthorityDto> authorities;
-
     @NotNull
     @JsonProperty("preferred_username")
     @Schema(example = "jcarberry", description = "Only contains lowercase characters")
@@ -34,13 +30,6 @@ public class UserBriefDto {
     @Schema(example = "Josiah Carberry")
     private String name;
 
-    @JsonProperty("titles_before")
-    @Schema(example = "Prof.")
-    private String titlesBefore;
-
-    @JsonProperty("titles_after")
-    private String titlesAfter;
-
     @JsonProperty("given_name")
     @Schema(example = "Josiah")
     private String firstname;
@@ -49,18 +38,6 @@ public class UserBriefDto {
     @Schema(example = "Carberry")
     private String lastname;
 
-    @Schema(example = "Brown University")
-    private String affiliation;
-
-    @Schema(example = "0000-0002-1825-0097")
-    private String orcid;
-
-    @JsonIgnore
-    @JsonProperty("theme_dark")
-    @Schema(example = "true")
-    @org.springframework.data.annotation.Transient
-    private Boolean themeDark;
-
     @JsonIgnore
     @JsonProperty("email_verified")
     @Schema(example = "true")
diff --git a/fda-metadata-db/api/src/main/java/at/tuwien/api/user/UserDto.java b/fda-metadata-db/api/src/main/java/at/tuwien/api/user/UserDto.java
index 4ea96b26876d00cc68a845ad78733e7301de5e94..f48aab4e6938cf87a4c9b9cf16089296621fcf1d 100644
--- a/fda-metadata-db/api/src/main/java/at/tuwien/api/user/UserDto.java
+++ b/fda-metadata-db/api/src/main/java/at/tuwien/api/user/UserDto.java
@@ -22,10 +22,6 @@ public class UserDto {
     @JsonProperty("sub")
     private String id;
 
-    @ToString.Exclude
-    @org.springframework.data.annotation.Transient
-    private List<GrantedAuthorityDto> authorities;
-
     @NotNull
     @JsonProperty("preferred_username")
     @Schema(example = "jcarberry", description = "Only contains lowercase characters")
@@ -34,13 +30,6 @@ public class UserDto {
     @Schema(example = "Josiah Carberry")
     private String name;
 
-    @JsonProperty("titles_before")
-    @Schema(example = "Prof.")
-    private String titlesBefore;
-
-    @JsonProperty("titles_after")
-    private String titlesAfter;
-
     @JsonProperty("given_name")
     @Schema(example = "Josiah")
     private String firstname;
@@ -49,23 +38,6 @@ public class UserDto {
     @Schema(example = "Carberry")
     private String lastname;
 
-    @Schema(example = "Brown University")
-    private String affiliation;
-
-    @Schema(example = "0000-0002-1825-0097")
-    private String orcid;
-
-    @NotNull
-    @Schema(description = "Roles of the user", example = "[ROLE_RESEARCHER]")
-    @org.springframework.data.annotation.Transient
-    private List<String> roles;
-
-    @NotNull
-    @JsonProperty("theme_dark")
-    @Schema(example = "true")
-    @org.springframework.data.annotation.Transient
-    private Boolean themeDark;
-
     @EqualsAndHashCode.Exclude
     @org.springframework.data.annotation.Transient
     private List<ContainerDto> containers;
@@ -78,12 +50,6 @@ public class UserDto {
     @org.springframework.data.annotation.Transient
     private List<ContainerDto> identifiers;
 
-    @JsonIgnore
-    @ToString.Exclude
-    @EqualsAndHashCode.Exclude
-    @org.springframework.data.annotation.Transient
-    private String password;
-
     @NotNull
     @Schema(example = "jcarberry@brown.edu")
     @org.springframework.data.annotation.Transient
diff --git a/fda-metadata-db/entities/src/main/java/at/tuwien/entities/user/User.java b/fda-metadata-db/entities/src/main/java/at/tuwien/entities/user/User.java
index aebd5075b6def7c4542399d4f719d5e26def42db..f394a018034cd8eaa4d9e44d619934fcb480be25 100644
--- a/fda-metadata-db/entities/src/main/java/at/tuwien/entities/user/User.java
+++ b/fda-metadata-db/entities/src/main/java/at/tuwien/entities/user/User.java
@@ -1,14 +1,13 @@
 package at.tuwien.entities.user;
 
 import at.tuwien.entities.container.Container;
+import at.tuwien.entities.database.Database;
+import at.tuwien.entities.identifier.Identifier;
 import lombok.*;
-import org.hibernate.annotations.GenericGenerator;
-import org.springframework.data.annotation.CreatedDate;
-import org.springframework.data.annotation.LastModifiedDate;
+import org.hibernate.annotations.Immutable;
 import org.springframework.data.jpa.domain.support.AuditingEntityListener;
 
 import javax.persistence.*;
-import java.time.Instant;
 import java.util.List;
 
 @Data
@@ -17,25 +16,15 @@ import java.util.List;
 @AllArgsConstructor
 @NoArgsConstructor
 @ToString
+@Immutable
 @EntityListeners(AuditingEntityListener.class)
 @EqualsAndHashCode(onlyExplicitlyIncluded = true)
-@Table(name = "mdb_users")
-@NamedNativeQueries({
-        @NamedNativeQuery(name = "User.findAll",
-                query = "SELECT e.* FROM `keycloak`.`REALM` r JOIN `keycloak`.`USER_ENTITY` e ON r.`ID` = e.`REALM_ID` WHERE r.`NAME` = 'dbrepo' AND e.`USERNAME` != 'system'",
-                resultClass = User.class),
-        @NamedNativeQuery(name = "User.findByUsername",
-                query = "SELECT e.* FROM `keycloak`.`REALM` r JOIN `keycloak`.`USER_ENTITY` e ON r.`ID` = e.`REALM_ID` WHERE r.`NAME` = 'dbrepo' AND e.`USERNAME` = ?",
-                resultClass = User.class)
-})
+@Table(name = "user_entity")
 public class User {
 
-
     @Id
     @EqualsAndHashCode.Include
-    @GeneratedValue(generator = "users-sequence")
-    @GenericGenerator(name = "users-sequence", strategy = "increment")
-    @Column(updatable = false, nullable = false)
+    @Column(nullable = false)
     private String id;
 
     @Column(unique = true, nullable = false)
@@ -47,46 +36,30 @@ public class User {
     @Column(name = "last_name")
     private String lastname;
 
-    @Column(name = "preceding_titles")
-    private String titlesBefore;
-
-    @Column(name = "postpositioned_title")
-    private String titlesAfter;
-
-    @Column(name = "main_email", unique = true, nullable = false)
+    @Column(unique = true, nullable = false)
     private String email;
 
-    @Column
-    private String affiliation;
-
-    @Column
-    private String orcid;
-
     @Column(nullable = false)
-    private Boolean themeDark;
-
-    @Column(name = "main_email_verified", nullable = false)
     private Boolean emailVerified;
 
-    @ToString.Exclude
-    @Column(nullable = false)
-    private String password;
-
+    @Transient
     @ToString.Exclude
     @Column(nullable = false)
     private String databasePassword;
 
     @Transient
     @ToString.Exclude
-    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "creator")
+    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "owner")
     private List<Container> containers;
 
-    @CreatedDate
-    @Column(nullable = false, updatable = false)
-    private Instant created;
+    @Transient
+    @ToString.Exclude
+    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "owner")
+    private List<Database> databases;
 
-    @LastModifiedDate
-    @Column(name = "last_modified")
-    private Instant lastModified;
+    @Transient
+    @ToString.Exclude
+    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "creator")
+    private List<Identifier> identifiers;
 
 }
diff --git a/fda-metadata-db/setup-schema.sql b/fda-metadata-db/setup-schema.sql
index b2f0309313b59294ff2e059f7d2af5e24093bf37..d390aac4874431231bd4095e7abacedd30ba0c60 100644
--- a/fda-metadata-db/setup-schema.sql
+++ b/fda-metadata-db/setup-schema.sql
@@ -1,5 +1,3 @@
-CREATE DATABASE IF NOT EXISTS `keycloak`;
-
 BEGIN;
 
 CREATE TABLE IF NOT EXISTS `fda`.`mdb_images`
diff --git a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/AbstractEndpoint.java b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/AbstractEndpoint.java
deleted file mode 100644
index 0abead058bd32565d4625f123668684eef1f12b4..0000000000000000000000000000000000000000
--- a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/AbstractEndpoint.java
+++ /dev/null
@@ -1,301 +0,0 @@
-package at.tuwien.endpoint;
-
-import at.tuwien.SortType;
-import at.tuwien.api.database.query.ExecuteStatementDto;
-import at.tuwien.config.QueryConfig;
-import at.tuwien.entities.database.AccessType;
-import at.tuwien.entities.database.Database;
-import at.tuwien.entities.database.DatabaseAccess;
-import at.tuwien.entities.database.table.Table;
-import at.tuwien.entities.identifier.Identifier;
-import at.tuwien.exception.*;
-import at.tuwien.service.AccessService;
-import at.tuwien.service.DatabaseService;
-import at.tuwien.service.IdentifierService;
-import at.tuwien.service.TableService;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.core.Authentication;
-
-import java.security.Principal;
-import java.util.Arrays;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import static at.tuwien.entities.identifier.VisibilityType.EVERYONE;
-
-@Slf4j
-public abstract class AbstractEndpoint {
-
-    private final QueryConfig queryConfig;
-    private final TableService tableService;
-    private final AccessService accessService;
-    private final DatabaseService databaseService;
-    private final IdentifierService identifierService;
-
-    @Autowired
-    protected AbstractEndpoint(TableService tableService, AccessService accessService, DatabaseService databaseService,
-                               IdentifierService identifierService, QueryConfig queryConfig) {
-        this.queryConfig = queryConfig;
-        this.tableService = tableService;
-        this.accessService = accessService;
-        this.databaseService = databaseService;
-        this.identifierService = identifierService;
-    }
-
-    protected Boolean hasDatabasePermission(Long containerId, Long databaseId, String permissionCode,
-                                            Principal principal) throws NotAllowedException {
-        log.trace("validate database permission, containerId={}, databaseId={}, permissionCode={}, principal={}",
-                containerId, databaseId, permissionCode, principal);
-        final Database database;
-        try {
-            database = databaseService.find(containerId, databaseId);
-        } catch (DatabaseNotFoundException e) {
-            log.error("Failed to find database with id {}", databaseId);
-            return false;
-        }
-        /* view-only operations are allowed on public databases */
-        if (database.getIsPublic() && List.of("DATA_VIEW", "DATA_HISTORY", "QUERY_VIEW_ALL").contains(permissionCode)) {
-            log.debug("grant permission {} because database is public", permissionCode);
-            return true;
-        }
-        if (List.of("LIST_VIEWS", "FIND_VIEW", "DATA_VIEW").contains(permissionCode)) {
-            log.debug("grant permission {} because it is allowed on public/private databases", permissionCode);
-            return true;
-        }
-        if (principal == null) {
-            log.error("Failed to grant permission {} because principal is null", permissionCode);
-            return false;
-        }
-        final DatabaseAccess access = accessService.find(databaseId, principal.getName());
-        /* modification operations are limited to the creator */
-        if (database.getOwner().getUsername().equals(principal.getName())) {
-            log.debug("grant permission {} because user {} is owner {}", permissionCode, principal.getName(),
-                    database.getOwner().getUsername());
-            return true;
-        }
-        /* check view access */
-        if (List.of("QUERY_EXECUTE").contains(permissionCode)) {
-            log.debug("grant permission {} because user has access {}", permissionCode, access.getType());
-            return true;
-        }
-        /* write permission */
-        final Authentication authentication = (Authentication) principal /* with pre-authorization this always holds */;
-        if (authentication.getAuthorities().stream().noneMatch(a -> a.getAuthority().equals("ROLE_RESEARCHER"))) {
-            log.error("Failed to grant permission {} because current user misses authority 'ROLE_RESEARCHER'",
-                    permissionCode);
-            return false;
-        }
-        log.error("Failed to grant permission {} because database is not owned by the current user", permissionCode);
-        return false;
-    }
-
-    protected void validateDataParams(Long page, Long size) throws PaginationException {
-        log.trace("validate data params, page={}, size={}", page, size);
-        if ((page == null && size != null) || (page != null && size == null)) {
-            log.error("Failed to validate page and/or size number, either both are present or none");
-            throw new PaginationException("Failed to validate page and/or size number");
-        }
-        if (page != null && page < 0) {
-            log.error("Failed to validate page number, is lower than zero");
-            throw new PaginationException("Failed to validate page number");
-        }
-        if (size != null && size <= 0) {
-            log.error("Failed to validate size number, is lower or equal than zero");
-            throw new PaginationException("Failed to validate size number");
-        }
-    }
-
-    protected void validateDataParams(Long page, Long size, SortType sortDirection, String sortColumn)
-            throws PaginationException, SortException {
-        log.trace("validate data params, page={}, size={}, sortDirection={}, sortColumn={}", page, size,
-                sortDirection, sortColumn);
-        validateDataParams(page, size);
-        if ((sortDirection == null && sortColumn != null) || (sortDirection != null && sortColumn == null)) {
-            log.error("Failed to validate sort direction and/or sort column, either both are present or none");
-            throw new SortException("Failed to validate sort direction and/or sort column");
-        }
-    }
-
-    /**
-     * Do not allow aggregate functions and comments
-     * https://mariadb.com/kb/en/aggregate-functions/
-     */
-    protected void validateForbiddenStatements(ExecuteStatementDto data) throws QueryMalformedException {
-        final List<String> words = new LinkedList<>();
-        Arrays.stream(queryConfig.getNotSupportedKeywords())
-                .forEach(keyword -> {
-                    final Pattern pattern = Pattern.compile(keyword);
-                    final Matcher matcher = pattern.matcher(data.getStatement());
-                    final boolean found = matcher.find();
-                    if (found) {
-                        words.add(keyword);
-                    }
-                });
-        if (words.size() == 0) {
-            return;
-        }
-        log.error("Query contains forbidden keyword(s): {}", words);
-        log.debug("forbidden keywords: {}", words);
-        throw new QueryMalformedException("Query contains forbidden keyword(s): " + Arrays.toString(words.toArray()));
-    }
-
-    protected Boolean hasTablePermission(Long containerId, Long databaseId, Long tableId, String permissionCode,
-                                         Principal principal) throws NotAllowedException {
-        log.trace("validate queue permission, containerId={}, databaseId={}, tableId={}, permissionCode={}, principal={}",
-                containerId, databaseId, tableId, permissionCode, principal);
-        final Database database;
-        try {
-            database = databaseService.find(containerId, databaseId);
-        } catch (DatabaseNotFoundException e) {
-            log.error("Failed to find database with id {}", databaseId);
-            return false;
-        }
-        final Table table;
-        try {
-            table = tableService.find(containerId, databaseId, tableId);
-        } catch (TableNotFoundException e) {
-            log.error("Failed to find table with id {} in database with id {}", tableId, databaseId);
-            return false;
-        } catch (DatabaseNotFoundException e) {
-            /* can never occur here */
-            return false;
-        }
-        /* view-only operations are allowed on public databases */
-        if (database.getIsPublic() && List.of("TABLE_EXPORT", "DATA_VIEW", "DATA_HISTORY").contains(permissionCode)) {
-            log.debug("grant permission {} because database is public", permissionCode);
-            return true;
-        }
-        if (principal == null) {
-            log.error("Failed to grant permission {} because principal is null", permissionCode);
-            return false;
-        }
-        /* modification operations for creators are trivial */
-        if (table.getCreator().getUsername().equals(principal.getName())) {
-            log.debug("grant permission {} because user {} is table creator {}", permissionCode, principal.getName(),
-                    table.getCreator().getUsername());
-            return true;
-        }
-        final Authentication authentication = (Authentication) principal /* with pre-authorization this always holds */;
-        if (authentication.getAuthorities().stream().noneMatch(a -> a.getAuthority().equals("ROLE_RESEARCHER"))) {
-            log.error("Failed to grant permission {} because current user misses authority 'ROLE_RESEARCHER'",
-                    permissionCode);
-            return false;
-        }
-        final DatabaseAccess access = accessService.find(databaseId, principal.getName());
-        /* check view access */
-        if (List.of("TABLE_EXPORT", "DATA_VIEW", "DATA_HISTORY", "QUERY_VIEW_ALL", "QUERY_RE_EXECUTE", "QUERY_VIEW", "FIND_VIEW").contains(permissionCode)) {
-            log.trace("grant permission {} because user has access {}", permissionCode, access.getType());
-            return true;
-        }
-        if (List.of("DATA_INSERT", "DATA_UPDATE", "DATA_DELETE", "QUERY_PERSIST").contains(permissionCode) && (access.getType().equals(AccessType.WRITE_ALL))) {
-            /* write own is already effective with creator check above */
-            log.debug("grant permission {} because user {} is has table write permission {}", permissionCode, principal.getName(),
-                    access.getType());
-            return true;
-        }
-        log.debug("failed to grant permission {} because database is not owner by the current user and also has not appropriate access", permissionCode);
-        return false;
-    }
-
-    protected Boolean hasQueryPermission(Long containerId, Long databaseId, Long queryId, String permissionCode,
-                                         Principal principal) throws NotAllowedException {
-        log.trace("validate query permission, containerId={}, databaseId={}, queryId={}, permissionCode={}, principal={}",
-                containerId, databaseId, queryId, permissionCode, principal);
-        final Database database;
-        try {
-            database = databaseService.find(containerId, databaseId);
-        } catch (DatabaseNotFoundException e) {
-            log.error("Failed to find database with id {}", databaseId);
-            return false;
-        }
-        if (hasPublicIdentifier(databaseId, queryId, permissionCode)) {
-            return true;
-        }
-        /* modification operations are limited to the creator */
-        if (isMyPrivateIdentifier(databaseId, queryId, principal, permissionCode)) {
-            return true;
-        }
-        /* view-only operations are allowed on public databases */
-        if (database.getIsPublic() && List.of("QUERY_VIEW_ALL", "QUERY_VIEW", "QUERY_EXPORT", "QUERY_RE_EXECUTE").contains(
-                permissionCode)) {
-            log.debug("grant permission {} because database is public", permissionCode);
-            return true;
-        }
-        if (principal == null) {
-            log.error("Failed to grant permission {} because principal is null", permissionCode);
-            return false;
-        }
-        final Authentication authentication = (Authentication) principal /* with pre-authorization this always holds */;
-        if (authentication.getAuthorities().stream().noneMatch(a -> a.getAuthority().equals("ROLE_RESEARCHER"))) {
-            log.error("Failed to grant permission {} because current user misses authority 'ROLE_RESEARCHER'",
-                    permissionCode);
-            return false;
-        }
-        /* modification operations are limited to the creator */
-        if (database.getCreator().getUsername().equals(principal.getName())) {
-            log.debug("grant permission {} because database is private and creator is the current user",
-                    permissionCode);
-            return true;
-        }
-        final DatabaseAccess access = accessService.find(databaseId, principal.getName());
-        /* check view access */
-        if (List.of("DATA_VIEW", "DATA_HISTORY", "QUERY_VIEW_ALL", "QUERY_RE_EXECUTE", "QUERY_VIEW", "FIND_VIEW", "QUERY_EXPORT").contains(permissionCode)) {
-            log.trace("grant permission {} because user has access {}", permissionCode, access.getType());
-            return true;
-        }
-        if (access.getType().equals(AccessType.WRITE_ALL)) {
-            log.trace("grant permission {} because user has access {}", permissionCode, access.getType());
-            return true;
-        }
-        log.debug("failed to grant permission {} because database is not owner by the current user and also has not appropriate access", permissionCode);
-        return false;
-    }
-
-    protected Boolean hasPublicIdentifier(Long databaseId, Long queryId, String permissionCode) {
-        log.trace("validate has public identifier, databaseId={}, queryId={}, permissionCode={}", databaseId, queryId,
-                permissionCode);
-        final Identifier identifier;
-        try {
-            identifier = identifierService.findByDatabaseIdAndQueryId(databaseId, queryId);
-        } catch (IdentifierNotFoundException e) {
-            return false;
-        }
-        if (identifier.getVisibility().equals(EVERYONE)) {
-            log.debug("grant permission {} because identifier visibility is public", permissionCode);
-            return true;
-        }
-        log.error("Failed to grant permission {} because identifier visibility is not public", permissionCode);
-        return false;
-    }
-
-    protected Boolean isMyPrivateIdentifier(Long databaseId, Long queryId, Principal principal, String permissionCode) {
-        log.trace("validate is my private identifier, databaseId={}, queryId={}, permissionCode={}", databaseId, queryId,
-                permissionCode);
-        final Identifier identifier;
-        try {
-            identifier = identifierService.findByDatabaseIdAndQueryId(databaseId, queryId);
-        } catch (IdentifierNotFoundException e) {
-            return false;
-        }
-        if (identifier.getDatabase().getIsPublic()) {
-            log.debug("grant permission {} because database is public", permissionCode);
-            return true;
-        }
-        if (principal == null) {
-            log.error("Failed to grant permission {} because database is private and principal is null",
-                    permissionCode);
-            return false;
-        }
-        if (identifier.getCreator().getUsername().equals(principal.getName())) {
-            log.debug("grant permission {} because database is private and identifier creator is the current user",
-                    permissionCode);
-            return true;
-        }
-        log.error("Failed to grant permission {} because database is private and identifier creator is not the current user", permissionCode);
-        return false;
-    }
-
-}
diff --git a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/ExportEndpoint.java b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/ExportEndpoint.java
index 33a9c761a6ebd7d6ffd065362c5db011faf9483f..356a3fc77ae7d5d975277e1d17c95454585b95c2 100644
--- a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/ExportEndpoint.java
+++ b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/ExportEndpoint.java
@@ -12,6 +12,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.core.io.InputStreamResource;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
 
@@ -23,19 +24,18 @@ import java.time.Instant;
 @CrossOrigin(origins = "*")
 @RestController
 @RequestMapping("/api/container/{id}/database/{databaseId}/table/{tableId}/export")
-public class ExportEndpoint extends AbstractEndpoint {
+public class ExportEndpoint {
 
     private final QueryService queryService;
 
     @Autowired
-    public ExportEndpoint(QueryService queryService, DatabaseService databaseService, AccessService accessService,
-                          IdentifierService identifierService, TableService tableService, QueryConfig queryConfig) {
-        super(tableService, accessService, databaseService, identifierService, queryConfig);
+    public ExportEndpoint(QueryService queryService) {
         this.queryService = queryService;
     }
 
     @GetMapping
     @Transactional(readOnly = true)
+    @PreAuthorize("hasAuthority('export-table')")
     @Timed(value = "table.export", description = "Time needed to export table data")
     @Operation(summary = "Export table", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<InputStreamResource> export(@NotNull @PathVariable("id") Long containerId,
@@ -45,13 +45,9 @@ public class ExportEndpoint extends AbstractEndpoint {
                                                       Principal principal)
             throws TableNotFoundException, DatabaseConnectionException, TableMalformedException,
             DatabaseNotFoundException, ImageNotSupportedException, PaginationException, ContainerNotFoundException,
-            FileStorageException, NotAllowedException, QueryMalformedException, UserNotFoundException {
+            FileStorageException, QueryMalformedException, UserNotFoundException {
         log.debug("endpoint export table, id={}, databaseId={}, tableId={}, timestamp={}, principal={}", containerId, databaseId,
                 tableId, timestamp, principal);
-        if (!hasTablePermission(containerId, databaseId, tableId, "TABLE_EXPORT", principal)) {
-            log.error("Missing data export permission");
-            throw new NotAllowedException("Missing data export permission");
-        }
         final HttpHeaders headers = new HttpHeaders();
         final ExportResource resource = queryService.tableFindAll(containerId, databaseId, tableId, timestamp, principal);
         headers.add("Content-Disposition", "attachment; filename=\"" + resource.getFilename() + "\"");
diff --git a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/QueryEndpoint.java b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/QueryEndpoint.java
index 6f4faa2b4197be398b573ea68f76eddc8c42ab8b..31f447c71ce14c54fbcc7591e30a88f492165e29 100644
--- a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/QueryEndpoint.java
+++ b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/QueryEndpoint.java
@@ -7,6 +7,7 @@ import at.tuwien.config.QueryConfig;
 import at.tuwien.querystore.Query;
 import at.tuwien.exception.*;
 import at.tuwien.service.*;
+import at.tuwien.validation.EndpointValidator;
 import io.micrometer.core.annotation.Timed;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.security.SecurityRequirement;
@@ -15,6 +16,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
 
@@ -22,19 +24,20 @@ import javax.validation.Valid;
 import javax.validation.constraints.NotNull;
 import java.security.Principal;
 
+
 @Log4j2
 @RestController
 @RequestMapping("/api/container/{id}/database/{databaseId}/query")
-public class QueryEndpoint extends AbstractEndpoint {
+public class QueryEndpoint {
 
     private final QueryService queryService;
     private final StoreService storeService;
+    private final EndpointValidator endpointValidator;
 
     @Autowired
-    public QueryEndpoint(QueryService queryService, StoreService storeService, DatabaseService databaseService,
-                         IdentifierService identifierService, TableService tableService, AccessService accessService,
-                         QueryConfig queryConfig) {
-        super(tableService, accessService, databaseService, identifierService, queryConfig);
+    public QueryEndpoint(QueryService queryService, StoreService storeService,
+                         EndpointValidator endpointValidator) {
+        this.endpointValidator = endpointValidator;
         this.queryService = queryService;
         this.storeService = storeService;
     }
@@ -42,6 +45,7 @@ public class QueryEndpoint extends AbstractEndpoint {
     @PostMapping
     @Transactional(readOnly = true)
     @Timed(value = "query.execute", description = "Time needed to execute a query")
+    @PreAuthorize("hasAuthority('execute-query')")
     @Operation(summary = "Execute query", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<QueryResultDto> execute(@NotNull @PathVariable("id") Long containerId,
                                                   @NotNull @PathVariable("databaseId") Long databaseId,
@@ -57,16 +61,12 @@ public class QueryEndpoint extends AbstractEndpoint {
         log.debug("endpoint execute query, containerId={}, databaseId={}, data={}, page={}, size={}, principal={}, sortDirection={}, sortColumn={}",
                 containerId, databaseId, data, page, size, principal, sortDirection, sortColumn);
         /* check */
-        if (!hasDatabasePermission(containerId, databaseId, "QUERY_EXECUTE", principal)) {
-            log.error("Missing execute query permission");
-            throw new NotAllowedException("Missing execute query permission");
-        }
         if (data.getStatement() == null || data.getStatement().isBlank()) {
             log.error("Failed to execute empty query");
             throw new QueryMalformedException("Failed to execute empty query");
         }
-        validateForbiddenStatements(data);
-        validateDataParams(page, size, sortDirection, sortColumn);
+        endpointValidator.validateForbiddenStatements(data);
+        endpointValidator.validateDataParams(page, size, sortDirection, sortColumn);
         /* execute */
         final QueryResultDto result = queryService.execute(containerId, databaseId, data, principal, page, size,
                 sortDirection, sortColumn);
@@ -77,6 +77,7 @@ public class QueryEndpoint extends AbstractEndpoint {
 
     @GetMapping("/{queryId}/data")
     @Transactional(readOnly = true)
+    @PreAuthorize("hasAuthority('re-execute-query')")
     @Timed(value = "query.reexecute", description = "Time needed to re-execute a query")
     @Operation(summary = "Re-execute some query", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<QueryResultDto> reExecute(@NotNull @PathVariable("id") Long containerId,
@@ -92,12 +93,7 @@ public class QueryEndpoint extends AbstractEndpoint {
             DatabaseConnectionException, SortException, PaginationException, UserNotFoundException {
         log.debug("endpoint re-execute query, containerId={}, databaseId={}, queryId={}, principal={}, page={}, size={}, sortDirection={}, sortColumn={}",
                 containerId, databaseId, queryId, principal, page, size, sortDirection, sortColumn);
-        /* check */
-        if (!hasQueryPermission(containerId, databaseId, queryId, "QUERY_RE_EXECUTE", principal)) {
-            log.error("Missing re-execute query permission");
-            throw new NotAllowedException("Missing re-execute query permission");
-        }
-        validateDataParams(page, size, sortDirection, sortColumn);
+        endpointValidator.validateDataParams(page, size, sortDirection, sortColumn);
         /* execute */
         final Query query = storeService.findOne(containerId, databaseId, queryId, principal);
         final QueryResultDto result = queryService.reExecute(containerId, databaseId, query, page, size,
@@ -110,6 +106,7 @@ public class QueryEndpoint extends AbstractEndpoint {
 
     @GetMapping("/{queryId}/data/count")
     @Transactional(readOnly = true)
+    @PreAuthorize("hasAuthority('re-execute-query')")
     @Timed(value = "query.reexecute.count", description = "Time needed to re-execute a query")
     @Operation(summary = "Re-execute some query", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<Long> reExecuteCount(@NotNull @PathVariable("id") Long containerId,
@@ -121,11 +118,6 @@ public class QueryEndpoint extends AbstractEndpoint {
             DatabaseConnectionException, UserNotFoundException {
         log.debug("endpoint re-execute query count, containerId={}, databaseId={}, queryId={}, principal={}",
                 containerId, databaseId, queryId, principal);
-        /* check */
-        if (!hasQueryPermission(containerId, databaseId, queryId, "QUERY_RE_EXECUTE", principal)) {
-            log.error("Missing re-execute query permission");
-            throw new NotAllowedException("Missing re-execute query permission");
-        }
         /* execute */
         final Query query = storeService.findOne(containerId, databaseId, queryId, principal);
         final Long result = queryService.reExecuteCount(containerId, databaseId, query, principal);
@@ -136,6 +128,7 @@ public class QueryEndpoint extends AbstractEndpoint {
 
     @GetMapping("/{queryId}/export")
     @Transactional(readOnly = true)
+    @PreAuthorize("hasAuthority('export-query')")
     @Timed(value = "query.export", description = "Time needed to export query data")
     @Operation(summary = "Exports some query", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<?> export(@NotNull @PathVariable("id") Long containerId,
@@ -148,10 +141,6 @@ public class QueryEndpoint extends AbstractEndpoint {
             QueryMalformedException, DatabaseConnectionException, UserNotFoundException {
         log.debug("endpoint export query, containerId={}, databaseId={}, queryId={}, accept={}, principal={}",
                 containerId, databaseId, queryId, accept, principal);
-        if (!hasQueryPermission(containerId, databaseId, queryId, "QUERY_EXPORT", principal)) {
-            log.error("Missing export query permission");
-            throw new NotAllowedException("Missing export query permission");
-        }
         log.trace("checking if query exists in the query store");
         final Query query = storeService.findOne(containerId, databaseId, queryId, principal);
         log.trace("querystore returned query {}", query);
diff --git a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/StoreEndpoint.java b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/StoreEndpoint.java
index 54e31bf3d1d0ac4879838919a3b0079f0099ccb3..03c527325cfb2f0d68de2c72a7bf94dca0888b47 100644
--- a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/StoreEndpoint.java
+++ b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/StoreEndpoint.java
@@ -19,6 +19,7 @@ import lombok.extern.log4j.Log4j2;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
 
@@ -31,7 +32,7 @@ import java.util.stream.Collectors;
 @Log4j2
 @RestController
 @RequestMapping("/api/container/{id}/database/{databaseId}/query")
-public class StoreEndpoint extends AbstractEndpoint {
+public class StoreEndpoint {
 
     private final UserMapper userMapper;
     private final QueryMapper queryMapper;
@@ -41,35 +42,30 @@ public class StoreEndpoint extends AbstractEndpoint {
     private final IdentifierService identifierService;
 
     @Autowired
-    public StoreEndpoint(QueryConfig queryConfig, UserMapper userMapper, QueryMapper queryMapper,
-                         UserService userService, StoreService storeService, DatabaseService databaseService,
-                         IdentifierService identifierService, TableService tableService, AccessService accessService,
-                         IdentifierMapper identifierMapper, IdentifierService identifierService1) {
-        super(tableService, accessService, databaseService, identifierService, queryConfig);
+    public StoreEndpoint(UserMapper userMapper, QueryMapper queryMapper, UserService userService,
+                         StoreService storeService, IdentifierMapper identifierMapper,
+                         IdentifierService identifierService) {
         this.userMapper = userMapper;
         this.queryMapper = queryMapper;
         this.userService = userService;
         this.storeService = storeService;
         this.identifierMapper = identifierMapper;
-        this.identifierService = identifierService1;
+        this.identifierService = identifierService;
     }
 
     @GetMapping
     @Transactional(readOnly = true)
     @Timed(value = "store.list", description = "Time needed to list queries from the query store")
+    @PreAuthorize("hasAuthority('find-queries')")
     @Operation(summary = "Find queries", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<List<QueryBriefDto>> findAll(@NotNull @PathVariable("id") Long containerId,
                                                        @NotNull @PathVariable("databaseId") Long databaseId,
                                                        @RequestParam(value = "persisted", required = false) Boolean persisted,
                                                        Principal principal) throws QueryStoreException,
-            DatabaseNotFoundException, ImageNotSupportedException, ContainerNotFoundException, NotAllowedException,
+            DatabaseNotFoundException, ImageNotSupportedException, ContainerNotFoundException,
             DatabaseConnectionException, TableMalformedException, UserNotFoundException {
         log.debug("endpoint list queries, containerId={}, databaseId={}, persisted={}, principal={}", containerId,
                 databaseId, persisted, principal);
-        if (!hasDatabasePermission(containerId, databaseId, "QUERY_VIEW_ALL", principal)) {
-            log.error("Missing view all queries permission");
-            throw new NotAllowedException("Missing view all queries permission");
-        }
         final List<Query> queries = storeService.findAll(containerId, databaseId, persisted, principal);
         final List<Identifier> identifiers = identifierService.findAll();
         final List<User> users = userService.findAll();
@@ -93,6 +89,7 @@ public class StoreEndpoint extends AbstractEndpoint {
 
     @GetMapping("/{queryId}")
     @Transactional(readOnly = true)
+    @PreAuthorize("hasAuthority('find-query')")
     @Timed(value = "store.find", description = "Time needed to find a query from the query store")
     @Operation(summary = "Find some query", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<QueryDto> find(@NotNull @PathVariable("id") Long containerId,
@@ -104,10 +101,6 @@ public class StoreEndpoint extends AbstractEndpoint {
             DatabaseConnectionException {
         log.debug("endpoint find query, containerId={}, databaseId={}, queryId={}, principal={}", containerId, databaseId,
                 queryId, principal);
-        if (!hasQueryPermission(containerId, databaseId, queryId, "QUERY_VIEW", principal)) {
-            log.error("Missing view query permission");
-            throw new NotAllowedException("Missing view query permission");
-        }
         final Query query = storeService.findOne(containerId, databaseId, queryId, principal);
         final QueryDto dto = queryMapper.queryToQueryDto(query);
         final User creator = userService.findByUsername(query.getCreatedBy());
@@ -124,6 +117,7 @@ public class StoreEndpoint extends AbstractEndpoint {
 
     @PutMapping("/{queryId}")
     @Transactional(readOnly = true)
+    @PreAuthorize("hasAuthority('persist-query')")
     @Timed(value = "store.persist", description = "Time needed to persist a query in the query store")
     @Operation(summary = "Persist some query", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<QueryDto> persist(@NotNull @PathVariable("id") Long containerId,
@@ -131,14 +125,10 @@ public class StoreEndpoint extends AbstractEndpoint {
                                             @NotNull @PathVariable("queryId") Long queryId,
                                             @NotNull Principal principal)
             throws QueryStoreException, DatabaseNotFoundException, ImageNotSupportedException,
-            NotAllowedException, DatabaseConnectionException, UserNotFoundException, QueryNotFoundException,
+            DatabaseConnectionException, UserNotFoundException, QueryNotFoundException,
             QueryAlreadyPersistedException {
         log.debug("endpoint persist query, container, containerId={}, databaseId={}, queryId={}, principal={}",
                 containerId, databaseId, queryId, principal);
-        if (!hasQueryPermission(containerId, databaseId, queryId, "QUERY_PERSIST", principal)) {
-            log.error("Missing query persist permission");
-            throw new NotAllowedException("Missing query persist permission");
-        }
         final Query check = storeService.findOne(containerId, databaseId, queryId, principal);
         if (check.getIsPersisted()) {
             log.error("Failed to persist, is already persisted");
diff --git a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableDataEndpoint.java b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableDataEndpoint.java
index 277e622b6273f66827b116972b8498e30b20dc43..1edc99ccb0a102d0023e3729ea8fa6bd16b69068 100644
--- a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableDataEndpoint.java
+++ b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableDataEndpoint.java
@@ -6,15 +6,16 @@ import at.tuwien.api.database.query.QueryResultDto;
 import at.tuwien.api.database.table.TableCsvDeleteDto;
 import at.tuwien.api.database.table.TableCsvDto;
 import at.tuwien.api.database.table.TableCsvUpdateDto;
-import at.tuwien.config.QueryConfig;
 import at.tuwien.exception.*;
 import at.tuwien.service.*;
+import at.tuwien.validation.EndpointValidator;
 import io.micrometer.core.annotation.Timed;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.security.SecurityRequirement;
 import lombok.extern.log4j.Log4j2;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
 
@@ -27,21 +28,21 @@ import java.time.Instant;
 @CrossOrigin(origins = "*")
 @RestController
 @RequestMapping("/api/container/{id}/database/{databaseId}/table/{tableId}/data")
-public class TableDataEndpoint extends AbstractEndpoint {
+public class TableDataEndpoint {
 
     private final QueryService queryService;
+    private final EndpointValidator endpointValidator;
 
     @Autowired
-    public TableDataEndpoint(QueryService queryService, DatabaseService databaseService,
-                             IdentifierService identifierService, TableService tableService,
-                             AccessService accessService, QueryConfig queryConfig) {
-        super(tableService, accessService, databaseService, identifierService, queryConfig);
+    public TableDataEndpoint(QueryService queryService, EndpointValidator endpointValidator) {
         this.queryService = queryService;
+        this.endpointValidator = endpointValidator;
     }
 
     @PostMapping
     @Transactional
     @Timed(value = "data.insert", description = "Time needed to insert data into a table")
+    @PreAuthorize("hasAuthority('modify-data')")
     @Operation(summary = "Insert data", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<Void> insert(@NotNull @PathVariable("id") Long containerId,
                                        @NotNull @PathVariable("databaseId") Long databaseId,
@@ -53,10 +54,6 @@ public class TableDataEndpoint extends AbstractEndpoint {
             UserNotFoundException {
         log.debug("endpoint insert data, containerId={}, databaseId={}, tableId={}, data={}, principal={}", containerId,
                 databaseId, tableId, data, principal);
-        if (!hasTablePermission(containerId, databaseId, tableId, "DATA_INSERT", principal)) {
-            log.error("Missing data insert permission");
-            throw new NotAllowedException("Missing data insert permission");
-        }
         queryService.insert(containerId, databaseId, tableId, data, principal);
         return ResponseEntity.accepted()
                 .build();
@@ -65,6 +62,7 @@ public class TableDataEndpoint extends AbstractEndpoint {
     @PutMapping
     @Transactional
     @Deprecated
+    @PreAuthorize("hasAuthority('modify-data')")
     @Timed(value = "data.update", description = "Time needed to update data in a table")
     @Operation(summary = "Update data", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<Void> update(@NotNull @PathVariable("id") Long containerId,
@@ -73,14 +71,10 @@ public class TableDataEndpoint extends AbstractEndpoint {
                                        @NotNull @Valid @RequestBody TableCsvUpdateDto data,
                                        @NotNull Principal principal)
             throws TableNotFoundException, DatabaseNotFoundException, TableMalformedException,
-            ImageNotSupportedException, NotAllowedException, DatabaseConnectionException, QueryMalformedException,
+            ImageNotSupportedException, DatabaseConnectionException, QueryMalformedException,
             UserNotFoundException {
         log.debug("endpoint update data, containerId={}, databaseId={}, tableId={}, data={}, principal={}", containerId,
                 databaseId, tableId, data, principal);
-        if (!hasTablePermission(containerId, databaseId, tableId, "DATA_UPDATE", principal)) {
-            log.error("Missing data update permission");
-            throw new NotAllowedException("Missing data update permission");
-        }
         queryService.update(containerId, databaseId, tableId, data, principal);
         return ResponseEntity.accepted()
                 .build();
@@ -88,6 +82,7 @@ public class TableDataEndpoint extends AbstractEndpoint {
 
     @DeleteMapping
     @Transactional
+    @PreAuthorize("hasAuthority('modify-data')")
     @Timed(value = "data.delete", description = "Time needed to delete data into a table")
     @Operation(summary = "Delete data", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<Void> delete(@NotNull @PathVariable("id") Long containerId,
@@ -96,14 +91,10 @@ public class TableDataEndpoint extends AbstractEndpoint {
                                        @NotNull @Valid @RequestBody TableCsvDeleteDto data,
                                        @NotNull Principal principal)
             throws TableNotFoundException, DatabaseNotFoundException, TableMalformedException,
-            ImageNotSupportedException, TupleDeleteException, NotAllowedException, ContainerNotFoundException,
+            ImageNotSupportedException, TupleDeleteException, ContainerNotFoundException,
             DatabaseConnectionException, QueryMalformedException, UserNotFoundException {
         log.debug("endpoint delete data, containerId={}, databaseId={}, tableId={}, data={}, principal={}", containerId,
                 databaseId, tableId, data, principal);
-        if (!hasTablePermission(containerId, databaseId, tableId, "DATA_DELETE", principal)) {
-            log.error("Missing data delete permission");
-            throw new NotAllowedException("Missing data delete permission");
-        }
         queryService.delete(containerId, databaseId, tableId, data, principal);
         return ResponseEntity.accepted()
                 .build();
@@ -111,6 +102,7 @@ public class TableDataEndpoint extends AbstractEndpoint {
 
     @PostMapping("/import")
     @Transactional
+    @PreAuthorize("hasAuthority('modify-data')")
     @Timed(value = "data.insertbulk", description = "Time needed to insert data from .csv into a table")
     @Operation(summary = "Insert data from csv", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<Void> importCsv(@NotNull @PathVariable("id") Long containerId,
@@ -119,14 +111,10 @@ public class TableDataEndpoint extends AbstractEndpoint {
                                           @NotNull @Valid @RequestBody ImportDto data,
                                           @NotNull Principal principal)
             throws TableNotFoundException, DatabaseNotFoundException, TableMalformedException,
-            ImageNotSupportedException, ContainerNotFoundException, NotAllowedException, DatabaseConnectionException,
+            ImageNotSupportedException, ContainerNotFoundException, DatabaseConnectionException,
             QueryMalformedException, UserNotFoundException {
         log.debug("endpoint insert data from csv, containerId={}, databaseId={}, tableId={}, data={}, principal={}",
                 containerId, databaseId, tableId, data, principal);
-        if (!hasTablePermission(containerId, databaseId, tableId, "DATA_INSERT", principal)) {
-            log.error("Missing data insert permission");
-            throw new NotAllowedException("Missing data insert permission");
-        }
         queryService.insert(containerId, databaseId, tableId, data, principal);
         return ResponseEntity.accepted()
                 .build();
@@ -134,6 +122,7 @@ public class TableDataEndpoint extends AbstractEndpoint {
 
     @RequestMapping(method = {RequestMethod.GET, RequestMethod.HEAD})
     @Transactional(readOnly = true)
+    @PreAuthorize("hasAuthority('get-data')")
     @Timed(value = "data.all", description = "Time needed to find all data from a table")
     @Operation(summary = "Find data", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<QueryResultDto> getAll(@NotNull @PathVariable("id") Long containerId,
@@ -147,15 +136,11 @@ public class TableDataEndpoint extends AbstractEndpoint {
                                                  @RequestParam(required = false) String sortColumn)
             throws TableNotFoundException, DatabaseNotFoundException, DatabaseConnectionException,
             ImageNotSupportedException, TableMalformedException, PaginationException, ContainerNotFoundException,
-            NotAllowedException, QueryMalformedException, SortException, UserNotFoundException {
+            QueryMalformedException, UserNotFoundException, SortException {
         log.debug("endpoint find table data, containerId={}, databaseId={}, tableId={}, principal={}, timestamp={}, page={}, size={}, sortDirection={}, sortColumn={}",
                 containerId, databaseId, tableId, principal, timestamp, page, size, sortDirection, sortColumn);
         /* check */
-        if (!hasTablePermission(containerId, databaseId, tableId, "DATA_VIEW", principal)) {
-            log.error("Missing data view permission");
-            throw new NotAllowedException("Missing data view permission");
-        }
-        validateDataParams(page, size, sortDirection, sortColumn);
+        endpointValidator.validateDataParams(page, size, sortDirection, sortColumn);
         final QueryResultDto response = queryService.tableFindAll(containerId, databaseId, tableId, timestamp, page, size, principal);
         log.trace("find table data resulted in result {}", response);
         return ResponseEntity.ok()
@@ -163,6 +148,7 @@ public class TableDataEndpoint extends AbstractEndpoint {
     }
 
     @GetMapping("/count")
+    @PreAuthorize("hasAuthority('get-data')")
     @Timed(value = "data.all.count", description = "Time needed to get count of all data from a table")
     @Operation(summary = "Find data", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<Long> getCount(@NotNull @PathVariable("id") Long containerId,
@@ -171,15 +157,10 @@ public class TableDataEndpoint extends AbstractEndpoint {
                                                  @NotNull Principal principal,
                                                  @RequestParam(required = false) Instant timestamp)
             throws TableNotFoundException, DatabaseNotFoundException, DatabaseConnectionException,
-            ImageNotSupportedException, TableMalformedException, PaginationException, ContainerNotFoundException,
-            QueryStoreException, NotAllowedException, QueryMalformedException, SortException, UserNotFoundException {
+            ImageNotSupportedException, TableMalformedException, ContainerNotFoundException,
+            QueryStoreException, QueryMalformedException, UserNotFoundException {
         log.debug("endpoint find table data, containerId={}, databaseId={}, tableId={}, principal={}, timestamp={}",
                 containerId, databaseId, tableId, principal, timestamp);
-        /* check */
-        if (!hasTablePermission(containerId, databaseId, tableId, "DATA_VIEW", principal)) {
-            log.error("Missing data view permission");
-            throw new NotAllowedException("Missing data view permission");
-        }
         /* find */
         final Long count = queryService.tableCount(containerId, databaseId, tableId, timestamp, principal);
         log.debug("table data count is {} tuples", count);
diff --git a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableHistoryEndpoint.java b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableHistoryEndpoint.java
index 9b1225baee15c7687882329e5c7f7613da0ef218..e2fff813a7d0d913c98c234890c551b561c0c4ae 100644
--- a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableHistoryEndpoint.java
+++ b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableHistoryEndpoint.java
@@ -10,6 +10,7 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement;
 import lombok.extern.log4j.Log4j2;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
 
@@ -21,34 +22,28 @@ import java.util.List;
 @CrossOrigin(origins = "*")
 @RestController
 @RequestMapping("/api/container/{id}/database/{databaseId}/table/{tableId}/history")
-public class TableHistoryEndpoint extends AbstractEndpoint {
+public class TableHistoryEndpoint {
 
     private final TableService tableService;
 
     @Autowired
-    public TableHistoryEndpoint(TableService tableService, DatabaseService databaseService,
-                                IdentifierService identifierService, AccessService accessService,
-                                QueryConfig queryConfig) {
-        super(tableService, accessService, databaseService, identifierService, queryConfig);
+    public TableHistoryEndpoint(TableService tableService) {
         this.tableService = tableService;
     }
 
     @RequestMapping(method = {RequestMethod.GET, RequestMethod.HEAD})
     @Transactional(readOnly = true)
+    @PreAuthorize("hasAuthority('data-history')")
     @Timed(value = "history.list", description = "Time needed to retrieve table history")
     @Operation(summary = "Find all history", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<List<TableHistoryDto>> getAll(@NotNull @PathVariable("id") Long containerId,
                                                         @NotNull @PathVariable("databaseId") Long databaseId,
                                                         @NotNull @PathVariable("tableId") Long tableId,
                                                         @NotNull Principal principal)
-            throws TableNotFoundException, QueryMalformedException, DatabaseNotFoundException, NotAllowedException,
+            throws TableNotFoundException, QueryMalformedException, DatabaseNotFoundException,
             QueryStoreException, DatabaseConnectionException, UserNotFoundException {
         log.debug("endpoint find all history, containerId={}, databaseid={}, tableId={}, principal={}", containerId,
                 databaseId, tableId, principal);
-        if (!hasTablePermission(containerId, databaseId, tableId, "DATA_HISTORY", principal)) {
-            log.error("Missing data history permission");
-            throw new NotAllowedException("Missing data history permission");
-        }
         final List<TableHistoryDto> history = tableService.findHistory(containerId, databaseId, tableId, principal);
         log.trace("find all history resulted in history {}", history);
         return ResponseEntity.ok(history);
diff --git a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/ViewEndpoint.java b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/ViewEndpoint.java
index b5b2bf6954025a38f46c79de4b489067324b0945..01f35fd3398fb2cb3d20fc12c859198ce3e9bb52 100644
--- a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/ViewEndpoint.java
+++ b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/ViewEndpoint.java
@@ -3,15 +3,13 @@ package at.tuwien.endpoint;
 import at.tuwien.api.database.ViewBriefDto;
 import at.tuwien.api.database.ViewCreateDto;
 import at.tuwien.api.database.ViewDto;
-import at.tuwien.api.database.query.ExecuteStatementDto;
 import at.tuwien.api.database.query.QueryResultDto;
-import at.tuwien.api.database.query.QueryTypeDto;
-import at.tuwien.config.QueryConfig;
 import at.tuwien.entities.database.Database;
 import at.tuwien.entities.database.View;
 import at.tuwien.exception.*;
 import at.tuwien.mapper.ViewMapper;
 import at.tuwien.service.*;
+import at.tuwien.validation.EndpointValidator;
 import io.micrometer.core.annotation.Timed;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.security.SecurityRequirement;
@@ -19,6 +17,7 @@ import lombok.extern.log4j.Log4j2;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
 
@@ -32,38 +31,35 @@ import java.util.stream.Collectors;
 @CrossOrigin(origins = "*")
 @RestController
 @RequestMapping("/api/container/{id}/database/{databaseId}/view")
-public class ViewEndpoint extends AbstractEndpoint {
+public class ViewEndpoint {
 
     private final ViewMapper viewMapper;
     private final ViewService viewService;
     private final QueryService queryService;
     private final DatabaseService databaseService;
+    private final EndpointValidator endpointValidator;
 
     @Autowired
-    public ViewEndpoint(ViewService viewService, DatabaseService databaseService, IdentifierService identifierService,
-                        ViewMapper viewMapper, QueryService queryService, TableService tableService,
-                        AccessService accessService, QueryConfig queryConfig) {
-        super(tableService, accessService, databaseService, identifierService, queryConfig);
+    public ViewEndpoint(ViewService viewService, DatabaseService databaseService,
+                        ViewMapper viewMapper, QueryService queryService, EndpointValidator endpointValidator) {
         this.viewService = viewService;
         this.databaseService = databaseService;
         this.viewMapper = viewMapper;
         this.queryService = queryService;
+        this.endpointValidator = endpointValidator;
     }
 
     @GetMapping
     @Transactional(readOnly = true)
+    @PreAuthorize("hasAuthority('find-views')")
     @Timed(value = "view.list", description = "Time needed to list all views in a database")
     @Operation(summary = "Find all views", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<List<ViewBriefDto>> findAll(@NotNull @PathVariable("id") Long containerId,
                                                       @NotNull @PathVariable("databaseId") Long databaseId,
                                                       Principal principal) throws DatabaseNotFoundException,
-            NotAllowedException, UserNotFoundException {
+            UserNotFoundException {
         log.debug("endpoint find all views, containerId={}, databaseId={}, principal={}", containerId,
                 databaseId, principal);
-        if (!hasDatabasePermission(containerId, databaseId, "LIST_VIEWS", principal)) {
-            log.error("Missing list views permission");
-            throw new NotAllowedException("Missing list views permission");
-        }
         final Database database = databaseService.find(containerId, databaseId);
         log.trace("find all views for database {}", database);
         final List<ViewBriefDto> views = viewService.findAll(databaseId, principal)
@@ -76,6 +72,7 @@ public class ViewEndpoint extends AbstractEndpoint {
 
     @PostMapping
     @Transactional
+    @PreAuthorize("hasAuthority('create-view')")
     @Timed(value = "view.create", description = "Time needed to create a view")
     @Operation(summary = "Create a view", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<ViewBriefDto> create(@NotNull @PathVariable("id") Long containerId,
@@ -86,10 +83,6 @@ public class ViewEndpoint extends AbstractEndpoint {
             UserNotFoundException {
         log.debug("endpoint create view, containerId={}, databaseId={}, data={}, principal={}", containerId,
                 databaseId, data, principal);
-        if (!hasDatabasePermission(containerId, databaseId, "CREATE_VIEW", principal)) {
-            log.error("Missing create view permission");
-            throw new NotAllowedException("Missing create view permission");
-        }
         final Database database = databaseService.find(containerId, databaseId);
         log.trace("create view for database {}", database);
         final View view;
@@ -102,6 +95,7 @@ public class ViewEndpoint extends AbstractEndpoint {
 
     @GetMapping("/{viewId}")
     @Transactional(readOnly = true)
+    @PreAuthorize("hasAuthority('find-view')")
     @Timed(value = "view.find", description = "Time needed to find a view")
     @Operation(summary = "Find one view", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<ViewDto> find(@NotNull @PathVariable("id") Long containerId,
@@ -111,10 +105,6 @@ public class ViewEndpoint extends AbstractEndpoint {
             NotAllowedException, ViewNotFoundException, UserNotFoundException {
         log.debug("endpoint find view, containerId={}, databaseId={}, viewId={}, principal={}", containerId,
                 databaseId, viewId, principal);
-        if (!hasDatabasePermission(containerId, databaseId, "FIND_VIEW", principal)) {
-            log.error("Missing find views permission");
-            throw new NotAllowedException("Missing find views permission");
-        }
         final Database database = databaseService.find(containerId, databaseId);
         log.trace("find view for database {}", database);
         final ViewDto view = viewMapper.viewToViewDto(viewService.findById(databaseId, viewId, principal));
@@ -124,20 +114,17 @@ public class ViewEndpoint extends AbstractEndpoint {
 
     @DeleteMapping("/{viewId}")
     @Transactional
+    @PreAuthorize("hasAuthority('delete-view')")
     @Timed(value = "view.delete", description = "Time needed to delete a view")
     @Operation(summary = "Delete one view", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<?> delete(@NotNull @PathVariable("id") Long containerId,
                                     @NotNull @PathVariable("databaseId") Long databaseId,
                                     @NotNull @PathVariable("viewId") Long viewId,
                                     @NotNull Principal principal) throws DatabaseNotFoundException,
-            NotAllowedException, ViewNotFoundException, UserNotFoundException, DatabaseConnectionException,
+            ViewNotFoundException, UserNotFoundException, DatabaseConnectionException,
             ViewMalformedException, QueryMalformedException {
         log.debug("endpoint delete view, containerId={}, databaseId={}, viewId={}, principal={}", containerId,
                 databaseId, viewId, principal);
-        if (!hasDatabasePermission(containerId, databaseId, "DELETE_VIEW", principal)) {
-            log.error("Missing delete view permission");
-            throw new NotAllowedException("Missing delete view permission");
-        }
         viewService.delete(containerId, databaseId, viewId, principal);
         return ResponseEntity.accepted()
                 .build();
@@ -145,6 +132,7 @@ public class ViewEndpoint extends AbstractEndpoint {
 
     @GetMapping("/{viewId}/data")
     @Transactional(readOnly = true)
+    @PreAuthorize("hasAuthority('view-view')")
     @Timed(value = "view.data", description = "Time needed to retrieve data from a view")
     @Operation(summary = "Find view data", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<QueryResultDto> data(@NotNull @PathVariable("id") Long containerId,
@@ -159,11 +147,7 @@ public class ViewEndpoint extends AbstractEndpoint {
         log.debug("endpoint find view data, containerId={}, databaseId={}, viewId={}, principal={}, page={}, size={}",
                 containerId, databaseId, viewId, principal, page, size);
         /* check */
-        if (!hasDatabasePermission(containerId, databaseId, "DATA_VIEW", principal)) {
-            log.error("Missing view data in view permission");
-            throw new NotAllowedException("Missing view data in view permission");
-        }
-        validateDataParams(page, size);
+        endpointValidator.validateDataParams(page, size);
         /* find */
         final Database database = databaseService.find(containerId, databaseId);
         log.trace("find view data for database {}", database);
@@ -177,6 +161,7 @@ public class ViewEndpoint extends AbstractEndpoint {
 
     @GetMapping("/{viewId}/data/count")
     @Transactional(readOnly = true)
+    @PreAuthorize("hasAuthority('view-view')")
     @Timed(value = "view.data.count", description = "Time needed to retrieve data count from a view")
     @Operation(summary = "Find view data count", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<Long> count(@NotNull @PathVariable("id") Long containerId,
@@ -188,11 +173,6 @@ public class ViewEndpoint extends AbstractEndpoint {
             ImageNotSupportedException, ColumnParseException, UserNotFoundException, ContainerNotFoundException, ViewMalformedException {
         log.debug("endpoint find view data count, containerId={}, databaseId={}, viewId={}, principal={}",
                 containerId, databaseId, viewId, principal);
-        /* check */
-        if (!hasDatabasePermission(containerId, databaseId, "DATA_VIEW", principal)) {
-            log.error("Missing view data in view permission");
-            throw new NotAllowedException("Missing view data in view permission");
-        }
         /* find */
         final Database database = databaseService.find(containerId, databaseId);
         log.trace("find view data for database {}", database);
diff --git a/fda-query-service/rest-service/src/main/java/at/tuwien/validation/EndpointValidator.java b/fda-query-service/rest-service/src/main/java/at/tuwien/validation/EndpointValidator.java
new file mode 100644
index 0000000000000000000000000000000000000000..61775a087420a25a9ff0b7dc6de167b67e4e3497
--- /dev/null
+++ b/fda-query-service/rest-service/src/main/java/at/tuwien/validation/EndpointValidator.java
@@ -0,0 +1,77 @@
+package at.tuwien.validation;
+
+import at.tuwien.SortType;
+import at.tuwien.api.database.query.ExecuteStatementDto;
+import at.tuwien.config.QueryConfig;
+import at.tuwien.exception.PaginationException;
+import at.tuwien.exception.QueryMalformedException;
+import at.tuwien.exception.SortException;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.stereotype.Component;
+
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@Log4j2
+@Component
+public class EndpointValidator {
+
+    private final QueryConfig queryConfig;
+
+    public EndpointValidator(QueryConfig queryConfig) {
+        this.queryConfig = queryConfig;
+    }
+
+    public void validateDataParams(Long page, Long size) throws PaginationException {
+        log.trace("validate data params, page={}, size={}", page, size);
+        if ((page == null && size != null) || (page != null && size == null)) {
+            log.error("Failed to validate page and/or size number, either both are present or none");
+            throw new PaginationException("Failed to validate page and/or size number");
+        }
+        if (page != null && page < 0) {
+            log.error("Failed to validate page number, is lower than zero");
+            throw new PaginationException("Failed to validate page number");
+        }
+        if (size != null && size <= 0) {
+            log.error("Failed to validate size number, is lower or equal than zero");
+            throw new PaginationException("Failed to validate size number");
+        }
+    }
+
+    public void validateDataParams(Long page, Long size, SortType sortDirection, String sortColumn)
+            throws PaginationException, SortException {
+        log.trace("validate data params, page={}, size={}, sortDirection={}, sortColumn={}", page, size,
+                sortDirection, sortColumn);
+        validateDataParams(page, size);
+        if ((sortDirection == null && sortColumn != null) || (sortDirection != null && sortColumn == null)) {
+            log.error("Failed to validate sort direction and/or sort column, either both are present or none");
+            throw new SortException("Failed to validate sort direction and/or sort column");
+        }
+    }
+
+    /**
+     * Do not allow aggregate functions and comments
+     * https://mariadb.com/kb/en/aggregate-functions/
+     */
+    public void validateForbiddenStatements(ExecuteStatementDto data) throws QueryMalformedException {
+        final List<String> words = new LinkedList<>();
+        Arrays.stream(queryConfig.getNotSupportedKeywords())
+                .forEach(keyword -> {
+                    final Pattern pattern = Pattern.compile(keyword);
+                    final Matcher matcher = pattern.matcher(data.getStatement());
+                    final boolean found = matcher.find();
+                    if (found) {
+                        words.add(keyword);
+                    }
+                });
+        if (words.size() == 0) {
+            return;
+        }
+        log.error("Query contains forbidden keyword(s): {}", words);
+        throw new QueryMalformedException("Query contains forbidden keyword(s): " + Arrays.toString(words.toArray()));
+    }
+
+}
diff --git a/fda-query-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java b/fda-query-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java
index 43ed8c4414f3c9eb8a51d7303150cb6890c2334f..c62dae93fd85df9e890c560b430cd64cee746402 100644
--- a/fda-query-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java
+++ b/fda-query-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java
@@ -77,11 +77,7 @@ public abstract class BaseUnitTest {
             .username(USER_1_USERNAME)
             .email(USER_1_EMAIL)
             .emailVerified(true)
-            .themeDark(false)
-            .password(USER_1_PASSWORD)
             .databasePassword(USER_1_DATABASE_PASSWORD)
-            .created(USER_1_CREATED)
-            .lastModified(USER_1_CREATED)
             .build();
 
     public final static UserDto USER_1_DTO = UserDto.builder()
@@ -89,8 +85,6 @@ public abstract class BaseUnitTest {
             .username(USER_1_USERNAME)
             .email(USER_1_EMAIL)
             .emailVerified(true)
-            .themeDark(false)
-            .password(USER_1_PASSWORD)
             .build();
 
     public final static UserDetails USER_1_DETAILS = UserDetailsDto.builder()
@@ -115,11 +109,7 @@ public abstract class BaseUnitTest {
             .username(USER_2_USERNAME)
             .email(USER_2_EMAIL)
             .emailVerified(true)
-            .themeDark(false)
-            .password(USER_2_PASSWORD)
             .databasePassword(USER_2_DATABASE_PASSWORD)
-            .created(USER_2_CREATED)
-            .lastModified(USER_2_CREATED)
             .build();
 
     public final static UserDetails USER_2_DETAILS = UserDetailsDto.builder()
@@ -144,11 +134,7 @@ public abstract class BaseUnitTest {
             .username(USER_3_USERNAME)
             .email(USER_3_EMAIL)
             .emailVerified(true)
-            .themeDark(false)
-            .password(USER_3_PASSWORD)
             .databasePassword(USER_3_DATABASE_PASSWORD)
-            .created(USER_3_CREATED)
-            .lastModified(USER_3_CREATED)
             .build();
 
     public final static UserDetails USER_3_DETAILS = UserDetailsDto.builder()
@@ -174,8 +160,6 @@ public abstract class BaseUnitTest {
             .username(USER_4_USERNAME)
             .email(USER_4_EMAIL)
             .emailVerified(USER_4_EMAIL_VERIFIED)
-            .themeDark(USER_4_THEME_DARK)
-            .password(USER_4_PASSWORD)
             .databasePassword(USER_4_DATABASE_PASSWORD)
             .build();
 
diff --git a/fda-table-service/rest-service/src/main/java/at/tuwien/endpoints/AbstractEndpoint.java b/fda-table-service/rest-service/src/main/java/at/tuwien/endpoints/AbstractEndpoint.java
deleted file mode 100644
index 57243400eebc92003549278b15f8eb3047e35c13..0000000000000000000000000000000000000000
--- a/fda-table-service/rest-service/src/main/java/at/tuwien/endpoints/AbstractEndpoint.java
+++ /dev/null
@@ -1,110 +0,0 @@
-package at.tuwien.endpoints;
-
-import at.tuwien.entities.database.AccessType;
-import at.tuwien.entities.database.Database;
-import at.tuwien.entities.database.DatabaseAccess;
-import at.tuwien.exception.AccessDeniedException;
-import at.tuwien.exception.DatabaseNotFoundException;
-import at.tuwien.service.AccessService;
-import at.tuwien.service.DatabaseService;
-import at.tuwien.service.TableService;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.core.Authentication;
-
-import java.security.Principal;
-import java.util.List;
-
-
-@Slf4j
-public abstract class AbstractEndpoint {
-
-    private final AccessService accessService;
-    private final DatabaseService databaseService;
-
-    @Autowired
-    protected AbstractEndpoint(AccessService accessService, DatabaseService databaseService) {
-        this.accessService = accessService;
-        this.databaseService = databaseService;
-    }
-
-    protected Boolean hasDatabasePermission(Long containerId, Long databaseId, String permissionCode,
-                                            Principal principal) {
-        log.debug("validate has database permission, containerId={}, databaseId={}, permissionCode={}, principal={}",
-                containerId, databaseId, permissionCode, principal);
-        final Database database;
-        try {
-            database = databaseService.find(containerId, databaseId);
-        } catch (DatabaseNotFoundException e) {
-            log.error("Failed to find database with id {}", databaseId);
-            return false;
-        }
-        if (principal != null && database.getCreator().getUsername().equals(principal.getName())) {
-            log.debug("grant permission {} because user is creator of database with id {}", permissionCode, databaseId);
-            return true;
-        }
-        /* view-only operations are allowed on public databases */
-        if (database.getIsPublic() && List.of("TABLE_CREATE", "TABLES_VIEW").contains(permissionCode)) {
-            log.debug("grant permission {} because database is public", permissionCode);
-            return true;
-        }
-        /* modification operations are limited to the creator */
-        if (principal == null) {
-            log.error("Failed to grant permission {} because principal is null", permissionCode);
-            return false;
-        }
-        final Authentication authentication = (Authentication) principal /* with pre-authorization this always holds */;
-        if (authentication.getAuthorities().stream().noneMatch(a -> a.getAuthority().equals("ROLE_RESEARCHER"))) {
-            log.error("Failed to grant permission {} because current user misses authority 'ROLE_RESEARCHER'",
-                    permissionCode);
-            return false;
-        }
-        log.debug("grant permission {} because user is creator", permissionCode);
-        return true;
-    }
-
-    protected Boolean hasTablePermission(Long containerId, Long databaseId, Long tableId, String permissionCode,
-                                         Principal principal) throws AccessDeniedException {
-        log.debug("validate has table permissions, containerId={}, databaseId={}, tableId={}, permissionCode={}, principal={}",
-                containerId, databaseId, tableId, permissionCode, principal);
-        final Database database;
-        try {
-            database = databaseService.find(containerId, databaseId);
-        } catch (DatabaseNotFoundException e) {
-            log.error("Failed to find database with id {}", databaseId);
-            return false;
-        }
-        if (principal != null && database.getCreator().getUsername().equals(principal.getName())) {
-            log.debug("grant permission {} because user is creator of database with id {}", permissionCode, databaseId);
-            return true;
-        }
-        /* view-only operations are allowed on public databases */
-        if (database.getIsPublic() && List.of("TABLE_INFO").contains(permissionCode)) {
-            log.debug("grant permission {} because database is public", permissionCode);
-            return true;
-        }
-        /* modification operations are limited to the creator */
-        if (principal == null) {
-            log.error("Failed to grant permission {} because principal is null", permissionCode);
-            return false;
-        }
-        final Authentication authentication = (Authentication) principal /* with pre-authorization this always holds */;
-        if (authentication.getAuthorities().stream().noneMatch(a -> a.getAuthority().equals("ROLE_RESEARCHER"))) {
-            log.error("Failed to grant permission {} because current user misses authority 'ROLE_RESEARCHER'",
-                    permissionCode);
-            return false;
-        }
-        final DatabaseAccess access = accessService.hasAccess(databaseId, tableId, principal.getName());
-        if (hasReadAccess(access) && List.of("TABLE_INFO", "CHECK_ACCESS").contains(permissionCode)) {
-            log.debug("grant permission {} because user {} has at least read access", permissionCode, principal.getName());
-            return true;
-        }
-        log.error("Failed to grant permission {} because user {} has insufficient access {} or is not creator", permissionCode, principal.getName(), access.getType());
-        return false;
-    }
-
-    private boolean hasReadAccess(DatabaseAccess access) {
-        return List.of(AccessType.READ, AccessType.WRITE_OWN, AccessType.WRITE_ALL).contains(access.getType());
-    }
-
-}
diff --git a/fda-table-service/rest-service/src/main/java/at/tuwien/endpoints/AccessEndpoint.java b/fda-table-service/rest-service/src/main/java/at/tuwien/endpoints/AccessEndpoint.java
index f48226219187863f667117080faf333f60b08614..cee900fc5a5cd458e03962cd8509d75a719ef24f 100644
--- a/fda-table-service/rest-service/src/main/java/at/tuwien/endpoints/AccessEndpoint.java
+++ b/fda-table-service/rest-service/src/main/java/at/tuwien/endpoints/AccessEndpoint.java
@@ -25,14 +25,13 @@ import java.security.Principal;
 @CrossOrigin(origins = "*")
 @RestController
 @RequestMapping("/api/container/{id}/database/{databaseId}/table/{tableId}/access")
-public class AccessEndpoint extends AbstractEndpoint {
+public class AccessEndpoint {
 
     private final AccessService accessService;
     private final DatabaseMapper databaseMapper;
 
     @Autowired
     public AccessEndpoint(DatabaseService databaseService, AccessService accessService, DatabaseMapper databaseMapper) {
-        super(accessService, databaseService);
         this.accessService = accessService;
         this.databaseMapper = databaseMapper;
     }
@@ -40,19 +39,15 @@ public class AccessEndpoint extends AbstractEndpoint {
     @GetMapping
     @Transactional
     @Timed(value = "access.check", description = "Time needed to check access to a table")
-    @PreAuthorize("hasRole('ROLE_RESEARCHER')")
+    @PreAuthorize("hasAuthority('check-access')")
     @Operation(summary = "Check access to some table", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<DatabaseAccessDto> checkAccess(@NotBlank @PathVariable("id") Long containerId,
                                                          @NotBlank @PathVariable("databaseId") Long databaseId,
                                                          @NotBlank @PathVariable("tableId") Long tableId,
                                                          @NotNull Principal principal)
-            throws NotAllowedException, AccessDeniedException {
+            throws AccessDeniedException {
         log.debug("endpoint check access to database, containerId={}, databaseId={}, principal={}",
                 containerId, databaseId, principal);
-        if (!hasTablePermission(containerId, databaseId, tableId, "CHECK_ACCESS", principal)) {
-            log.error("Missing check access permission");
-            throw new NotAllowedException("Missing check access permission");
-        }
         final DatabaseAccess access = accessService.hasAccess(databaseId, tableId, principal.getName());
         final DatabaseAccessDto dto = databaseMapper.databaseAccessToDatabaseAccessDto(access);
         log.trace("check access resulted in dto {}", dto);
diff --git a/fda-table-service/rest-service/src/main/java/at/tuwien/endpoints/TableColumnEndpoint.java b/fda-table-service/rest-service/src/main/java/at/tuwien/endpoints/TableColumnEndpoint.java
index 73b170ae4ae4151c11bbbf62ffb9abf08d62fba4..d5b44de563f3a387b183b5b515a847eb45d30547 100644
--- a/fda-table-service/rest-service/src/main/java/at/tuwien/endpoints/TableColumnEndpoint.java
+++ b/fda-table-service/rest-service/src/main/java/at/tuwien/endpoints/TableColumnEndpoint.java
@@ -5,8 +5,6 @@ import at.tuwien.api.database.table.columns.concepts.ColumnSemanticsUpdateDto;
 import at.tuwien.entities.database.table.columns.TableColumn;
 import at.tuwien.exception.*;
 import at.tuwien.mapper.TableMapper;
-import at.tuwien.service.AccessService;
-import at.tuwien.service.DatabaseService;
 import at.tuwien.service.TableService;
 import io.micrometer.core.annotation.Timed;
 import io.swagger.v3.oas.annotations.Operation;
@@ -25,15 +23,13 @@ import java.security.Principal;
 @CrossOrigin(origins = "*")
 @RestController
 @RequestMapping("/api/container/{id}/database/{databaseId}/table/{tableId}/column/{columnId}")
-public class TableColumnEndpoint extends AbstractEndpoint {
+public class TableColumnEndpoint {
 
     private final TableMapper tableMapper;
     private final TableService tableService;
 
     @Autowired
-    public TableColumnEndpoint(TableMapper tableMapper, TableService tableService, DatabaseService databaseService,
-                               AccessService accessService) {
-        super(accessService, databaseService);
+    public TableColumnEndpoint(TableMapper tableMapper, TableService tableService) {
         this.tableMapper = tableMapper;
         this.tableService = tableService;
     }
@@ -48,14 +44,10 @@ public class TableColumnEndpoint extends AbstractEndpoint {
                                             @NotNull @PathVariable("columnId") Long columnId,
                                             @NotNull @Valid @RequestBody ColumnSemanticsUpdateDto updateDto,
                                             @NotNull Principal principal) throws NotAllowedException,
-            AccessDeniedException, TableNotFoundException, TableMalformedException, DatabaseNotFoundException,
+            TableNotFoundException, TableMalformedException, DatabaseNotFoundException,
             ContainerNotFoundException, UnitNotFoundException, ConceptNotFoundException {
         log.debug("endpoint update table, containerId={}, databaseId={}, tableId={}, principal={}", containerId,
                 databaseId, tableId, principal);
-        if (!hasTablePermission(containerId, databaseId, tableId, "TABLE_UPDATE", principal)) {
-            log.error("Missing table update permission");
-            throw new NotAllowedException("Missing table update permission");
-        }
         final TableColumn column = tableService.update(containerId, databaseId, tableId, columnId, updateDto, principal);
         log.info("Updated table semantics of table with id {} and database with id {}", tableId, databaseId);
         final ColumnDto dto = tableMapper.tableColumnToColumnDto(column);
diff --git a/fda-table-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java b/fda-table-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java
index d1a6784f83ce35c698f84b78de6cd1d981106945..6c6c1c4577f3b2d2668a4f85c2014f483988ec75 100644
--- a/fda-table-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java
+++ b/fda-table-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java
@@ -15,6 +15,7 @@ import lombok.extern.log4j.Log4j2;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
 
@@ -28,16 +29,14 @@ import java.util.stream.Collectors;
 @CrossOrigin(origins = "*")
 @RestController
 @RequestMapping("/api/container/{id}/database/{databaseId}/table")
-public class TableEndpoint extends AbstractEndpoint {
+public class TableEndpoint {
 
     private final TableMapper tableMapper;
     private final TableService tableService;
     private final MessageQueueService amqpService;
 
     @Autowired
-    public TableEndpoint(TableMapper tableMapper, TableService tableService, MessageQueueService amqpService,
-                         DatabaseService databaseService, AccessService accessService) {
-        super(accessService, databaseService);
+    public TableEndpoint(TableMapper tableMapper, TableService tableService, MessageQueueService amqpService) {
         this.tableMapper = tableMapper;
         this.amqpService = amqpService;
         this.tableService = tableService;
@@ -46,17 +45,14 @@ public class TableEndpoint extends AbstractEndpoint {
     @GetMapping
     @Transactional(readOnly = true)
     @Timed(value = "table.list", description = "Time needed to list the tables")
+    @PreAuthorize("hasAuthority('find-tables')")
     @Operation(summary = "List all tables", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<List<TableBriefDto>> list(@NotNull @PathVariable("id") Long containerId,
                                                     @NotNull @PathVariable("databaseId") Long databaseId,
                                                     Principal principal)
-            throws DatabaseNotFoundException, NotAllowedException {
+            throws DatabaseNotFoundException {
         log.debug("endpoint list tables, containerId={}, databaseId={}, principal={}", containerId, databaseId,
                 principal);
-        if (!hasDatabasePermission(containerId, databaseId, "TABLES_VIEW", principal)) {
-            log.error("Missing table view permission");
-            throw new NotAllowedException("Missing table view permission");
-        }
         final List<TableBriefDto> dto = tableService.findAll(containerId, databaseId)
                 .stream()
                 .map(tableMapper::tableToTableBriefDto)
@@ -67,6 +63,7 @@ public class TableEndpoint extends AbstractEndpoint {
 
     @PostMapping
     @Transactional
+    @PreAuthorize("hasAuthority('create-table')")
     @Timed(value = "table.create", description = "Time needed to create a table")
     @Operation(summary = "Create a table", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<TableBriefDto> create(@NotNull @PathVariable("id") Long containerId,
@@ -78,10 +75,6 @@ public class TableEndpoint extends AbstractEndpoint {
             NotAllowedException {
         log.debug("endpoint create table, containerId={}, databaseId={}, createDto={}, principal={}", containerId,
                 databaseId, createDto, principal);
-        if (!hasDatabasePermission(containerId, databaseId, "TABLE_CREATE", principal)) {
-            log.error("Missing table create permission");
-            throw new NotAllowedException("Missing table create permission");
-        }
         final Table table = tableService.createTable(containerId, databaseId, createDto, principal);
         amqpService.create(table);
         final TableBriefDto dto = tableMapper.tableToTableBriefDto(table);
@@ -94,19 +87,15 @@ public class TableEndpoint extends AbstractEndpoint {
     @GetMapping("/{tableId}")
     @Transactional(readOnly = true)
     @Timed(value = "table.find", description = "Time needed to find a table")
+    @PreAuthorize("hasAuthority('find-table')")
     @Operation(summary = "Get information about table", security = @SecurityRequirement(name = "bearerAuth"))
     public ResponseEntity<TableDto> findById(@NotNull @PathVariable("id") Long containerId,
                                              @NotNull @PathVariable("databaseId") Long databaseId,
                                              @NotNull @PathVariable("tableId") Long tableId,
                                              Principal principal)
-            throws TableNotFoundException, DatabaseNotFoundException, ContainerNotFoundException, NotAllowedException,
-            AccessDeniedException {
+            throws TableNotFoundException, DatabaseNotFoundException, ContainerNotFoundException {
         log.debug("endpoint find table, containerId={}, databaseId={}, tableId={}, principal={}", containerId,
                 databaseId, tableId, principal);
-        if (!hasTablePermission(containerId, databaseId, tableId, "TABLE_INFO", principal)) {
-            log.error("Missing table view permission");
-            throw new NotAllowedException("Missing table view permission");
-        }
         final Table table = tableService.findById(containerId, databaseId, tableId);
         final TableDto dto = tableMapper.tableToTableDto(table);
         log.trace("find table resulted in table {}", dto);
@@ -115,6 +104,7 @@ public class TableEndpoint extends AbstractEndpoint {
 
     @DeleteMapping("/{tableId}")
     @Transactional
+    @PreAuthorize("hasAuthority('delete-table')")
     @Timed(value = "table.delete", description = "Time needed to delete a table")
     @Operation(summary = "Delete a table", security = @SecurityRequirement(name = "bearerAuth"))
     @ResponseStatus(HttpStatus.OK)
@@ -123,14 +113,9 @@ public class TableEndpoint extends AbstractEndpoint {
                        @NotNull @PathVariable("tableId") Long tableId,
                        @NotNull Principal principal)
             throws TableNotFoundException, DatabaseNotFoundException, ImageNotSupportedException,
-            DataProcessingException, ContainerNotFoundException, TableMalformedException, QueryMalformedException,
-            NotAllowedException, AccessDeniedException {
+            DataProcessingException, ContainerNotFoundException, TableMalformedException, QueryMalformedException {
         log.debug("endpoint delete table, containerId={}, databaseId={}, tableId={}, principal={}", containerId,
                 databaseId, tableId, principal);
-        if (!hasTablePermission(containerId, databaseId, tableId, "TABLE_DELETE", principal)) {
-            log.error("Missing table delete permission");
-            throw new NotAllowedException("Missing table delete permission");
-        }
         tableService.deleteTable(containerId, databaseId, tableId);
     }
 
diff --git a/fda-table-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java b/fda-table-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java
index 522047601a5efe16e82a2b1c148fa192588308b9..d7b8d42bf48e98351cbb94bb7bb0a47648f0db30 100644
--- a/fda-table-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java
+++ b/fda-table-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java
@@ -38,19 +38,13 @@ public abstract class BaseUnitTest {
             .username(USER_1_USERNAME)
             .email(USER_1_EMAIL)
             .emailVerified(true)
-            .themeDark(false)
-            .password(USER_1_PASSWORD)
             .databasePassword(USER_1_DATABASE_PASSWORD)
-            .created(USER_1_CREATED)
-            .lastModified(USER_1_CREATED)
             .build();
     public final static UserDto USER_1_DTO = UserDto.builder()
             .id(USER_1_ID)
             .username(USER_1_USERNAME)
             .email(USER_1_EMAIL)
             .emailVerified(true)
-            .themeDark(false)
-            .password("password")
             .build();
 
     public final static String USER_2_ID = "0153f998-bd4c-4154-993e-75c355499044";
@@ -64,19 +58,13 @@ public abstract class BaseUnitTest {
             .username(USER_2_USERNAME)
             .email(USER_2_EMAIL)
             .emailVerified(true)
-            .themeDark(false)
-            .password(USER_2_PASSWORD)
             .databasePassword(USER_2_DATABASE_PASSWORD)
-            .created(USER_2_CREATED)
-            .lastModified(USER_2_CREATED)
             .build();
     public final static UserDto USER_2_DTO = UserDto.builder()
             .id(USER_2_ID)
             .username(USER_2_USERNAME)
             .email(USER_2_EMAIL)
             .emailVerified(true)
-            .themeDark(false)
-            .password("password")
             .build();
 
     public final static String DATABASE_NET = "fda-userdb";
diff --git a/fda-ui/api/container/index.js b/fda-ui/api/container/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..3cd8e7a2d83c6cbb086937b55b5f3343e7002acc
--- /dev/null
+++ b/fda-ui/api/container/index.js
@@ -0,0 +1,16 @@
+const axios = require('axios/dist/browser/axios.cjs')
+
+export function listContainers (limit) {
+  return axios.get(`/api/container?limit=${limit}`)
+}
+
+export function startContainer (token, containerId) {
+  const payload = {
+    action: 'start'
+  }
+  return axios.put(`/api/container/${containerId}`, payload, {
+    headers: {
+      Authorization: `Bearer ${token}`
+    }
+  })
+}
diff --git a/fda-ui/api/database/index.js b/fda-ui/api/database/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..7b61f97544d21309d765565765f4df380890c206
--- /dev/null
+++ b/fda-ui/api/database/index.js
@@ -0,0 +1,13 @@
+const axios = require('axios/dist/browser/axios.cjs')
+
+export function createDatabase (token, containerId) {
+  const payload = {
+    name: null,
+    is_public: true
+  }
+  return axios.post(`/api/container/${containerId}/database`, payload, {
+    headers: {
+      Authorization: `Bearer ${token}`
+    }
+  })
+}
diff --git a/fda-ui/components/DatabaseList.vue b/fda-ui/components/DatabaseList.vue
index 2f85f6a72ed63ff7c776abf354e839b5fc4198c9..f3ffd4196bc6c17580a86a5d5a9a2cfcd4af503c 100644
--- a/fda-ui/components/DatabaseList.vue
+++ b/fda-ui/components/DatabaseList.vue
@@ -5,7 +5,6 @@
       v-for="(container, idx) in containers"
       :key="idx"
       :to="link(container)"
-      :disabled="!container.database"
       flat
       tile>
       <v-divider class="mx-4" />
@@ -32,7 +31,7 @@
         <v-btn
           small
           secondary
-          :loading="container.database.loading"
+          :loading="container?.loading"
           @click.stop="initDatabase(container)">
           Start
         </v-btn>
@@ -53,6 +52,8 @@
 
 <script>
 import { formatCreators, formatUser, formatYearUTC, isResearcher } from '@/utils'
+import { listContainers, startContainer } from '@/api/container'
+import { createDatabase } from '@/api/database'
 
 export default {
   data () {
@@ -64,10 +65,6 @@ export default {
       containers: [],
       searchQuery: null,
       limit: 100,
-      createDatabaseDto: {
-        name: null,
-        is_public: true
-      },
       items: [
         { text: 'Databases', to: '/container', activeClass: '' }
       ],
@@ -109,13 +106,13 @@ export default {
       return creators || this.formatCreator(container.creator)
     },
     canInit (container) {
-      if (!this.token) {
+      if (!this.user) {
         return false
       }
-      if (container.creator.username !== this.user.username) {
+      if (container.creator.sub !== this.user.id) {
         return false
       }
-      return !container.database.id && !this.loadingDatabases
+      return !container.database
     },
     hasDatabase (container) {
       return container.database
@@ -128,13 +125,13 @@ export default {
         .then(() => this.createDatabase(container))
     },
     identifierCreated (container) {
-      if (!container.database.identifier) {
+      if (!container || !container.database || !container.database.identifier) {
         return null
       }
       return formatYearUTC(container.database.identifier.created)
     },
     identifierDescription (container) {
-      if (!container.database.identifier) {
+      if (!container || !container.database || !container.database.identifier) {
         return null
       }
       return container.database.identifier.description
@@ -143,7 +140,7 @@ export default {
       this.createDbDialog = false
       try {
         this.loadingContainers = true
-        const res = await this.$axios.get(`/api/container?limit=${this.limit}`)
+        const res = await listContainers(this.limit)
         this.containers = res.data
         console.debug('containers', this.containers)
         this.error = false
@@ -157,18 +154,35 @@ export default {
     },
     async startContainer (container) {
       try {
-        container.database.loading = true
-        const res = await this.$axios.put(`/api/container/${container.id}`, { action: 'start' }, this.config)
+        container.loading = true
+        const res = await startContainer(this.token, container.id)
         console.debug('started container', res.data)
         this.error = false
       } catch (error) {
+        console.error('start container', error)
         const { status } = error.response
         if (status !== 409) {
           this.error = true
           this.$toast.error('Failed to start container')
         }
       }
-      container.database.loading = false
+      container.loading = false
+    },
+    async createDatabase (container) {
+      try {
+        container.loading = true
+        const res = await createDatabase(this.token, container.id)
+        container.database = res.data
+        console.debug('created database', container.database)
+        this.error = false
+      } catch (error) {
+        console.error('create database', error)
+        const { message } = error.response
+        this.error = true
+        console.error('Failed to create database', error)
+        this.$toast.error(`${message}`)
+      }
+      container.loading = false
     },
     link (container) {
       if (!container.database || !container.database.id) {