From 0db212f5ce415fdab0ee22fcfacc4b7d3a59cb4b Mon Sep 17 00:00:00 2001
From: Martin Weise <martin.weise@tuwien.ac.at>
Date: Wed, 29 Mar 2023 14:18:53 +0200
Subject: [PATCH] Large update

- Added JWT check on container, database, table, query, identifier
  service
- Removed all gateway check to the authentication service, instead check
  the token validity directly in the service
- Removed some env vars
---
 .env.unix.example                             |  28 ++--
 docker-compose.yml                            |   2 +
 fda-authentication-service/Dockerfile         |   4 +-
 fda-authentication-service/dbrepo-realm.json  | 124 ++++++++++++++----
 fda-broker-service/rabbitmq.conf              |   2 +-
 fda-container-service/Dockerfile              |   4 +-
 fda-container-service/pom.xml                 |   7 +
 .../src/main/resources/application-docker.yml |   3 +
 .../src/main/resources/application-local.yml  |   5 +-
 .../src/main/resources/application.yml        |   3 +
 .../at/tuwien/auth/AuthTokenFilterTest.java   |  11 --
 .../AuthenticationServiceGatewayTest.java     |  76 -----------
 .../java/at/tuwien/auth/AuthTokenFilter.java  |  56 +++++++-
 .../at/tuwien/config/WebSecurityConfig.java   |  11 +-
 .../gateway/AuthenticationServiceGateway.java |  16 ---
 .../AuthenticationServiceGatewayImpl.java     |  64 ---------
 fda-database-service/Dockerfile               |   4 +-
 fda-database-service/pom.xml                  |   7 +
 .../src/main/resources/application-docker.yml |   3 +
 .../src/main/resources/application-local.yml  |   5 +-
 .../src/main/resources/application.yml        |   3 +
 .../at/tuwien/auth/AuthTokenFilterTest.java   |   9 --
 .../AuthenticationServiceGatewayTest.java     |  87 ------------
 .../java/at/tuwien/auth/AuthTokenFilter.java  |  60 +++++++--
 .../at/tuwien/config/WebSecurityConfig.java   |  11 +-
 .../gateway/AuthenticationServiceGateway.java |  16 ---
 .../AuthenticationServiceGatewayImpl.java     |  66 ----------
 fda-identifier-service/Dockerfile             |   4 +
 fda-identifier-service/pom.xml                |   7 +
 .../src/main/resources/application-docker.yml |   5 +
 .../src/main/resources/application-local.yml  |   5 +
 .../src/main/resources/application.yml        |   5 +
 .../at/tuwien/auth/AuthTokenFilterTest.java   |  11 --
 .../AuthenticationServiceGatewayTest.java     |  81 ------------
 .../java/at/tuwien/auth/AuthTokenFilter.java  |  60 +++++++--
 .../at/tuwien/config/WebSecurityConfig.java   |  11 +-
 .../gateway/AuthenticationServiceGateway.java |  17 ---
 .../AuthenticationServiceGatewayImpl.java     |  49 -------
 fda-query-service/Dockerfile                  |   4 +
 fda-query-service/pom.xml                     |   7 +
 .../src/main/resources/application-docker.yml |   5 +
 .../src/main/resources/application-local.yml  |   5 +
 .../src/main/resources/application.yml        |   5 +
 .../at/tuwien/auth/AuthTokenFilterTest.java   |   9 --
 .../java/at/tuwien/auth/AuthTokenFilter.java  |  60 +++++++--
 .../at/tuwien/config/WebSecurityConfig.java   |  11 +-
 .../gateway/AuthenticationServiceGateway.java |  16 ---
 .../AuthenticationServiceGatewayImpl.java     |  49 -------
 fda-table-service/Dockerfile                  |   4 +
 fda-table-service/pom.xml                     |   7 +
 .../src/main/resources/application-docker.yml |   5 +
 .../src/main/resources/application-local.yml  |   5 +
 .../src/main/resources/application.yml        |   5 +
 .../java/at/tuwien/auth/AuthTokenFilter.java  |  58 +++++++-
 .../at/tuwien/config/WebSecurityConfig.java   |  11 +-
 .../gateway/AuthenticationServiceGateway.java |  14 --
 .../AuthenticationServiceGatewayImpl.java     |  38 ------
 57 files changed, 491 insertions(+), 769 deletions(-)
 delete mode 100644 fda-container-service/rest-service/src/test/java/at/tuwien/gateway/AuthenticationServiceGatewayTest.java
 delete mode 100644 fda-container-service/services/src/main/java/at/tuwien/gateway/AuthenticationServiceGateway.java
 delete mode 100644 fda-container-service/services/src/main/java/at/tuwien/gateway/impl/AuthenticationServiceGatewayImpl.java
 delete mode 100644 fda-database-service/rest-service/src/test/java/at/tuwien/gateway/AuthenticationServiceGatewayTest.java
 delete mode 100644 fda-database-service/services/src/main/java/at/tuwien/gateway/AuthenticationServiceGateway.java
 delete mode 100644 fda-database-service/services/src/main/java/at/tuwien/gateway/impl/AuthenticationServiceGatewayImpl.java
 delete mode 100644 fda-identifier-service/rest-service/src/test/java/at/tuwien/gateway/AuthenticationServiceGatewayTest.java
 delete mode 100644 fda-identifier-service/services/src/main/java/at/tuwien/gateway/AuthenticationServiceGateway.java
 delete mode 100644 fda-identifier-service/services/src/main/java/at/tuwien/gateway/impl/AuthenticationServiceGatewayImpl.java
 delete mode 100644 fda-query-service/services/src/main/java/at/tuwien/gateway/AuthenticationServiceGateway.java
 delete mode 100644 fda-query-service/services/src/main/java/at/tuwien/gateway/impl/AuthenticationServiceGatewayImpl.java
 delete mode 100644 fda-table-service/services/src/main/java/at/tuwien/gateway/AuthenticationServiceGateway.java
 delete mode 100644 fda-table-service/services/src/main/java/at/tuwien/gateway/impl/AuthenticationServiceGatewayImpl.java

diff --git a/.env.unix.example b/.env.unix.example
index 2919f4e34a..3f39fd7b37 100644
--- a/.env.unix.example
+++ b/.env.unix.example
@@ -1,24 +1,16 @@
+DBREPO_CLIENT_SECRET=MUwRc7yfXSJwX8AdRMWaQC3Nep1VjwgG
+RABBITMQ_CLIENT_SECRET=JEC2FexxrX4N65fLeDGukAl6R3Lc9y0u
+JWT_ISSUER=http://localhost:8080/realms/dbrepo
+JWT_PUBKEY=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqqnHQ2BWWW9vDNLRCcxD++xZg/16oqMo/c1l+lcFEjjAIJjJp/HqrPYU/U9GvquGE6PbVFtTzW1KcKawOW+FJNOA3CGo8Q1TFEfz43B8rZpKsFbJKvQGVv1Z4HaKPvLUm7iMm8Hv91cLduuoWx6Q3DPe2vg13GKKEZe7UFghF+0T9u8EKzA/XqQ0OiICmsmYPbwvf9N3bCKsB/Y10EYmZRb8IhCoV9mmO5TxgWgiuNeCTtNCv2ePYqL/U0WvyGFW0reasIK8eg3KrAUj8DpyOgPOVBn3lBGf+3KFSYi+0bwZbJZWqbC/Xlk20Go1YfeJPRIt7ImxD27R/lNjgDO/MwIDAQAB
+SHARED_FILESYSTEM=/tmp
+LOG_LEVEL=trace
+ELASTIC_PASSWORD=elastic
 METADATA_DB=fda
 METADATA_USERNAME=root
 METADATA_PASSWORD=dbrepo
 BROKER_USERNAME=fda
 BROKER_PASSWORD=fda
+KEYCLOAK_ADMIN=fda
+KEYCLOAK_ADMIN_PASSWORD=fda
 BROKER_CONSUMERS=2
-WEBSITE=http://example.com
-TOKEN_MAX=5
-MAIL_FROM="Database Repository <noreply@example.com>"
-MAIL_REPLY_TO="Admin <somebody@example.com>"
-MAIL_VERIFY=false
-SMTP_HOST=
-SMTP_PORT=
-SMTP_USERNAME=
-SMTP_PASSWORD=
-JWT_ISSUER=dbrepo
-JWT_SECRET=secret
-JWT_EXPIRATION=86400000
-SHARED_FILESYSTEM=/tmp
-LOG_LEVEL=trace # error, warning, info, debug, trace
-DEFAULT_ROLES=ROLE_RESEARCHER
-SUPERUSERS=user1,user2
-ELASTIC_PASSWORD=elastic
-CLIENT_SECRET=client-secret
\ No newline at end of file
+WEBSITE=http://example.com
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
index 3d1870ea7a..2ec72e2df9 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -282,6 +282,8 @@ services:
         condition: service_healthy
       fda-metadata-db:
         condition: service_healthy
+      fda-authentication-service:
+        condition: service_healthy
     logging:
       driver: json-file
 
diff --git a/fda-authentication-service/Dockerfile b/fda-authentication-service/Dockerfile
index 3cbf6b9295..38e737360b 100644
--- a/fda-authentication-service/Dockerfile
+++ b/fda-authentication-service/Dockerfile
@@ -49,8 +49,8 @@ ENV KC_DB_PASSWORD=${METADATA_PASSWORD}
 ENV KC_HOSTNAME=localhost
 
 ENV KEYCLOAK_IMPORT=/opt/keycloak/data/import/dbrepo-realm.json
-ENV KEYCLOAK_ADMIN=keycloak
-ENV KEYCLOAK_ADMIN_PASSWORD=keycloak
+ENV KEYCLOAK_ADMIN=fda
+ENV KEYCLOAK_ADMIN_PASSWORD=fda
 
 HEALTHCHECK --interval=10s --timeout=5s --retries=12 CMD ["bash", "/app/healthcheck.sh"]
 
diff --git a/fda-authentication-service/dbrepo-realm.json b/fda-authentication-service/dbrepo-realm.json
index bfe28ab1e0..e4df97e0b8 100644
--- a/fda-authentication-service/dbrepo-realm.json
+++ b/fda-authentication-service/dbrepo-realm.json
@@ -696,6 +696,7 @@
         "attributes" : { }
       } ],
       "security-admin-console" : [ ],
+      "dbrepo-client" : [ ],
       "admin-cli" : [ ],
       "rabbitmq-client" : [ ],
       "account-console" : [ ],
@@ -842,7 +843,7 @@
   "otpPolicyLookAheadWindow" : 1,
   "otpPolicyPeriod" : 30,
   "otpPolicyCodeReusable" : false,
-  "otpSupportedApplications" : [ "totpAppGoogleName", "totpAppFreeOTPName", "totpAppMicrosoftAuthenticatorName" ],
+  "otpSupportedApplications" : [ "totpAppFreeOTPName", "totpAppMicrosoftAuthenticatorName", "totpAppGoogleName" ],
   "webAuthnPolicyRpEntityName" : "keycloak",
   "webAuthnPolicySignatureAlgorithms" : [ "ES256" ],
   "webAuthnPolicyRpId" : "",
@@ -994,6 +995,73 @@
     "nodeReRegistrationTimeout" : 0,
     "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ],
     "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ]
+  }, {
+    "id" : "6b7ef364-4132-4831-b4e2-b6e9e9dc63ee",
+    "clientId" : "dbrepo-client",
+    "name" : "${dbrepo-client}",
+    "description" : "",
+    "rootUrl" : "",
+    "adminUrl" : "",
+    "baseUrl" : "",
+    "surrogateAuthRequired" : false,
+    "enabled" : true,
+    "alwaysDisplayInConsole" : true,
+    "clientAuthenticatorType" : "client-secret",
+    "secret" : "MUwRc7yfXSJwX8AdRMWaQC3Nep1VjwgG",
+    "redirectUris" : [ "*" ],
+    "webOrigins" : [ "*" ],
+    "notBefore" : 0,
+    "bearerOnly" : false,
+    "consentRequired" : false,
+    "standardFlowEnabled" : true,
+    "implicitFlowEnabled" : false,
+    "directAccessGrantsEnabled" : true,
+    "serviceAccountsEnabled" : false,
+    "publicClient" : false,
+    "frontchannelLogout" : true,
+    "protocol" : "openid-connect",
+    "attributes" : {
+      "oidc.ciba.grant.enabled" : "false",
+      "client.secret.creation.time" : "1680085365",
+      "backchannel.logout.session.required" : "true",
+      "post.logout.redirect.uris" : "*",
+      "oauth2.device.authorization.grant.enabled" : "false",
+      "backchannel.logout.revoke.offline.tokens" : "false"
+    },
+    "authenticationFlowBindingOverrides" : { },
+    "fullScopeAllowed" : true,
+    "nodeReRegistrationTimeout" : -1,
+    "protocolMappers" : [ {
+      "id" : "6a8cae99-294f-4fc2-9561-5a52f3f6a1ba",
+      "name" : "Audience",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-hardcoded-claim-mapper",
+      "consentRequired" : false,
+      "config" : {
+        "claim.value" : "spring",
+        "userinfo.token.claim" : "false",
+        "id.token.claim" : "false",
+        "access.token.claim" : "true",
+        "claim.name" : "aud",
+        "access.tokenResponse.claim" : "false"
+      }
+    }, {
+      "id" : "ef081a47-f023-4056-958c-4194d3878d8c",
+      "name" : "username",
+      "protocol" : "openid-connect",
+      "protocolMapper" : "oidc-usermodel-property-mapper",
+      "consentRequired" : false,
+      "config" : {
+        "userinfo.token.claim" : "false",
+        "user.attribute" : "username",
+        "id.token.claim" : "false",
+        "access.token.claim" : "true",
+        "claim.name" : "client_id",
+        "jsonType.label" : "String"
+      }
+    } ],
+    "defaultClientScopes" : [ ],
+    "optionalClientScopes" : [ "rabbitmq.read:*/*", "web-origins", "acr", "rabbitmq.write:*/*", "address", "phone", "offline_access", "profile", "roles", "microprofile-jwt", "email", "rabbitmq.configure:*/*" ]
   }, {
     "id" : "25741f6b-4867-4138-8238-6345c6ba8702",
     "clientId" : "rabbitmq-client",
@@ -1579,7 +1647,7 @@
   }, {
     "id" : "c96f0b73-ea79-4b46-93ef-d1092297f855",
     "name" : "rabbitmq.read:*/*",
-    "description" : "RabbitMQ Read All",
+    "description" : "",
     "protocol" : "openid-connect",
     "attributes" : {
       "include.in.token.scope" : "true",
@@ -1632,8 +1700,8 @@
       }
     } ]
   } ],
-  "defaultDefaultClientScopes" : [ "rabbitmq.read:*/*" ],
-  "defaultOptionalClientScopes" : [ "rabbitmq.write:*/*", "offline_access", "rabbitmq.configure:*/*", "roles", "role_list", "address", "phone", "acr", "microprofile-jwt", "email", "profile", "web-origins" ],
+  "defaultDefaultClientScopes" : [ ],
+  "defaultOptionalClientScopes" : [ "rabbitmq.write:*/*", "offline_access", "rabbitmq.configure:*/*", "roles", "role_list", "address", "phone", "acr", "microprofile-jwt", "email", "profile", "rabbitmq.read:*/*", "web-origins" ],
   "browserSecurityHeaders" : {
     "contentSecurityPolicyReportOnly" : "",
     "xContentTypeOptions" : "nosniff",
@@ -1710,7 +1778,7 @@
       "subType" : "authenticated",
       "subComponents" : { },
       "config" : {
-        "allowed-protocol-mapper-types" : [ "saml-role-list-mapper", "saml-user-attribute-mapper", "saml-user-property-mapper", "oidc-full-name-mapper", "oidc-usermodel-attribute-mapper", "oidc-usermodel-property-mapper", "oidc-address-mapper", "oidc-sha256-pairwise-sub-mapper" ]
+        "allowed-protocol-mapper-types" : [ "saml-role-list-mapper", "oidc-usermodel-property-mapper", "saml-user-attribute-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-attribute-mapper", "oidc-full-name-mapper", "oidc-address-mapper", "saml-user-property-mapper" ]
       }
     }, {
       "id" : "3ab11d74-5e76-408a-b85a-26bf8950f979",
@@ -1719,7 +1787,7 @@
       "subType" : "anonymous",
       "subComponents" : { },
       "config" : {
-        "allowed-protocol-mapper-types" : [ "oidc-full-name-mapper", "oidc-usermodel-property-mapper", "oidc-usermodel-attribute-mapper", "oidc-address-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-user-attribute-mapper", "saml-role-list-mapper", "saml-user-property-mapper" ]
+        "allowed-protocol-mapper-types" : [ "saml-user-attribute-mapper", "oidc-full-name-mapper", "saml-user-property-mapper", "oidc-usermodel-attribute-mapper", "oidc-usermodel-property-mapper", "oidc-address-mapper", "saml-role-list-mapper", "oidc-sha256-pairwise-sub-mapper" ]
       }
     } ],
     "org.keycloak.keys.KeyProvider" : [ {
@@ -1771,7 +1839,7 @@
   "internationalizationEnabled" : false,
   "supportedLocales" : [ ],
   "authenticationFlows" : [ {
-    "id" : "792d8d8f-d309-44c5-beb7-fea91787e081",
+    "id" : "f5670e73-ebe7-4df8-a412-720db86688a0",
     "alias" : "Account verification options",
     "description" : "Method with which to verity the existing account",
     "providerId" : "basic-flow",
@@ -1793,7 +1861,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "70569ef2-3449-4396-9598-bb5923350072",
+    "id" : "594021be-b169-45e1-af94-f38308239acb",
     "alias" : "Authentication Options",
     "description" : "Authentication options.",
     "providerId" : "basic-flow",
@@ -1822,7 +1890,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "131b9d56-8611-4d41-9bf6-5b23f9e6c27f",
+    "id" : "008b215a-c415-481e-a56e-d916ad7b8be8",
     "alias" : "Browser - Conditional OTP",
     "description" : "Flow to determine if the OTP is required for the authentication",
     "providerId" : "basic-flow",
@@ -1844,7 +1912,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "63e6014f-a0b6-4bad-b3a0-4eb6241fe8e2",
+    "id" : "225f4a5e-3e16-4f96-a74d-106ee2a648a8",
     "alias" : "Direct Grant - Conditional OTP",
     "description" : "Flow to determine if the OTP is required for the authentication",
     "providerId" : "basic-flow",
@@ -1866,7 +1934,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "03220669-d897-4024-ae59-44292d1897be",
+    "id" : "76e0a042-42ee-4968-bc9c-ea99aa5da1e9",
     "alias" : "First broker login - Conditional OTP",
     "description" : "Flow to determine if the OTP is required for the authentication",
     "providerId" : "basic-flow",
@@ -1888,7 +1956,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "ed6a42e8-98d1-4d79-a6ac-2ca4ebfb9853",
+    "id" : "325c7942-4a29-4be9-8b11-18eac7b94576",
     "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",
@@ -1910,7 +1978,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "b3703eae-dec0-4499-8c55-6f2077483941",
+    "id" : "5ec59354-2513-4969-94c4-155a5d9d40bb",
     "alias" : "Reset - Conditional OTP",
     "description" : "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.",
     "providerId" : "basic-flow",
@@ -1932,7 +2000,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "8fa9ba51-d2d9-4f4c-96d5-f318753eab5e",
+    "id" : "f9059e44-8678-4239-b022-964d0778b2eb",
     "alias" : "User creation or linking",
     "description" : "Flow for the existing/non-existing user alternatives",
     "providerId" : "basic-flow",
@@ -1955,7 +2023,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "66adeda3-1206-4483-a8f1-3e1541573f4e",
+    "id" : "aeb11709-cfc9-4983-a49a-a6a394738390",
     "alias" : "Verify Existing Account by Re-authentication",
     "description" : "Reauthentication of existing account",
     "providerId" : "basic-flow",
@@ -1977,7 +2045,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "a3e2950d-32d3-4fdd-b110-3eb0de425bd4",
+    "id" : "9060a17e-e895-4080-8d15-736ffc935d69",
     "alias" : "browser",
     "description" : "browser based authentication",
     "providerId" : "basic-flow",
@@ -2013,7 +2081,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "33617d26-0322-4a35-8e48-1f3ffca7a8d4",
+    "id" : "cf5dd646-4767-4f1f-a868-932f25158e8e",
     "alias" : "clients",
     "description" : "Base authentication for clients",
     "providerId" : "client-flow",
@@ -2049,7 +2117,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "04b15af3-99ae-404a-a844-06eb0444d2c4",
+    "id" : "bd5f02ab-595b-42ef-bee5-536240a1f5e4",
     "alias" : "direct grant",
     "description" : "OpenID Connect Resource Owner Grant",
     "providerId" : "basic-flow",
@@ -2078,7 +2146,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "f9499050-d69b-4fd0-8b22-81926234bea2",
+    "id" : "bb4fc7b4-ce03-48a5-953a-efa64f9b1f08",
     "alias" : "docker auth",
     "description" : "Used by Docker clients to authenticate against the IDP",
     "providerId" : "basic-flow",
@@ -2093,7 +2161,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "37b7eaef-6460-47c1-80da-b97213e4fea6",
+    "id" : "5263fbcf-1e5e-4073-82b9-61c284be6a1d",
     "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",
@@ -2116,7 +2184,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "faf95ea8-ba5a-4c1e-b4ef-b748052b8131",
+    "id" : "e3b9c110-2293-450c-95a8-1ab67a13a40b",
     "alias" : "forms",
     "description" : "Username, password, otp and other auth forms.",
     "providerId" : "basic-flow",
@@ -2138,7 +2206,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "565c209d-bba7-49c8-b1c5-78c3c4284d40",
+    "id" : "40c9d62a-783d-4434-9642-1c34c9101d87",
     "alias" : "http challenge",
     "description" : "An authentication flow based on challenge-response HTTP Authentication Schemes",
     "providerId" : "basic-flow",
@@ -2160,7 +2228,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "1bbee072-9026-4d94-9c63-2694fdb8b2b0",
+    "id" : "aaecb234-2351-467f-81ad-d56c1f8811a4",
     "alias" : "registration",
     "description" : "registration flow",
     "providerId" : "basic-flow",
@@ -2176,7 +2244,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "6f7fa9eb-a4de-4fdf-a052-93ec33706e02",
+    "id" : "6c79d6c6-08e9-44b1-8d98-e536c18e1b2a",
     "alias" : "registration form",
     "description" : "registration form",
     "providerId" : "form-flow",
@@ -2212,7 +2280,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "41f57c84-4db4-4665-ac74-fc0683626d08",
+    "id" : "1e6cf937-d987-4898-a1dc-765bbae4da72",
     "alias" : "reset credentials",
     "description" : "Reset credentials for a user if they forgot their password or something",
     "providerId" : "basic-flow",
@@ -2248,7 +2316,7 @@
       "userSetupAllowed" : false
     } ]
   }, {
-    "id" : "951bb395-6a09-4fb9-b688-7a531d68a34b",
+    "id" : "ea9c6ea6-5cef-4e5c-9ca1-217d2833257b",
     "alias" : "saml ecp",
     "description" : "SAML ECP Profile Authentication Flow",
     "providerId" : "basic-flow",
@@ -2264,13 +2332,13 @@
     } ]
   } ],
   "authenticatorConfig" : [ {
-    "id" : "349cab01-873e-4aa0-bdc3-50e20e6990cd",
+    "id" : "8aabe328-f9c8-4842-a084-601deacd79e2",
     "alias" : "create unique user config",
     "config" : {
       "require.password.update.after.registration" : "false"
     }
   }, {
-    "id" : "b2c7f5f4-028b-40df-8dc8-0686736b71a4",
+    "id" : "398beafd-0718-4a86-a6d4-5a5ab54c8bc6",
     "alias" : "review profile config",
     "config" : {
       "update.profile.on.first.login" : "missing"
diff --git a/fda-broker-service/rabbitmq.conf b/fda-broker-service/rabbitmq.conf
index f3779fab7f..170b8b8fd2 100644
--- a/fda-broker-service/rabbitmq.conf
+++ b/fda-broker-service/rabbitmq.conf
@@ -12,7 +12,7 @@ listeners.tcp.1 = 0.0.0.0:5672
 
 # logging
 log.console = true
-log.console.level = debug
+log.console.level = warning
 
 # Obviously your authentication server cannot vouch for itself, so you'll need another backend with at least one user in
 # it. You should probably use the internal database
diff --git a/fda-container-service/Dockerfile b/fda-container-service/Dockerfile
index 0ea94f5755..cac8dff13d 100644
--- a/fda-container-service/Dockerfile
+++ b/fda-container-service/Dockerfile
@@ -29,8 +29,10 @@ ENV BROKER_PASSWORD=fda
 ENV SHARED_FILESYSTEM=/tmp
 ENV USER_NETWORK=userdb
 ENV LOG_LEVEL=debug
-ENV CLIENT_SECRET=client-secret
+ENV CLIENT_SECRET="${DBREPO_CLIENT_SECRET:-client-secret}"
 ENV CLIENT_ID=dbrepo-client
+ENV JWT_ISSUER=http://localhost:8080/realms/dbrepo
+ENV JWT_PUBKEY=public-key
 
 COPY ./service_ready /usr/bin
 RUN chmod +x /usr/bin/service_ready
diff --git a/fda-container-service/pom.xml b/fda-container-service/pom.xml
index 8e9b626ec9..6e0ef2e8cf 100644
--- a/fda-container-service/pom.xml
+++ b/fda-container-service/pom.xml
@@ -29,6 +29,7 @@
         <swagger.version>2.2.2</swagger.version>
         <jacoco.version>0.8.7</jacoco.version>
         <sqlserver.version>9.2.1.jre11</sqlserver.version>
+        <jwt.version>4.3.0</jwt.version>
     </properties>
 
     <dependencies>
@@ -62,6 +63,12 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-actuator</artifactId>
         </dependency>
+        <!-- Authentication -->
+        <dependency>
+            <groupId>com.auth0</groupId>
+            <artifactId>java-jwt</artifactId>
+            <version>${jwt.version}</version>
+        </dependency>
         <!-- Monitoring -->
         <dependency>
             <groupId>io.micrometer</groupId>
diff --git a/fda-container-service/rest-service/src/main/resources/application-docker.yml b/fda-container-service/rest-service/src/main/resources/application-docker.yml
index 83273c8f26..f2afff130e 100644
--- a/fda-container-service/rest-service/src/main/resources/application-docker.yml
+++ b/fda-container-service/rest-service/src/main/resources/application-docker.yml
@@ -38,6 +38,9 @@ fda:
   network: userdb
   mount.path: /tmp
   ready.path: /ready
+  jwt:
+    issuer: "${JWT_ISSUER}"
+    public_key: "${JWT_PUBKEY}"
   client_secret: "${CLIENT_SECRET}"
   client_id: "${CLIENT_ID}"
   gateway.endpoint: http://gateway-service:9095
\ No newline at end of file
diff --git a/fda-container-service/rest-service/src/main/resources/application-local.yml b/fda-container-service/rest-service/src/main/resources/application-local.yml
index 2a14576f35..a8ff84d143 100644
--- a/fda-container-service/rest-service/src/main/resources/application-local.yml
+++ b/fda-container-service/rest-service/src/main/resources/application-local.yml
@@ -38,6 +38,9 @@ fda:
   network: userdb
   mount.path: /tmp
   ready.path: ./ready
-  client_secret: Gp9IALXWsfftK8ek1J6jNT9hNfWV5U5c
+  jwt:
+    issuer: http://localhost:8080/realms/dbrepo
+    public_key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqqnHQ2BWWW9vDNLRCcxD++xZg/16oqMo/c1l+lcFEjjAIJjJp/HqrPYU/U9GvquGE6PbVFtTzW1KcKawOW+FJNOA3CGo8Q1TFEfz43B8rZpKsFbJKvQGVv1Z4HaKPvLUm7iMm8Hv91cLduuoWx6Q3DPe2vg13GKKEZe7UFghF+0T9u8EKzA/XqQ0OiICmsmYPbwvf9N3bCKsB/Y10EYmZRb8IhCoV9mmO5TxgWgiuNeCTtNCv2ePYqL/U0WvyGFW0reasIK8eg3KrAUj8DpyOgPOVBn3lBGf+3KFSYi+0bwZbJZWqbC/Xlk20Go1YfeJPRIt7ImxD27R/lNjgDO/MwIDAQAB
+  client_secret: client-secret
   client_id: dbrepo-client
   gateway.endpoint: http://localhost:9095
\ No newline at end of file
diff --git a/fda-container-service/rest-service/src/main/resources/application.yml b/fda-container-service/rest-service/src/main/resources/application.yml
index c51eb9dd40..0d80243df8 100644
--- a/fda-container-service/rest-service/src/main/resources/application.yml
+++ b/fda-container-service/rest-service/src/main/resources/application.yml
@@ -38,6 +38,9 @@ fda:
   network: "${USER_NETWORK}"
   mount.path: "${SHARED_FILESYSTEM}"
   ready.path: /ready
+  jwt:
+    issuer: "${JWT_ISSUER}"
+    public_key: "${JWT_PUBKEY}"
   client_secret: "${CLIENT_SECRET}"
   client_id: "${CLIENT_ID}"
   gateway.endpoint: http://gateway-service:9095
\ No newline at end of file
diff --git a/fda-container-service/rest-service/src/test/java/at/tuwien/auth/AuthTokenFilterTest.java b/fda-container-service/rest-service/src/test/java/at/tuwien/auth/AuthTokenFilterTest.java
index 79e4d7ee81..8dd394b17d 100644
--- a/fda-container-service/rest-service/src/test/java/at/tuwien/auth/AuthTokenFilterTest.java
+++ b/fda-container-service/rest-service/src/test/java/at/tuwien/auth/AuthTokenFilterTest.java
@@ -3,7 +3,6 @@ package at.tuwien.auth;
 import at.tuwien.BaseUnitTest;
 import at.tuwien.config.H2Utils;
 import at.tuwien.config.ReadyConfig;
-import at.tuwien.gateway.AuthenticationServiceGateway;
 import at.tuwien.repository.jpa.UserRepository;
 import lombok.extern.log4j.Log4j2;
 import org.junit.jupiter.api.BeforeEach;
@@ -23,8 +22,6 @@ import java.io.IOException;
 import java.util.Optional;
 
 import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.when;
 
 @Log4j2
@@ -38,9 +35,6 @@ public class AuthTokenFilterTest extends BaseUnitTest {
     @MockBean
     private UserRepository userRepository;
 
-    @MockBean
-    private AuthenticationServiceGateway authenticationServiceGateway;
-
     @Autowired
     private AuthTokenFilter authTokenFilter;
 
@@ -60,9 +54,6 @@ public class AuthTokenFilterTest extends BaseUnitTest {
         final FilterChain chain = new MockFilterChain();
 
         /* mock */
-        doThrow(new ServletException("Username not found"))
-                .when(authenticationServiceGateway)
-                .validate(anyString());
         when(userRepository.findByUsername("mweise"))
                 .thenReturn(Optional.empty());
 
@@ -80,8 +71,6 @@ public class AuthTokenFilterTest extends BaseUnitTest {
         final FilterChain chain = new MockFilterChain();
 
         /* mock */
-        when(authenticationServiceGateway.validate(anyString()))
-                .thenReturn(USER_1_DETAILS);
         when(userRepository.findByUsername("mweise"))
                 .thenReturn(Optional.of(USER_1));
 
diff --git a/fda-container-service/rest-service/src/test/java/at/tuwien/gateway/AuthenticationServiceGatewayTest.java b/fda-container-service/rest-service/src/test/java/at/tuwien/gateway/AuthenticationServiceGatewayTest.java
deleted file mode 100644
index 09afe818e3..0000000000
--- a/fda-container-service/rest-service/src/test/java/at/tuwien/gateway/AuthenticationServiceGatewayTest.java
+++ /dev/null
@@ -1,76 +0,0 @@
-package at.tuwien.gateway;
-
-import at.tuwien.BaseUnitTest;
-import at.tuwien.api.user.UserDto;
-import at.tuwien.config.ReadyConfig;
-import lombok.extern.log4j.Log4j2;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.boot.test.mock.mockito.MockBean;
-import org.springframework.http.HttpEntity;
-import org.springframework.http.HttpMethod;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.ResponseEntity;
-import org.springframework.security.core.authority.SimpleGrantedAuthority;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.test.context.junit.jupiter.SpringExtension;
-import org.springframework.web.client.RestTemplate;
-
-
-import javax.servlet.ServletException;
-import java.util.List;
-
-import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.any;
-
-@Log4j2
-@SpringBootTest
-@ExtendWith(SpringExtension.class)
-public class AuthenticationServiceGatewayTest extends BaseUnitTest {
-
-    @MockBean
-    private ReadyConfig readyConfig;
-
-    @MockBean
-    private RestTemplate restTemplate;
-
-    @Autowired
-    private AuthenticationServiceGateway authenticationServiceGateway;
-
-    @Test
-    public void validate_succeeds() throws ServletException {
-        final ResponseEntity<UserDto> mock = ResponseEntity.status(HttpStatus.ACCEPTED)
-                .body(USER_1_DTO);
-
-        /* mock */
-        when(restTemplate.exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(UserDto.class)))
-                .thenReturn(mock);
-
-        /* test */
-        final UserDetails response = authenticationServiceGateway.validate(JWT_1);
-        assertNotNull(response);
-        assertEquals(USER_1_USERNAME, response.getUsername());
-        assertEquals(List.of(new SimpleGrantedAuthority("ROLE_RESEARCHER")), response.getAuthorities());
-    }
-
-    @Test
-    public void validate_notFound_fails() {
-        final ResponseEntity<UserDto> mock = ResponseEntity.status(HttpStatus.NOT_FOUND)
-                .body(USER_1_DTO);
-
-        /* mock */
-        when(restTemplate.exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(UserDto.class)))
-                .thenReturn(mock);
-
-        /* test */
-        assertThrows(ServletException.class, () -> {
-            authenticationServiceGateway.validate(JWT_1);
-        });
-    }
-
-}
diff --git a/fda-container-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java b/fda-container-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java
index 20b5fd9fcd..b2b01c42ee 100644
--- a/fda-container-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java
+++ b/fda-container-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java
@@ -1,8 +1,15 @@
 package at.tuwien.auth;
 
-import at.tuwien.gateway.AuthenticationServiceGateway;
+import at.tuwien.api.auth.RealmAccessDto;
+import at.tuwien.api.user.UserDetailsDto;
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.JWTVerifier;
+import com.auth0.jwt.algorithms.Algorithm;
+import com.auth0.jwt.interfaces.DecodedJWT;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
@@ -14,22 +21,30 @@ import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.stream.Collectors;
 
 @Slf4j
 public class AuthTokenFilter extends OncePerRequestFilter {
 
-    private final AuthenticationServiceGateway authenticationServiceGateway;
+    @Value("${fda.jwt.issuer}")
+    private String issuer;
 
-    public AuthTokenFilter(AuthenticationServiceGateway authenticationServiceGateway) {
-        this.authenticationServiceGateway = authenticationServiceGateway;
-    }
+    @Value("${fda.jwt.public_key}")
+    private String publicKey;
 
     @Override
     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
             throws ServletException, IOException {
         final String jwt = parseJwt(request);
         if (jwt != null) {
-            final UserDetails userDetails = authenticationServiceGateway.validate(jwt);
+            final UserDetails userDetails = verifyJwt(jwt);
             log.debug("authenticated user {}", userDetails);
             final UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                     userDetails, null, userDetails.getAuthorities());
@@ -40,6 +55,35 @@ public class AuthTokenFilter extends OncePerRequestFilter {
         filterChain.doFilter(request, response);
     }
 
+    public UserDetails verifyJwt(String token) throws ServletException {
+        final KeyFactory kf;
+        try {
+            kf = KeyFactory.getInstance("RSA");
+        } catch (NoSuchAlgorithmException e) {
+            log.error("Failed to find RSA algorithm");
+            throw new ServletException("Failed to find RSA algorithm", e);
+        }
+        final X509EncodedKeySpec keySpecX509 = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey));
+        final RSAPublicKey pubKey;
+        try {
+            pubKey = (RSAPublicKey) kf.generatePublic(keySpecX509);
+        } catch (InvalidKeySpecException e) {
+            log.error("Provided public key is invalid");
+            throw new ServletException("Provided public key is invalid", e);
+        }
+        final Algorithm algorithm = Algorithm.RSA256(pubKey, null);
+        JWTVerifier verifier = JWT.require(algorithm)
+                .withIssuer(issuer)
+                .withAudience("spring")
+                .build();
+        final DecodedJWT jwt = verifier.verify(token);
+        final RealmAccessDto realmAccess = jwt.getClaim("realm_access").as(RealmAccessDto.class);
+        return UserDetailsDto.builder()
+                .username(jwt.getClaim("client_id").asString())
+                .authorities(Arrays.stream(realmAccess.getRoles()).map(SimpleGrantedAuthority::new).collect(Collectors.toList()))
+                .build();
+    }
+
     /**
      * Parses the token from the HTTP header of the request
      *
diff --git a/fda-container-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java b/fda-container-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java
index d5c8acfb2d..5724f37ead 100644
--- a/fda-container-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java
+++ b/fda-container-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java
@@ -1,10 +1,8 @@
 package at.tuwien.config;
 
 import at.tuwien.auth.AuthTokenFilter;
-import at.tuwien.gateway.AuthenticationServiceGateway;
 import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
 import io.swagger.v3.oas.annotations.security.SecurityScheme;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.http.HttpMethod;
@@ -31,16 +29,9 @@ import javax.servlet.http.HttpServletResponse;
 )
 public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 
-    private final AuthenticationServiceGateway authenticationServiceGateway;
-
-    @Autowired
-    public WebSecurityConfig(AuthenticationServiceGateway authenticationServiceGateway) {
-        this.authenticationServiceGateway = authenticationServiceGateway;
-    }
-
     @Bean
     public AuthTokenFilter authTokenFilter() {
-        return new AuthTokenFilter(authenticationServiceGateway);
+        return new AuthTokenFilter();
     }
 
     @Override
diff --git a/fda-container-service/services/src/main/java/at/tuwien/gateway/AuthenticationServiceGateway.java b/fda-container-service/services/src/main/java/at/tuwien/gateway/AuthenticationServiceGateway.java
deleted file mode 100644
index 323608b39f..0000000000
--- a/fda-container-service/services/src/main/java/at/tuwien/gateway/AuthenticationServiceGateway.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package at.tuwien.gateway;
-
-import org.springframework.security.core.userdetails.UserDetails;
-
-import javax.servlet.ServletException;
-
-public interface AuthenticationServiceGateway {
-
-    /**
-     * Validates a token
-     *
-     * @param token The token
-     * @return User details on success
-     */
-    UserDetails validate(String token) throws ServletException;
-}
diff --git a/fda-container-service/services/src/main/java/at/tuwien/gateway/impl/AuthenticationServiceGatewayImpl.java b/fda-container-service/services/src/main/java/at/tuwien/gateway/impl/AuthenticationServiceGatewayImpl.java
deleted file mode 100644
index f2e5e35219..0000000000
--- a/fda-container-service/services/src/main/java/at/tuwien/gateway/impl/AuthenticationServiceGatewayImpl.java
+++ /dev/null
@@ -1,64 +0,0 @@
-package at.tuwien.gateway.impl;
-
-import at.tuwien.api.auth.TokenIntrospectDto;
-import at.tuwien.api.user.UserBriefDto;
-import at.tuwien.api.user.UserDetailsDto;
-import at.tuwien.config.GatewayConfig;
-import at.tuwien.gateway.AuthenticationServiceGateway;
-import at.tuwien.mapper.UserMapper;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.http.*;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.stereotype.Service;
-import org.springframework.util.LinkedMultiValueMap;
-import org.springframework.util.MultiValueMap;
-import org.springframework.web.client.HttpStatusCodeException;
-import org.springframework.web.client.RestTemplate;
-
-import javax.servlet.ServletException;
-
-@Slf4j
-@Service
-public class AuthenticationServiceGatewayImpl implements AuthenticationServiceGateway {
-
-    private final UserMapper userMapper;
-    private final RestTemplate restTemplate;
-    private final GatewayConfig gatewayConfig;
-
-    @Autowired
-    public AuthenticationServiceGatewayImpl(UserMapper userMapper, RestTemplate restTemplate, GatewayConfig gatewayConfig) {
-        this.userMapper = userMapper;
-        this.restTemplate = restTemplate;
-        this.gatewayConfig = gatewayConfig;
-    }
-
-    @Override
-    public UserDetails validate(String token) throws ServletException {
-        final HttpHeaders headers = new HttpHeaders();
-        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
-        final MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
-        body.add("client_secret", gatewayConfig.getClientSecret());
-        body.add("client_id", gatewayConfig.getClientId());
-        body.add("token", token);
-        try {
-            final ResponseEntity<TokenIntrospectDto> response = restTemplate.exchange("/api/auth/realms/dbrepo/protocol/openid-connect/token/introspect", HttpMethod.POST,
-                    new HttpEntity<>(body, headers), TokenIntrospectDto.class);
-            if (!response.getStatusCode().equals(HttpStatus.OK)) {
-                log.error("Failed to validate token with status code {}", response.getStatusCode());
-                throw new ServletException("Failed to validate token: http status code is not ok");
-            } else if (response.getBody() == null) {
-                throw new ServletException("Failed to validate token: body is null");
-            } else if (!response.getBody().getActive()) {
-                throw new ServletException("Failed to validate token: token is not active");
-            }
-            final UserDetailsDto dto = userMapper.tokenIntrospectDtoToUserDetailsDto(response.getBody());
-            log.trace("gateway authenticated user {}", dto);
-            return dto;
-        } catch (HttpStatusCodeException e) {
-            log.error("Failed to validate token with status code {}", e.getStatusCode());
-            throw new ServletException("Failed to validate token", e);
-        }
-    }
-
-}
diff --git a/fda-database-service/Dockerfile b/fda-database-service/Dockerfile
index d57c8704f0..abc7709426 100644
--- a/fda-database-service/Dockerfile
+++ b/fda-database-service/Dockerfile
@@ -31,8 +31,10 @@ ENV SEARCH_USERNAME=elastic
 ENV SEARCH_PASSWORD=elastic
 ENV GATEWAY_ENDPOINT=http://gateway-service:9095
 ENV LOG_LEVEL=debug
-ENV CLIENT_SECRET=client-secret
+ENV CLIENT_SECRET="${DBREPO_CLIENT_SECRET:-client-secret}"
 ENV CLIENT_ID=dbrepo-client
+ENV JWT_ISSUER=http://localhost:8080/realms/dbrepo
+ENV JWT_PUBKEY=public-key
 
 COPY ./service_ready /usr/bin
 RUN chmod +x /usr/bin/service_ready
diff --git a/fda-database-service/pom.xml b/fda-database-service/pom.xml
index 84b065b55e..38ddd1951e 100644
--- a/fda-database-service/pom.xml
+++ b/fda-database-service/pom.xml
@@ -31,6 +31,7 @@
         <springfox.version>3.0.0</springfox.version>
         <jacoco.version>0.8.7</jacoco.version>
         <hibernate-c3po.version>5.6.3.Final</hibernate-c3po.version>
+        <jwt.version>4.3.0</jwt.version>
     </properties>
 
     <dependencies>
@@ -59,6 +60,12 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-actuator</artifactId>
         </dependency>
+        <!-- Authentication -->
+        <dependency>
+            <groupId>com.auth0</groupId>
+            <artifactId>java-jwt</artifactId>
+            <version>${jwt.version}</version>
+        </dependency>
         <!-- Entities and API -->
         <dependency>
             <groupId>at.tuwien</groupId>
diff --git a/fda-database-service/rest-service/src/main/resources/application-docker.yml b/fda-database-service/rest-service/src/main/resources/application-docker.yml
index 10adc604b2..077cd6cfaa 100644
--- a/fda-database-service/rest-service/src/main/resources/application-docker.yml
+++ b/fda-database-service/rest-service/src/main/resources/application-docker.yml
@@ -43,6 +43,9 @@ fda:
     username: elastic
     password: "${ELASTIC_PASSWORD}"
   ready.path: /ready
+  jwt:
+    issuer: "${JWT_ISSUER}"
+    public_key: "${JWT_PUBKEY}"
   client_secret: "${CLIENT_SECRET}"
   client_id: "${CLIENT_ID}"
   gateway.endpoint: "${GATEWAY_ENDPOINT}"
\ No newline at end of file
diff --git a/fda-database-service/rest-service/src/main/resources/application-local.yml b/fda-database-service/rest-service/src/main/resources/application-local.yml
index 5ace5e75c2..bd021238de 100644
--- a/fda-database-service/rest-service/src/main/resources/application-local.yml
+++ b/fda-database-service/rest-service/src/main/resources/application-local.yml
@@ -43,6 +43,9 @@ fda:
     username: elastic
     password: elastic
   ready.path: ./ready
-  client_secret: Gp9IALXWsfftK8ek1J6jNT9hNfWV5U5c
+  jwt:
+    issuer: http://localhost:8080/realms/dbrepo
+    public_key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqqnHQ2BWWW9vDNLRCcxD++xZg/16oqMo/c1l+lcFEjjAIJjJp/HqrPYU/U9GvquGE6PbVFtTzW1KcKawOW+FJNOA3CGo8Q1TFEfz43B8rZpKsFbJKvQGVv1Z4HaKPvLUm7iMm8Hv91cLduuoWx6Q3DPe2vg13GKKEZe7UFghF+0T9u8EKzA/XqQ0OiICmsmYPbwvf9N3bCKsB/Y10EYmZRb8IhCoV9mmO5TxgWgiuNeCTtNCv2ePYqL/U0WvyGFW0reasIK8eg3KrAUj8DpyOgPOVBn3lBGf+3KFSYi+0bwZbJZWqbC/Xlk20Go1YfeJPRIt7ImxD27R/lNjgDO/MwIDAQAB
+  client_secret: client-secret
   client_id: dbrepo-client
   gateway.endpoint: http://localhost:9095
\ No newline at end of file
diff --git a/fda-database-service/rest-service/src/main/resources/application.yml b/fda-database-service/rest-service/src/main/resources/application.yml
index 9ba93e2013..8a670dbd08 100644
--- a/fda-database-service/rest-service/src/main/resources/application.yml
+++ b/fda-database-service/rest-service/src/main/resources/application.yml
@@ -43,6 +43,9 @@ fda:
     username: elastic
     password: "${ELASTIC_PASSWORD}"
   ready.path: /ready
+  jwt:
+    issuer: "${JWT_ISSUER}"
+    public_key: "${JWT_PUBKEY}"
   client_secret: "${CLIENT_SECRET}"
   client_id: "${CLIENT_ID}"
   gateway.endpoint: http://gateway-service:9095
\ No newline at end of file
diff --git a/fda-database-service/rest-service/src/test/java/at/tuwien/auth/AuthTokenFilterTest.java b/fda-database-service/rest-service/src/test/java/at/tuwien/auth/AuthTokenFilterTest.java
index c2026c288b..6fa1b5a7ad 100644
--- a/fda-database-service/rest-service/src/test/java/at/tuwien/auth/AuthTokenFilterTest.java
+++ b/fda-database-service/rest-service/src/test/java/at/tuwien/auth/AuthTokenFilterTest.java
@@ -4,7 +4,6 @@ import at.tuwien.BaseUnitTest;
 import at.tuwien.config.H2Utils;
 import at.tuwien.config.IndexConfig;
 import at.tuwien.config.ReadyConfig;
-import at.tuwien.gateway.AuthenticationServiceGateway;
 import at.tuwien.repository.jpa.UserRepository;
 import com.rabbitmq.client.Channel;
 import lombok.extern.log4j.Log4j2;
@@ -46,9 +45,6 @@ public class AuthTokenFilterTest extends BaseUnitTest {
     @MockBean
     private UserRepository userRepository;
 
-    @MockBean
-    private AuthenticationServiceGateway authenticationServiceGateway;
-
     @Autowired
     private AuthTokenFilter authTokenFilter;
 
@@ -68,9 +64,6 @@ public class AuthTokenFilterTest extends BaseUnitTest {
         final FilterChain chain = new MockFilterChain();
 
         /* mock */
-        doThrow(new ServletException("Username not found"))
-                .when(authenticationServiceGateway)
-                .validate(anyString());
         when(userRepository.findByUsername("mweise"))
                 .thenReturn(Optional.empty());
 
@@ -88,8 +81,6 @@ public class AuthTokenFilterTest extends BaseUnitTest {
         final FilterChain chain = new MockFilterChain();
 
         /* mock */
-        when(authenticationServiceGateway.validate(anyString()))
-                .thenReturn(USER_1_DETAILS);
         when(userRepository.findByUsername("mweise"))
                 .thenReturn(Optional.of(USER_1));
 
diff --git a/fda-database-service/rest-service/src/test/java/at/tuwien/gateway/AuthenticationServiceGatewayTest.java b/fda-database-service/rest-service/src/test/java/at/tuwien/gateway/AuthenticationServiceGatewayTest.java
deleted file mode 100644
index 0abdc13f90..0000000000
--- a/fda-database-service/rest-service/src/test/java/at/tuwien/gateway/AuthenticationServiceGatewayTest.java
+++ /dev/null
@@ -1,87 +0,0 @@
-package at.tuwien.gateway;
-
-import at.tuwien.BaseUnitTest;
-import at.tuwien.api.user.UserDto;
-import at.tuwien.config.IndexConfig;
-import at.tuwien.config.ReadyConfig;
-import at.tuwien.repository.jpa.UserRepository;
-import com.rabbitmq.client.Channel;
-import lombok.extern.log4j.Log4j2;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.boot.test.mock.mockito.MockBean;
-import org.springframework.http.HttpEntity;
-import org.springframework.http.HttpMethod;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.ResponseEntity;
-import org.springframework.security.core.authority.SimpleGrantedAuthority;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.test.context.junit.jupiter.SpringExtension;
-import org.springframework.web.client.RestTemplate;
-
-import javax.servlet.ServletException;
-import java.util.List;
-
-import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.*;
-
-@Log4j2
-@SpringBootTest
-@ExtendWith(SpringExtension.class)
-public class AuthenticationServiceGatewayTest extends BaseUnitTest {
-
-    @MockBean
-    private ReadyConfig readyConfig;
-
-    @MockBean
-    private Channel channel;
-
-    @MockBean
-    private IndexConfig indexConfig;
-
-    @MockBean
-    @Qualifier("authenticationRestTemplate")
-    private RestTemplate restTemplate;
-
-    @MockBean
-    private UserRepository userRepository;
-
-    @Autowired
-    private AuthenticationServiceGateway authenticationServiceGateway;
-
-    @Test
-    public void validate_succeeds() throws ServletException {
-        final ResponseEntity<UserDto> mock = ResponseEntity.status(HttpStatus.ACCEPTED)
-                .body(USER_1_DTO);
-
-        /* mock */
-        when(restTemplate.exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(UserDto.class)))
-                .thenReturn(mock);
-
-        /* test */
-        final UserDetails response = authenticationServiceGateway.validate(JWT_1);
-        assertNotNull(response);
-        assertEquals(USER_1_USERNAME, response.getUsername());
-        assertEquals(List.of(new SimpleGrantedAuthority("ROLE_RESEARCHER")), response.getAuthorities());
-    }
-
-    @Test
-    public void validate_notFound_fails() {
-        final ResponseEntity<UserDto> mock = ResponseEntity.status(HttpStatus.NOT_FOUND)
-                .body(USER_1_DTO);
-
-        /* mock */
-        when(restTemplate.exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(UserDto.class)))
-                .thenReturn(mock);
-
-        /* test */
-        assertThrows(ServletException.class, () -> {
-            authenticationServiceGateway.validate(JWT_1);
-        });
-    }
-
-}
diff --git a/fda-database-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java b/fda-database-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java
index 1b7fcb36ea..b2b01c42ee 100644
--- a/fda-database-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java
+++ b/fda-database-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java
@@ -1,8 +1,15 @@
 package at.tuwien.auth;
 
-import at.tuwien.gateway.AuthenticationServiceGateway;
+import at.tuwien.api.auth.RealmAccessDto;
+import at.tuwien.api.user.UserDetailsDto;
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.JWTVerifier;
+import com.auth0.jwt.algorithms.Algorithm;
+import com.auth0.jwt.interfaces.DecodedJWT;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
@@ -14,22 +21,30 @@ import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.stream.Collectors;
 
 @Slf4j
 public class AuthTokenFilter extends OncePerRequestFilter {
 
-    private final AuthenticationServiceGateway authenticationServiceGateway;
+    @Value("${fda.jwt.issuer}")
+    private String issuer;
 
-    public AuthTokenFilter(AuthenticationServiceGateway authenticationServiceGateway) {
-        this.authenticationServiceGateway = authenticationServiceGateway;
-    }
+    @Value("${fda.jwt.public_key}")
+    private String publicKey;
 
     @Override
     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
             throws ServletException, IOException {
         final String jwt = parseJwt(request);
         if (jwt != null) {
-            final UserDetails userDetails = authenticationServiceGateway.validate(jwt);
+            final UserDetails userDetails = verifyJwt(jwt);
             log.debug("authenticated user {}", userDetails);
             final UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                     userDetails, null, userDetails.getAuthorities());
@@ -40,17 +55,46 @@ public class AuthTokenFilter extends OncePerRequestFilter {
         filterChain.doFilter(request, response);
     }
 
+    public UserDetails verifyJwt(String token) throws ServletException {
+        final KeyFactory kf;
+        try {
+            kf = KeyFactory.getInstance("RSA");
+        } catch (NoSuchAlgorithmException e) {
+            log.error("Failed to find RSA algorithm");
+            throw new ServletException("Failed to find RSA algorithm", e);
+        }
+        final X509EncodedKeySpec keySpecX509 = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey));
+        final RSAPublicKey pubKey;
+        try {
+            pubKey = (RSAPublicKey) kf.generatePublic(keySpecX509);
+        } catch (InvalidKeySpecException e) {
+            log.error("Provided public key is invalid");
+            throw new ServletException("Provided public key is invalid", e);
+        }
+        final Algorithm algorithm = Algorithm.RSA256(pubKey, null);
+        JWTVerifier verifier = JWT.require(algorithm)
+                .withIssuer(issuer)
+                .withAudience("spring")
+                .build();
+        final DecodedJWT jwt = verifier.verify(token);
+        final RealmAccessDto realmAccess = jwt.getClaim("realm_access").as(RealmAccessDto.class);
+        return UserDetailsDto.builder()
+                .username(jwt.getClaim("client_id").asString())
+                .authorities(Arrays.stream(realmAccess.getRoles()).map(SimpleGrantedAuthority::new).collect(Collectors.toList()))
+                .build();
+    }
+
     /**
      * Parses the token from the HTTP header of the request
      *
      * @param request The request.
      * @return The token.
      */
-    String parseJwt(HttpServletRequest request) {
+    public String parseJwt(HttpServletRequest request) {
         String headerAuth = request.getHeader("Authorization");
         if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) {
             return headerAuth.substring(7, headerAuth.length());
         }
         return null;
     }
-}
+}
\ No newline at end of file
diff --git a/fda-database-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java b/fda-database-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java
index 1bcc47efa7..afff7f2dad 100644
--- a/fda-database-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java
+++ b/fda-database-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java
@@ -1,10 +1,8 @@
 package at.tuwien.config;
 
 import at.tuwien.auth.AuthTokenFilter;
-import at.tuwien.gateway.AuthenticationServiceGateway;
 import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
 import io.swagger.v3.oas.annotations.security.SecurityScheme;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.http.HttpMethod;
@@ -31,16 +29,9 @@ import javax.servlet.http.HttpServletResponse;
 )
 public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 
-    private final AuthenticationServiceGateway authenticationServiceGateway;
-
-    @Autowired
-    public WebSecurityConfig(AuthenticationServiceGateway authenticationServiceGateway) {
-        this.authenticationServiceGateway = authenticationServiceGateway;
-    }
-
     @Bean
     public AuthTokenFilter authTokenFilter() {
-        return new AuthTokenFilter(authenticationServiceGateway);
+        return new AuthTokenFilter();
     }
 
     @Override
diff --git a/fda-database-service/services/src/main/java/at/tuwien/gateway/AuthenticationServiceGateway.java b/fda-database-service/services/src/main/java/at/tuwien/gateway/AuthenticationServiceGateway.java
deleted file mode 100644
index 323608b39f..0000000000
--- a/fda-database-service/services/src/main/java/at/tuwien/gateway/AuthenticationServiceGateway.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package at.tuwien.gateway;
-
-import org.springframework.security.core.userdetails.UserDetails;
-
-import javax.servlet.ServletException;
-
-public interface AuthenticationServiceGateway {
-
-    /**
-     * Validates a token
-     *
-     * @param token The token
-     * @return User details on success
-     */
-    UserDetails validate(String token) throws ServletException;
-}
diff --git a/fda-database-service/services/src/main/java/at/tuwien/gateway/impl/AuthenticationServiceGatewayImpl.java b/fda-database-service/services/src/main/java/at/tuwien/gateway/impl/AuthenticationServiceGatewayImpl.java
deleted file mode 100644
index dbdec859da..0000000000
--- a/fda-database-service/services/src/main/java/at/tuwien/gateway/impl/AuthenticationServiceGatewayImpl.java
+++ /dev/null
@@ -1,66 +0,0 @@
-package at.tuwien.gateway.impl;
-
-import at.tuwien.api.auth.TokenIntrospectDto;
-import at.tuwien.api.user.UserDetailsDto;
-import at.tuwien.config.GatewayConfig;
-import at.tuwien.gateway.AuthenticationServiceGateway;
-import at.tuwien.mapper.UserMapper;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.http.*;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.stereotype.Service;
-import org.springframework.util.LinkedMultiValueMap;
-import org.springframework.util.MultiValueMap;
-import org.springframework.web.client.HttpStatusCodeException;
-import org.springframework.web.client.RestTemplate;
-
-import javax.servlet.ServletException;
-
-@Slf4j
-@Service
-public class AuthenticationServiceGatewayImpl implements AuthenticationServiceGateway {
-
-    private final UserMapper userMapper;
-    private final RestTemplate restTemplate;
-    private final GatewayConfig gatewayConfig;
-
-    @Autowired
-    public AuthenticationServiceGatewayImpl(UserMapper userMapper,
-                                            @Qualifier("authenticationRestTemplate") RestTemplate restTemplate,
-                                            GatewayConfig gatewayConfig) {
-        this.userMapper = userMapper;
-        this.restTemplate = restTemplate;
-        this.gatewayConfig = gatewayConfig;
-    }
-
-    @Override
-    public UserDetails validate(String token) throws ServletException {
-        final HttpHeaders headers = new HttpHeaders();
-        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
-        final MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
-        body.add("client_secret", gatewayConfig.getClientSecret());
-        body.add("client_id", gatewayConfig.getClientId());
-        body.add("token", token);
-        try {
-            final ResponseEntity<TokenIntrospectDto> response = restTemplate.exchange("/api/auth/realms/dbrepo/protocol/openid-connect/token/introspect", HttpMethod.POST,
-                    new HttpEntity<>(body, headers), TokenIntrospectDto.class);
-            if (!response.getStatusCode().equals(HttpStatus.OK)) {
-                log.error("Failed to validate token with status code {}", response.getStatusCode());
-                throw new ServletException("Failed to validate token: http status code is not ok");
-            } else if (response.getBody() == null) {
-                throw new ServletException("Failed to validate token: body is null");
-            } else if (!response.getBody().getActive()) {
-                throw new ServletException("Failed to validate token: token is not active");
-            }
-            final UserDetailsDto dto = userMapper.tokenIntrospectDtoToUserDetailsDto(response.getBody());
-            log.trace("gateway authenticated user {}", dto);
-            return dto;
-        } catch (HttpStatusCodeException e) {
-            log.error("Failed to validate token with status code {}", e.getStatusCode());
-            throw new ServletException("Failed to validate token", e);
-        }
-    }
-
-}
diff --git a/fda-identifier-service/Dockerfile b/fda-identifier-service/Dockerfile
index d9976484e8..47cbf34ae5 100644
--- a/fda-identifier-service/Dockerfile
+++ b/fda-identifier-service/Dockerfile
@@ -28,6 +28,10 @@ ENV METADATA_PASSWORD=dbrepo
 ENV GATEWAY_ENDPOINT=http://gateway-service:9095
 ENV WEBSITE=http://localhost:3000
 ENV LOG_LEVEL=debug
+ENV CLIENT_SECRET="${DBREPO_CLIENT_SECRET:-client-secret}"
+ENV CLIENT_ID=dbrepo-client
+ENV JWT_ISSUER=http://localhost:8080/realms/dbrepo
+ENV JWT_PUBKEY=public-key
 
 COPY ./service_ready /usr/bin
 RUN chmod +x /usr/bin/service_ready
diff --git a/fda-identifier-service/pom.xml b/fda-identifier-service/pom.xml
index b5fbe58b6a..1412057044 100644
--- a/fda-identifier-service/pom.xml
+++ b/fda-identifier-service/pom.xml
@@ -33,6 +33,7 @@
         <maven-site.version>3.10.0</maven-site.version>
         <docker.version>3.2.7</docker.version>
         <commons-io.version>2.11.0</commons-io.version>
+        <jwt.version>4.3.0</jwt.version>
     </properties>
 
     <dependencies>
@@ -70,6 +71,12 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-actuator</artifactId>
         </dependency>
+        <!-- Authentication -->
+        <dependency>
+            <groupId>com.auth0</groupId>
+            <artifactId>java-jwt</artifactId>
+            <version>${jwt.version}</version>
+        </dependency>
         <!-- Monitoring -->
         <dependency>
             <groupId>io.micrometer</groupId>
diff --git a/fda-identifier-service/rest-service/src/main/resources/application-docker.yml b/fda-identifier-service/rest-service/src/main/resources/application-docker.yml
index 418f38ddab..1930d3a318 100644
--- a/fda-identifier-service/rest-service/src/main/resources/application-docker.yml
+++ b/fda-identifier-service/rest-service/src/main/resources/application-docker.yml
@@ -35,6 +35,11 @@ eureka:
   client.serviceUrl.defaultZone: http://discovery-service:9090/eureka/
 fda:
   ready.path: /ready
+  jwt:
+    issuer: "${JWT_ISSUER}"
+    public_key: "${JWT_PUBKEY}"
+  client_secret: "${CLIENT_SECRET}"
+  client_id: "${CLIENT_ID}"
   gateway.endpoint: "${GATEWAY_ENDPOINT}"
   website: "${WEBSITE}"
   elastic:
diff --git a/fda-identifier-service/rest-service/src/main/resources/application-local.yml b/fda-identifier-service/rest-service/src/main/resources/application-local.yml
index d40853659b..6a7ec7b454 100644
--- a/fda-identifier-service/rest-service/src/main/resources/application-local.yml
+++ b/fda-identifier-service/rest-service/src/main/resources/application-local.yml
@@ -35,6 +35,11 @@ eureka:
   client.serviceUrl.defaultZone: http://localhost:9090/eureka/
 fda:
   ready.path: ./ready
+  jwt:
+    issuer: http://localhost:8080/realms/dbrepo
+    public_key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqqnHQ2BWWW9vDNLRCcxD++xZg/16oqMo/c1l+lcFEjjAIJjJp/HqrPYU/U9GvquGE6PbVFtTzW1KcKawOW+FJNOA3CGo8Q1TFEfz43B8rZpKsFbJKvQGVv1Z4HaKPvLUm7iMm8Hv91cLduuoWx6Q3DPe2vg13GKKEZe7UFghF+0T9u8EKzA/XqQ0OiICmsmYPbwvf9N3bCKsB/Y10EYmZRb8IhCoV9mmO5TxgWgiuNeCTtNCv2ePYqL/U0WvyGFW0reasIK8eg3KrAUj8DpyOgPOVBn3lBGf+3KFSYi+0bwZbJZWqbC/Xlk20Go1YfeJPRIt7ImxD27R/lNjgDO/MwIDAQAB
+  client_secret: client-secret
+  client_id: dbrepo-client
   gateway.endpoint: http://localhost:9095
   website: http://localhost:3000
   elastic:
diff --git a/fda-identifier-service/rest-service/src/main/resources/application.yml b/fda-identifier-service/rest-service/src/main/resources/application.yml
index e0c7ffd06f..c31a5533d2 100644
--- a/fda-identifier-service/rest-service/src/main/resources/application.yml
+++ b/fda-identifier-service/rest-service/src/main/resources/application.yml
@@ -35,6 +35,11 @@ eureka:
   client.serviceUrl.defaultZone: http://discovery-service:9090/eureka/
 fda:
   ready.path: /ready
+  jwt:
+    issuer: "${JWT_ISSUER}"
+    public_key: "${JWT_PUBKEY}"
+  client_secret: "${CLIENT_SECRET}"
+  client_id: "${CLIENT_ID}"
   gateway.endpoint: "${GATEWAY_ENDPOINT}"
   website: "${WEBSITE}"
   elastic:
diff --git a/fda-identifier-service/rest-service/src/test/java/at/tuwien/auth/AuthTokenFilterTest.java b/fda-identifier-service/rest-service/src/test/java/at/tuwien/auth/AuthTokenFilterTest.java
index b7dc8495cd..b18801abc8 100644
--- a/fda-identifier-service/rest-service/src/test/java/at/tuwien/auth/AuthTokenFilterTest.java
+++ b/fda-identifier-service/rest-service/src/test/java/at/tuwien/auth/AuthTokenFilterTest.java
@@ -4,7 +4,6 @@ import at.tuwien.BaseUnitTest;
 import at.tuwien.config.H2Utils;
 import at.tuwien.config.IndexInitializer;
 import at.tuwien.config.ReadyConfig;
-import at.tuwien.gateway.AuthenticationServiceGateway;
 import at.tuwien.repository.jpa.UserRepository;
 import lombok.extern.log4j.Log4j2;
 import org.junit.jupiter.api.BeforeEach;
@@ -24,8 +23,6 @@ import java.io.IOException;
 import java.util.Optional;
 
 import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.when;
 
 @Log4j2
@@ -42,9 +39,6 @@ public class AuthTokenFilterTest extends BaseUnitTest {
     @MockBean
     private UserRepository userRepository;
 
-    @MockBean
-    private AuthenticationServiceGateway authenticationServiceGateway;
-
     @Autowired
     private AuthTokenFilter authTokenFilter;
 
@@ -64,9 +58,6 @@ public class AuthTokenFilterTest extends BaseUnitTest {
         final FilterChain chain = new MockFilterChain();
 
         /* mock */
-        doThrow(new ServletException("Username not found"))
-                .when(authenticationServiceGateway)
-                .validate(anyString());
         when(userRepository.findByUsername("mweise"))
                 .thenReturn(Optional.empty());
 
@@ -84,8 +75,6 @@ public class AuthTokenFilterTest extends BaseUnitTest {
         final FilterChain chain = new MockFilterChain();
 
         /* mock */
-        when(authenticationServiceGateway.validate(anyString()))
-                .thenReturn(USER_1_DETAILS);
         when(userRepository.findByUsername("mweise"))
                 .thenReturn(Optional.of(USER_1));
 
diff --git a/fda-identifier-service/rest-service/src/test/java/at/tuwien/gateway/AuthenticationServiceGatewayTest.java b/fda-identifier-service/rest-service/src/test/java/at/tuwien/gateway/AuthenticationServiceGatewayTest.java
deleted file mode 100644
index d3ed4bc47e..0000000000
--- a/fda-identifier-service/rest-service/src/test/java/at/tuwien/gateway/AuthenticationServiceGatewayTest.java
+++ /dev/null
@@ -1,81 +0,0 @@
-package at.tuwien.gateway;
-
-import at.tuwien.BaseUnitTest;
-import at.tuwien.api.user.UserDto;
-import at.tuwien.config.IndexInitializer;
-import at.tuwien.config.ReadyConfig;
-import at.tuwien.repository.jpa.UserRepository;
-import lombok.extern.log4j.Log4j2;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.boot.test.mock.mockito.MockBean;
-import org.springframework.http.HttpEntity;
-import org.springframework.http.HttpMethod;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.ResponseEntity;
-import org.springframework.security.core.authority.SimpleGrantedAuthority;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.test.context.junit.jupiter.SpringExtension;
-import org.springframework.web.client.RestTemplate;
-
-import javax.servlet.ServletException;
-import java.util.List;
-
-import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.*;
-
-@Log4j2
-@SpringBootTest
-@ExtendWith(SpringExtension.class)
-public class AuthenticationServiceGatewayTest extends BaseUnitTest {
-
-    @MockBean
-    private IndexInitializer indexInitializer;
-
-    @MockBean
-    private ReadyConfig readyConfig;
-
-    @MockBean
-    private RestTemplate restTemplate;
-
-    @MockBean
-    private UserRepository userRepository;
-
-    @Autowired
-    private AuthenticationServiceGateway authenticationServiceGateway;
-
-    @Test
-    public void validate_succeeds() throws ServletException {
-        final ResponseEntity<UserDto> mock = ResponseEntity.status(HttpStatus.ACCEPTED)
-                .body(USER_1_DTO);
-
-        /* mock */
-        when(restTemplate.exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(UserDto.class)))
-                .thenReturn(mock);
-
-        /* test */
-        final UserDetails response = authenticationServiceGateway.validate(JWT_1);
-        assertNotNull(response);
-        assertEquals(USER_1_USERNAME, response.getUsername());
-        assertEquals(List.of(new SimpleGrantedAuthority("ROLE_RESEARCHER")), response.getAuthorities());
-    }
-
-    @Test
-    public void validate_notFound_fails() {
-        final ResponseEntity<UserDto> mock = ResponseEntity.status(HttpStatus.NOT_FOUND)
-                .body(USER_1_DTO);
-
-        /* mock */
-        when(restTemplate.exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(UserDto.class)))
-                .thenReturn(mock);
-
-        /* test */
-        assertThrows(ServletException.class, () -> {
-            authenticationServiceGateway.validate(JWT_1);
-        });
-    }
-
-}
diff --git a/fda-identifier-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java b/fda-identifier-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java
index 1b7fcb36ea..b2b01c42ee 100644
--- a/fda-identifier-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java
+++ b/fda-identifier-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java
@@ -1,8 +1,15 @@
 package at.tuwien.auth;
 
-import at.tuwien.gateway.AuthenticationServiceGateway;
+import at.tuwien.api.auth.RealmAccessDto;
+import at.tuwien.api.user.UserDetailsDto;
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.JWTVerifier;
+import com.auth0.jwt.algorithms.Algorithm;
+import com.auth0.jwt.interfaces.DecodedJWT;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
@@ -14,22 +21,30 @@ import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.stream.Collectors;
 
 @Slf4j
 public class AuthTokenFilter extends OncePerRequestFilter {
 
-    private final AuthenticationServiceGateway authenticationServiceGateway;
+    @Value("${fda.jwt.issuer}")
+    private String issuer;
 
-    public AuthTokenFilter(AuthenticationServiceGateway authenticationServiceGateway) {
-        this.authenticationServiceGateway = authenticationServiceGateway;
-    }
+    @Value("${fda.jwt.public_key}")
+    private String publicKey;
 
     @Override
     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
             throws ServletException, IOException {
         final String jwt = parseJwt(request);
         if (jwt != null) {
-            final UserDetails userDetails = authenticationServiceGateway.validate(jwt);
+            final UserDetails userDetails = verifyJwt(jwt);
             log.debug("authenticated user {}", userDetails);
             final UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                     userDetails, null, userDetails.getAuthorities());
@@ -40,17 +55,46 @@ public class AuthTokenFilter extends OncePerRequestFilter {
         filterChain.doFilter(request, response);
     }
 
+    public UserDetails verifyJwt(String token) throws ServletException {
+        final KeyFactory kf;
+        try {
+            kf = KeyFactory.getInstance("RSA");
+        } catch (NoSuchAlgorithmException e) {
+            log.error("Failed to find RSA algorithm");
+            throw new ServletException("Failed to find RSA algorithm", e);
+        }
+        final X509EncodedKeySpec keySpecX509 = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey));
+        final RSAPublicKey pubKey;
+        try {
+            pubKey = (RSAPublicKey) kf.generatePublic(keySpecX509);
+        } catch (InvalidKeySpecException e) {
+            log.error("Provided public key is invalid");
+            throw new ServletException("Provided public key is invalid", e);
+        }
+        final Algorithm algorithm = Algorithm.RSA256(pubKey, null);
+        JWTVerifier verifier = JWT.require(algorithm)
+                .withIssuer(issuer)
+                .withAudience("spring")
+                .build();
+        final DecodedJWT jwt = verifier.verify(token);
+        final RealmAccessDto realmAccess = jwt.getClaim("realm_access").as(RealmAccessDto.class);
+        return UserDetailsDto.builder()
+                .username(jwt.getClaim("client_id").asString())
+                .authorities(Arrays.stream(realmAccess.getRoles()).map(SimpleGrantedAuthority::new).collect(Collectors.toList()))
+                .build();
+    }
+
     /**
      * Parses the token from the HTTP header of the request
      *
      * @param request The request.
      * @return The token.
      */
-    String parseJwt(HttpServletRequest request) {
+    public String parseJwt(HttpServletRequest request) {
         String headerAuth = request.getHeader("Authorization");
         if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) {
             return headerAuth.substring(7, headerAuth.length());
         }
         return null;
     }
-}
+}
\ No newline at end of file
diff --git a/fda-identifier-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java b/fda-identifier-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java
index d665ebbbd4..04f19f375b 100644
--- a/fda-identifier-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java
+++ b/fda-identifier-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java
@@ -1,10 +1,8 @@
 package at.tuwien.config;
 
 import at.tuwien.auth.AuthTokenFilter;
-import at.tuwien.gateway.AuthenticationServiceGateway;
 import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
 import io.swagger.v3.oas.annotations.security.SecurityScheme;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.http.HttpMethod;
@@ -31,16 +29,9 @@ import javax.servlet.http.HttpServletResponse;
 )
 public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 
-    private final AuthenticationServiceGateway authenticationServiceGateway;
-
-    @Autowired
-    public WebSecurityConfig(AuthenticationServiceGateway authenticationServiceGateway) {
-        this.authenticationServiceGateway = authenticationServiceGateway;
-    }
-
     @Bean
     public AuthTokenFilter authTokenFilter() {
-        return new AuthTokenFilter(authenticationServiceGateway);
+        return new AuthTokenFilter();
     }
 
     @Override
diff --git a/fda-identifier-service/services/src/main/java/at/tuwien/gateway/AuthenticationServiceGateway.java b/fda-identifier-service/services/src/main/java/at/tuwien/gateway/AuthenticationServiceGateway.java
deleted file mode 100644
index 7eab6f1f8d..0000000000
--- a/fda-identifier-service/services/src/main/java/at/tuwien/gateway/AuthenticationServiceGateway.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package at.tuwien.gateway;
-
-import org.springframework.security.core.userdetails.UserDetails;
-
-import javax.servlet.ServletException;
-
-public interface AuthenticationServiceGateway {
-
-    /**
-     * Validates a token
-     *
-     * @param token The token
-     * @return User details on success
-     * @throws ServletException The token failed to validate at the Authentication Service.
-     */
-    UserDetails validate(String token) throws ServletException;
-}
diff --git a/fda-identifier-service/services/src/main/java/at/tuwien/gateway/impl/AuthenticationServiceGatewayImpl.java b/fda-identifier-service/services/src/main/java/at/tuwien/gateway/impl/AuthenticationServiceGatewayImpl.java
deleted file mode 100644
index 65a1b38867..0000000000
--- a/fda-identifier-service/services/src/main/java/at/tuwien/gateway/impl/AuthenticationServiceGatewayImpl.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package at.tuwien.gateway.impl;
-
-import at.tuwien.api.user.UserDto;
-import at.tuwien.gateway.AuthenticationServiceGateway;
-import at.tuwien.mapper.UserMapper;
-import lombok.extern.log4j.Log4j2;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.http.*;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.stereotype.Service;
-import org.springframework.web.client.HttpStatusCodeException;
-import org.springframework.web.client.RestTemplate;
-
-import javax.servlet.ServletException;
-
-@Log4j2
-@Service
-public class AuthenticationServiceGatewayImpl implements AuthenticationServiceGateway {
-
-    private final UserMapper userMapper;
-    private final RestTemplate restTemplate;
-
-    @Autowired
-    public AuthenticationServiceGatewayImpl(UserMapper userMapper, RestTemplate restTemplate) {
-        this.userMapper = userMapper;
-        this.restTemplate = restTemplate;
-    }
-
-    @Override
-    public UserDetails validate(String token) throws ServletException {
-        final HttpHeaders headers = new HttpHeaders();
-        headers.set("Authorization", "Bearer " + token);
-        try {
-            final ResponseEntity<UserDto> response = restTemplate.exchange("/api/auth", HttpMethod.PUT,
-                    new HttpEntity<>(null, headers), UserDto.class);
-            if (!response.getStatusCode().equals(HttpStatus.ACCEPTED)) {
-                log.error("Failed to validate token with status code {}", response.getStatusCode());
-                throw new ServletException("Failed to validate token");
-            }
-            final UserDetails dto = userMapper.userDtoToUserDetailsDto(response.getBody());
-            log.trace("gateway authenticated user {}", dto);
-            return dto;
-        } catch (HttpStatusCodeException e) {
-            log.error("Failed to validate token with status code {}", e.getStatusCode());
-            throw new ServletException("Failed to validate token", e);
-        }
-    }
-
-}
diff --git a/fda-query-service/Dockerfile b/fda-query-service/Dockerfile
index 7bcbda154f..90a92f23bd 100644
--- a/fda-query-service/Dockerfile
+++ b/fda-query-service/Dockerfile
@@ -31,6 +31,10 @@ ENV GATEWAY_ENDPOINT=http://gateway-service:9095
 ENV SHARED_FILESYSTEM=/tmp
 ENV BROKER_CONSUMERS=2
 ENV LOG_LEVEL=debug
+ENV CLIENT_SECRET="${DBREPO_CLIENT_SECRET:-client-secret}"
+ENV CLIENT_ID=dbrepo-client
+ENV JWT_ISSUER=http://localhost:8080/realms/dbrepo
+ENV JWT_PUBKEY=public-key
 ENV NOT_SUPPORTED_KEYWORDS=\\*,AVG,BIT_AND,BIT_OR,BIT_XOR,COUNT,COUNTDISTINCT,GROUP_CONCAT,JSON_ARRAYAGG,JSON_OBJECTAGG,MAX,MIN,STD,STDDEV,STDDEV_POP,STDDEV_SAMP,SUM,VARIANCE,VAR_POP,VAR_SAMP,--
 
 COPY ./service_ready /usr/bin
diff --git a/fda-query-service/pom.xml b/fda-query-service/pom.xml
index 9f83eafd6d..67fb838c96 100644
--- a/fda-query-service/pom.xml
+++ b/fda-query-service/pom.xml
@@ -51,6 +51,7 @@
         <opencsv.version>5.4</opencsv.version>
         <hibernate-c3po.version>5.6.3.Final</hibernate-c3po.version>
         <maven-report.version>3.0.0</maven-report.version>
+        <jwt.version>4.3.0</jwt.version>
     </properties>
 
     <dependencies>
@@ -96,6 +97,12 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-actuator</artifactId>
         </dependency>
+        <!-- Authentication -->
+        <dependency>
+            <groupId>com.auth0</groupId>
+            <artifactId>java-jwt</artifactId>
+            <version>${jwt.version}</version>
+        </dependency>
         <!-- Monitoring -->
         <dependency>
             <groupId>io.micrometer</groupId>
diff --git a/fda-query-service/rest-service/src/main/resources/application-docker.yml b/fda-query-service/rest-service/src/main/resources/application-docker.yml
index 4eb5d1238b..0010b04b70 100644
--- a/fda-query-service/rest-service/src/main/resources/application-docker.yml
+++ b/fda-query-service/rest-service/src/main/resources/application-docker.yml
@@ -40,6 +40,11 @@ eureka:
 fda:
   gateway.endpoint: "${GATEWAY_ENDPOINT}"
   ready.path: /ready
+  jwt:
+    issuer: "${JWT_ISSUER}"
+    public_key: "${JWT_PUBKEY}"
+  client_secret: "${CLIENT_SECRET}"
+  client_id: "${CLIENT_ID}"
   consumers: 2
   unsupported: "${NOT_SUPPORTED_KEYWORDS}"
   elastic:
diff --git a/fda-query-service/rest-service/src/main/resources/application-local.yml b/fda-query-service/rest-service/src/main/resources/application-local.yml
index 9451262fbd..2efc331ed9 100644
--- a/fda-query-service/rest-service/src/main/resources/application-local.yml
+++ b/fda-query-service/rest-service/src/main/resources/application-local.yml
@@ -40,6 +40,11 @@ eureka:
 fda:
   gateway.endpoint: http://localhost:9095
   ready.path: ./ready
+  jwt:
+    issuer: http://localhost:8080/realms/dbrepo
+    public_key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqqnHQ2BWWW9vDNLRCcxD++xZg/16oqMo/c1l+lcFEjjAIJjJp/HqrPYU/U9GvquGE6PbVFtTzW1KcKawOW+FJNOA3CGo8Q1TFEfz43B8rZpKsFbJKvQGVv1Z4HaKPvLUm7iMm8Hv91cLduuoWx6Q3DPe2vg13GKKEZe7UFghF+0T9u8EKzA/XqQ0OiICmsmYPbwvf9N3bCKsB/Y10EYmZRb8IhCoV9mmO5TxgWgiuNeCTtNCv2ePYqL/U0WvyGFW0reasIK8eg3KrAUj8DpyOgPOVBn3lBGf+3KFSYi+0bwZbJZWqbC/Xlk20Go1YfeJPRIt7ImxD27R/lNjgDO/MwIDAQAB
+  client_secret: client-secret
+  client_id: dbrepo-client
   consumers: 2
   unsupported: \*,AVG,BIT_AND,BIT_OR,BIT_XOR,COUNT,COUNTDISTINCT,GROUP_CONCAT,JSON_ARRAYAGG,JSON_OBJECTAGG,MAX,MIN,STD,STDDEV,STDDEV_POP,STDDEV_SAMP,SUM,VARIANCE,VAR_POP,VAR_SAMP,--
   elastic:
diff --git a/fda-query-service/rest-service/src/main/resources/application.yml b/fda-query-service/rest-service/src/main/resources/application.yml
index 3821bd12af..f773caffc0 100644
--- a/fda-query-service/rest-service/src/main/resources/application.yml
+++ b/fda-query-service/rest-service/src/main/resources/application.yml
@@ -40,6 +40,11 @@ eureka:
 fda:
   gateway.endpoint: "${GATEWAY_ENDPOINT}"
   ready.path: /ready
+  jwt:
+    issuer: "${JWT_ISSUER}"
+    public_key: "${JWT_PUBKEY}"
+  client_secret: "${CLIENT_SECRET}"
+  client_id: "${CLIENT_ID}"
   consumers: "${BROKER_CONSUMERS}"
   unsupported: "${NOT_SUPPORTED_KEYWORDS}"
   elastic:
diff --git a/fda-query-service/rest-service/src/test/java/at/tuwien/auth/AuthTokenFilterTest.java b/fda-query-service/rest-service/src/test/java/at/tuwien/auth/AuthTokenFilterTest.java
index bf3df721ba..8b0d8be0ce 100644
--- a/fda-query-service/rest-service/src/test/java/at/tuwien/auth/AuthTokenFilterTest.java
+++ b/fda-query-service/rest-service/src/test/java/at/tuwien/auth/AuthTokenFilterTest.java
@@ -4,7 +4,6 @@ import at.tuwien.BaseUnitTest;
 import at.tuwien.config.H2Utils;
 import at.tuwien.config.IndexConfig;
 import at.tuwien.config.ReadyConfig;
-import at.tuwien.gateway.AuthenticationServiceGateway;
 import at.tuwien.repository.jpa.UserRepository;
 import com.rabbitmq.client.Channel;
 import lombok.extern.log4j.Log4j2;
@@ -45,9 +44,6 @@ public class AuthTokenFilterTest extends BaseUnitTest {
     @MockBean
     private UserRepository userRepository;
 
-    @MockBean
-    private AuthenticationServiceGateway authenticationServiceGateway;
-
     @Autowired
     private AuthTokenFilter authTokenFilter;
 
@@ -62,9 +58,6 @@ public class AuthTokenFilterTest extends BaseUnitTest {
         final FilterChain chain = new MockFilterChain();
 
         /* mock */
-        doThrow(new ServletException("Username not found"))
-                .when(authenticationServiceGateway)
-                .validate(anyString());
         when(userRepository.findByUsername("mweise"))
                 .thenReturn(Optional.empty());
 
@@ -82,8 +75,6 @@ public class AuthTokenFilterTest extends BaseUnitTest {
         final FilterChain chain = new MockFilterChain();
 
         /* mock */
-        when(authenticationServiceGateway.validate(anyString()))
-                .thenReturn(USER_1_DETAILS);
         when(userRepository.findByUsername("mweise"))
                 .thenReturn(Optional.of(USER_1));
 
diff --git a/fda-query-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java b/fda-query-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java
index 1b7fcb36ea..b2b01c42ee 100644
--- a/fda-query-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java
+++ b/fda-query-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java
@@ -1,8 +1,15 @@
 package at.tuwien.auth;
 
-import at.tuwien.gateway.AuthenticationServiceGateway;
+import at.tuwien.api.auth.RealmAccessDto;
+import at.tuwien.api.user.UserDetailsDto;
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.JWTVerifier;
+import com.auth0.jwt.algorithms.Algorithm;
+import com.auth0.jwt.interfaces.DecodedJWT;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
@@ -14,22 +21,30 @@ import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.stream.Collectors;
 
 @Slf4j
 public class AuthTokenFilter extends OncePerRequestFilter {
 
-    private final AuthenticationServiceGateway authenticationServiceGateway;
+    @Value("${fda.jwt.issuer}")
+    private String issuer;
 
-    public AuthTokenFilter(AuthenticationServiceGateway authenticationServiceGateway) {
-        this.authenticationServiceGateway = authenticationServiceGateway;
-    }
+    @Value("${fda.jwt.public_key}")
+    private String publicKey;
 
     @Override
     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
             throws ServletException, IOException {
         final String jwt = parseJwt(request);
         if (jwt != null) {
-            final UserDetails userDetails = authenticationServiceGateway.validate(jwt);
+            final UserDetails userDetails = verifyJwt(jwt);
             log.debug("authenticated user {}", userDetails);
             final UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                     userDetails, null, userDetails.getAuthorities());
@@ -40,17 +55,46 @@ public class AuthTokenFilter extends OncePerRequestFilter {
         filterChain.doFilter(request, response);
     }
 
+    public UserDetails verifyJwt(String token) throws ServletException {
+        final KeyFactory kf;
+        try {
+            kf = KeyFactory.getInstance("RSA");
+        } catch (NoSuchAlgorithmException e) {
+            log.error("Failed to find RSA algorithm");
+            throw new ServletException("Failed to find RSA algorithm", e);
+        }
+        final X509EncodedKeySpec keySpecX509 = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey));
+        final RSAPublicKey pubKey;
+        try {
+            pubKey = (RSAPublicKey) kf.generatePublic(keySpecX509);
+        } catch (InvalidKeySpecException e) {
+            log.error("Provided public key is invalid");
+            throw new ServletException("Provided public key is invalid", e);
+        }
+        final Algorithm algorithm = Algorithm.RSA256(pubKey, null);
+        JWTVerifier verifier = JWT.require(algorithm)
+                .withIssuer(issuer)
+                .withAudience("spring")
+                .build();
+        final DecodedJWT jwt = verifier.verify(token);
+        final RealmAccessDto realmAccess = jwt.getClaim("realm_access").as(RealmAccessDto.class);
+        return UserDetailsDto.builder()
+                .username(jwt.getClaim("client_id").asString())
+                .authorities(Arrays.stream(realmAccess.getRoles()).map(SimpleGrantedAuthority::new).collect(Collectors.toList()))
+                .build();
+    }
+
     /**
      * Parses the token from the HTTP header of the request
      *
      * @param request The request.
      * @return The token.
      */
-    String parseJwt(HttpServletRequest request) {
+    public String parseJwt(HttpServletRequest request) {
         String headerAuth = request.getHeader("Authorization");
         if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) {
             return headerAuth.substring(7, headerAuth.length());
         }
         return null;
     }
-}
+}
\ No newline at end of file
diff --git a/fda-query-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java b/fda-query-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java
index 47807018bc..22586fb23b 100644
--- a/fda-query-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java
+++ b/fda-query-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java
@@ -1,10 +1,8 @@
 package at.tuwien.config;
 
 import at.tuwien.auth.AuthTokenFilter;
-import at.tuwien.gateway.AuthenticationServiceGateway;
 import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
 import io.swagger.v3.oas.annotations.security.SecurityScheme;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.http.HttpMethod;
@@ -31,16 +29,9 @@ import javax.servlet.http.HttpServletResponse;
 )
 public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 
-    private final AuthenticationServiceGateway authenticationServiceGateway;
-
-    @Autowired
-    public WebSecurityConfig(AuthenticationServiceGateway authenticationServiceGateway) {
-        this.authenticationServiceGateway = authenticationServiceGateway;
-    }
-
     @Bean
     public AuthTokenFilter authTokenFilter() {
-        return new AuthTokenFilter(authenticationServiceGateway);
+        return new AuthTokenFilter();
     }
 
     @Override
diff --git a/fda-query-service/services/src/main/java/at/tuwien/gateway/AuthenticationServiceGateway.java b/fda-query-service/services/src/main/java/at/tuwien/gateway/AuthenticationServiceGateway.java
deleted file mode 100644
index 323608b39f..0000000000
--- a/fda-query-service/services/src/main/java/at/tuwien/gateway/AuthenticationServiceGateway.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package at.tuwien.gateway;
-
-import org.springframework.security.core.userdetails.UserDetails;
-
-import javax.servlet.ServletException;
-
-public interface AuthenticationServiceGateway {
-
-    /**
-     * Validates a token
-     *
-     * @param token The token
-     * @return User details on success
-     */
-    UserDetails validate(String token) throws ServletException;
-}
diff --git a/fda-query-service/services/src/main/java/at/tuwien/gateway/impl/AuthenticationServiceGatewayImpl.java b/fda-query-service/services/src/main/java/at/tuwien/gateway/impl/AuthenticationServiceGatewayImpl.java
deleted file mode 100644
index 2a922bc209..0000000000
--- a/fda-query-service/services/src/main/java/at/tuwien/gateway/impl/AuthenticationServiceGatewayImpl.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package at.tuwien.gateway.impl;
-
-import at.tuwien.api.user.UserDto;
-import at.tuwien.gateway.AuthenticationServiceGateway;
-import at.tuwien.mapper.UserMapper;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.http.*;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.stereotype.Service;
-import org.springframework.web.client.HttpStatusCodeException;
-import org.springframework.web.client.RestTemplate;
-
-import javax.servlet.ServletException;
-
-@Slf4j
-@Service
-public class AuthenticationServiceGatewayImpl implements AuthenticationServiceGateway {
-
-    private final UserMapper userMapper;
-    private final RestTemplate restTemplate;
-
-    @Autowired
-    public AuthenticationServiceGatewayImpl(UserMapper userMapper, RestTemplate restTemplate) {
-        this.userMapper = userMapper;
-        this.restTemplate = restTemplate;
-    }
-
-    @Override
-    public UserDetails validate(String token) throws ServletException {
-        final HttpHeaders headers = new HttpHeaders();
-        headers.set("Authorization", "Bearer " + token);
-        try {
-            final ResponseEntity<UserDto> response = restTemplate.exchange("/api/auth", HttpMethod.PUT,
-                    new HttpEntity<>(null, headers), UserDto.class);
-            if (!response.getStatusCode().equals(HttpStatus.ACCEPTED)) {
-                log.error("Failed to validate token with status code {}", response.getStatusCode());
-                throw new ServletException("Failed to validate token");
-            }
-            final UserDetails dto = userMapper.userDtoToUserDetailsDto(response.getBody());
-            log.trace("gateway authenticated user {}", dto);
-            return dto;
-        } catch (HttpStatusCodeException e) {
-            log.error("Failed to validate token with status code {}", e.getStatusCode());
-            throw new ServletException("Failed to validate token", e);
-        }
-    }
-
-}
diff --git a/fda-table-service/Dockerfile b/fda-table-service/Dockerfile
index 4752a4a140..cac8dff13d 100644
--- a/fda-table-service/Dockerfile
+++ b/fda-table-service/Dockerfile
@@ -29,6 +29,10 @@ ENV BROKER_PASSWORD=fda
 ENV SHARED_FILESYSTEM=/tmp
 ENV USER_NETWORK=userdb
 ENV LOG_LEVEL=debug
+ENV CLIENT_SECRET="${DBREPO_CLIENT_SECRET:-client-secret}"
+ENV CLIENT_ID=dbrepo-client
+ENV JWT_ISSUER=http://localhost:8080/realms/dbrepo
+ENV JWT_PUBKEY=public-key
 
 COPY ./service_ready /usr/bin
 RUN chmod +x /usr/bin/service_ready
diff --git a/fda-table-service/pom.xml b/fda-table-service/pom.xml
index e9faf0cbb3..aa490cf812 100644
--- a/fda-table-service/pom.xml
+++ b/fda-table-service/pom.xml
@@ -34,6 +34,7 @@
         <random-utils.version>2.0.0</random-utils.version>
         <hibernate-c3po.version>5.6.3.Final</hibernate-c3po.version>
         <jsql.version>3.1</jsql.version>
+        <jwt.version>4.3.0</jwt.version>
     </properties>
 
     <dependencies>
@@ -67,6 +68,12 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-actuator</artifactId>
         </dependency>
+        <!-- Authentication -->
+        <dependency>
+            <groupId>com.auth0</groupId>
+            <artifactId>java-jwt</artifactId>
+            <version>${jwt.version}</version>
+        </dependency>
         <!-- Monitoring -->
         <dependency>
             <groupId>io.micrometer</groupId>
diff --git a/fda-table-service/rest-service/src/main/resources/application-docker.yml b/fda-table-service/rest-service/src/main/resources/application-docker.yml
index 1a03468a50..7e54984965 100644
--- a/fda-table-service/rest-service/src/main/resources/application-docker.yml
+++ b/fda-table-service/rest-service/src/main/resources/application-docker.yml
@@ -39,6 +39,11 @@ eureka:
   client.serviceUrl.defaultZone: http://discovery-service:9090/eureka/
 fda:
   ready.path: /ready
+  jwt:
+    issuer: "${JWT_ISSUER}"
+    public_key: "${JWT_PUBKEY}"
+  client_secret: "${CLIENT_SECRET}"
+  client_id: "${CLIENT_ID}"
   gateway.endpoint: "${GATEWAY_ENDPOINT}"
   elastic:
     endpoint: search-service:9200
diff --git a/fda-table-service/rest-service/src/main/resources/application-local.yml b/fda-table-service/rest-service/src/main/resources/application-local.yml
index 1852e935c8..c0fd28220a 100644
--- a/fda-table-service/rest-service/src/main/resources/application-local.yml
+++ b/fda-table-service/rest-service/src/main/resources/application-local.yml
@@ -39,6 +39,11 @@ eureka:
   client.serviceUrl.defaultZone: http://localhost:9090/eureka/
 fda:
   ready.path: ./ready
+  jwt:
+    issuer: http://localhost:8080/realms/dbrepo
+    public_key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqqnHQ2BWWW9vDNLRCcxD++xZg/16oqMo/c1l+lcFEjjAIJjJp/HqrPYU/U9GvquGE6PbVFtTzW1KcKawOW+FJNOA3CGo8Q1TFEfz43B8rZpKsFbJKvQGVv1Z4HaKPvLUm7iMm8Hv91cLduuoWx6Q3DPe2vg13GKKEZe7UFghF+0T9u8EKzA/XqQ0OiICmsmYPbwvf9N3bCKsB/Y10EYmZRb8IhCoV9mmO5TxgWgiuNeCTtNCv2ePYqL/U0WvyGFW0reasIK8eg3KrAUj8DpyOgPOVBn3lBGf+3KFSYi+0bwZbJZWqbC/Xlk20Go1YfeJPRIt7ImxD27R/lNjgDO/MwIDAQAB
+  client_secret: client-secret
+  client_id: dbrepo-client
   gateway.endpoint: http://localhost:9095
   elastic:
     endpoint: search-service:9200
diff --git a/fda-table-service/rest-service/src/main/resources/application.yml b/fda-table-service/rest-service/src/main/resources/application.yml
index c0853c68f4..0197bd442b 100644
--- a/fda-table-service/rest-service/src/main/resources/application.yml
+++ b/fda-table-service/rest-service/src/main/resources/application.yml
@@ -39,6 +39,11 @@ eureka:
   client.serviceUrl.defaultZone: http://discovery-service:9090/eureka/
 fda:
   ready.path: ./ready
+  jwt:
+    issuer: "${JWT_ISSUER}"
+    public_key: "${JWT_PUBKEY}"
+  client_secret: "${CLIENT_SECRET}"
+  client_id: "${CLIENT_ID}"
   gateway.endpoint: http://gateway-service:9095
   elastic:
     endpoint: search-service:9200
diff --git a/fda-table-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java b/fda-table-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java
index 12854321a8..b2b01c42ee 100644
--- a/fda-table-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java
+++ b/fda-table-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java
@@ -1,8 +1,15 @@
 package at.tuwien.auth;
 
-import at.tuwien.gateway.AuthenticationServiceGateway;
+import at.tuwien.api.auth.RealmAccessDto;
+import at.tuwien.api.user.UserDetailsDto;
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.JWTVerifier;
+import com.auth0.jwt.algorithms.Algorithm;
+import com.auth0.jwt.interfaces.DecodedJWT;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
@@ -14,22 +21,30 @@ import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.stream.Collectors;
 
 @Slf4j
 public class AuthTokenFilter extends OncePerRequestFilter {
 
-    private final AuthenticationServiceGateway authenticationServiceGateway;
+    @Value("${fda.jwt.issuer}")
+    private String issuer;
 
-    public AuthTokenFilter(AuthenticationServiceGateway authenticationServiceGateway) {
-        this.authenticationServiceGateway = authenticationServiceGateway;
-    }
+    @Value("${fda.jwt.public_key}")
+    private String publicKey;
 
     @Override
     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
             throws ServletException, IOException {
         final String jwt = parseJwt(request);
         if (jwt != null) {
-            final UserDetails userDetails = authenticationServiceGateway.validate(jwt);
+            final UserDetails userDetails = verifyJwt(jwt);
             log.debug("authenticated user {}", userDetails);
             final UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                     userDetails, null, userDetails.getAuthorities());
@@ -40,13 +55,42 @@ public class AuthTokenFilter extends OncePerRequestFilter {
         filterChain.doFilter(request, response);
     }
 
+    public UserDetails verifyJwt(String token) throws ServletException {
+        final KeyFactory kf;
+        try {
+            kf = KeyFactory.getInstance("RSA");
+        } catch (NoSuchAlgorithmException e) {
+            log.error("Failed to find RSA algorithm");
+            throw new ServletException("Failed to find RSA algorithm", e);
+        }
+        final X509EncodedKeySpec keySpecX509 = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey));
+        final RSAPublicKey pubKey;
+        try {
+            pubKey = (RSAPublicKey) kf.generatePublic(keySpecX509);
+        } catch (InvalidKeySpecException e) {
+            log.error("Provided public key is invalid");
+            throw new ServletException("Provided public key is invalid", e);
+        }
+        final Algorithm algorithm = Algorithm.RSA256(pubKey, null);
+        JWTVerifier verifier = JWT.require(algorithm)
+                .withIssuer(issuer)
+                .withAudience("spring")
+                .build();
+        final DecodedJWT jwt = verifier.verify(token);
+        final RealmAccessDto realmAccess = jwt.getClaim("realm_access").as(RealmAccessDto.class);
+        return UserDetailsDto.builder()
+                .username(jwt.getClaim("client_id").asString())
+                .authorities(Arrays.stream(realmAccess.getRoles()).map(SimpleGrantedAuthority::new).collect(Collectors.toList()))
+                .build();
+    }
+
     /**
      * Parses the token from the HTTP header of the request
      *
      * @param request The request.
      * @return The token.
      */
-    private String parseJwt(HttpServletRequest request) {
+    public String parseJwt(HttpServletRequest request) {
         String headerAuth = request.getHeader("Authorization");
         if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) {
             return headerAuth.substring(7, headerAuth.length());
diff --git a/fda-table-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java b/fda-table-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java
index 1f16894912..be46fcc18b 100644
--- a/fda-table-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java
+++ b/fda-table-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java
@@ -1,10 +1,8 @@
 package at.tuwien.config;
 
 import at.tuwien.auth.AuthTokenFilter;
-import at.tuwien.gateway.AuthenticationServiceGateway;
 import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
 import io.swagger.v3.oas.annotations.security.SecurityScheme;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.http.HttpMethod;
@@ -31,16 +29,9 @@ import javax.servlet.http.HttpServletResponse;
 )
 public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 
-    private final AuthenticationServiceGateway authenticationServiceGateway;
-
-    @Autowired
-    public WebSecurityConfig(AuthenticationServiceGateway authenticationServiceGateway) {
-        this.authenticationServiceGateway = authenticationServiceGateway;
-    }
-
     @Bean
     public AuthTokenFilter authTokenFilter() {
-        return new AuthTokenFilter(authenticationServiceGateway);
+        return new AuthTokenFilter();
     }
 
     @Override
diff --git a/fda-table-service/services/src/main/java/at/tuwien/gateway/AuthenticationServiceGateway.java b/fda-table-service/services/src/main/java/at/tuwien/gateway/AuthenticationServiceGateway.java
deleted file mode 100644
index fc47ce246d..0000000000
--- a/fda-table-service/services/src/main/java/at/tuwien/gateway/AuthenticationServiceGateway.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package at.tuwien.gateway;
-
-import org.springframework.security.core.userdetails.UserDetails;
-
-public interface AuthenticationServiceGateway {
-
-    /**
-     * Validates a token
-     *
-     * @param token The token
-     * @return User details on success
-     */
-    UserDetails validate(String token);
-}
diff --git a/fda-table-service/services/src/main/java/at/tuwien/gateway/impl/AuthenticationServiceGatewayImpl.java b/fda-table-service/services/src/main/java/at/tuwien/gateway/impl/AuthenticationServiceGatewayImpl.java
deleted file mode 100644
index 9b94b6b4f4..0000000000
--- a/fda-table-service/services/src/main/java/at/tuwien/gateway/impl/AuthenticationServiceGatewayImpl.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package at.tuwien.gateway.impl;
-
-import at.tuwien.api.user.UserDto;
-import at.tuwien.gateway.AuthenticationServiceGateway;
-import at.tuwien.mapper.UserMapper;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.http.HttpEntity;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.HttpMethod;
-import org.springframework.http.ResponseEntity;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.stereotype.Service;
-import org.springframework.web.client.RestTemplate;
-
-@Slf4j
-@Service
-public class AuthenticationServiceGatewayImpl implements AuthenticationServiceGateway {
-
-    private final UserMapper userMapper;
-    private final RestTemplate restTemplate;
-
-    @Autowired
-    public AuthenticationServiceGatewayImpl(UserMapper userMapper, RestTemplate restTemplate) {
-        this.userMapper = userMapper;
-        this.restTemplate = restTemplate;
-    }
-
-    @Override
-    public UserDetails validate(String token) {
-        final HttpHeaders headers = new HttpHeaders();
-        headers.set("Authorization", "Bearer " + token);
-        final ResponseEntity<UserDto> response = restTemplate.exchange("/api/auth", HttpMethod.PUT,
-                new HttpEntity<>(null, headers), UserDto.class);
-        return userMapper.userDtoToUserDetailsDto(response.getBody());
-    }
-
-}
-- 
GitLab