You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@streampipes.apache.org by ri...@apache.org on 2022/04/24 12:58:53 UTC

[incubator-streampipes] branch dev updated: [STREAMPIPES-535] Support asymmetric JWT signatures

This is an automated email from the ASF dual-hosted git repository.

riemer pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/incubator-streampipes.git


The following commit(s) were added to refs/heads/dev by this push:
     new 66dab7c2f [STREAMPIPES-535] Support asymmetric JWT signatures
66dab7c2f is described below

commit 66dab7c2f8f9fdb0d72dfb405e2c71ecfb07aac7
Author: Dominik Riemer <do...@gmail.com>
AuthorDate: Sun Apr 24 14:58:43 2022 +0200

    [STREAMPIPES-535] Support asymmetric JWT signatures
---
 .../backend/StreamPipesBackendApplication.java     |  2 +
 .../streampipes/backend/StreamPipesEnvChecker.java | 83 +++++++++++++++++++
 streampipes-backend/src/main/resources/banner.txt  |  2 +-
 .../credentials/StreamPipesTokenCredentials.java   |  4 +-
 .../apache/streampipes/commons/constants/Envs.java |  3 +
 .../streampipes/config/backend/BackendConfig.java  |  4 +
 .../config/backend/model/JwtSigningMode.java       |  6 ++
 .../config/backend/model/LocalAuthConfig.java      | 38 +++++++++
 .../impl/admin/GeneralConfigurationResource.java   | 36 +++++++++
 .../security/jwt/JwtTokenGenerator.java            | 94 ++++++++++++++++++++++
 .../streampipes/security/jwt/JwtTokenUtils.java    | 77 ++----------------
 .../security/jwt/JwtTokenValidator.java            | 40 +++++++++
 .../streampipes/security/jwt/KeyGenerator.java     | 77 ++++++++++++++++++
 .../apache/streampipes/security/jwt/KeyUtils.java  | 42 ++++++++++
 .../user/management/jwt/JwtTokenProvider.java      | 32 +++++++-
 .../user/management/jwt/SpKeyResolver.java         | 18 +++--
 ui/src/app/configuration/configuration.module.ts   | 88 ++++++++++----------
 .../authentication-configuration.component.html    | 26 ++++++
 .../authentication-configuration.component.scss    | 17 ++++
 .../authentication-configuration.component.ts      | 52 ++++++++++++
 .../security-configuration.component.html          |  7 ++
 .../configuration/shared/configuration.service.ts  |  9 ++-
 ui/src/app/configuration/shared/multipart-utils.ts | 39 +++++++++
 23 files changed, 668 insertions(+), 128 deletions(-)

diff --git a/streampipes-backend/src/main/java/org/apache/streampipes/backend/StreamPipesBackendApplication.java b/streampipes-backend/src/main/java/org/apache/streampipes/backend/StreamPipesBackendApplication.java
index b71101140..6b0fe4f45 100644
--- a/streampipes-backend/src/main/java/org/apache/streampipes/backend/StreamPipesBackendApplication.java
+++ b/streampipes-backend/src/main/java/org/apache/streampipes/backend/StreamPipesBackendApplication.java
@@ -100,6 +100,8 @@ public class StreamPipesBackendApplication extends StreamPipesServiceBase {
       doInitialSetup();
     }
 
+    new StreamPipesEnvChecker().updateEnvironmentVariables();
+
     executorService.schedule(this::startAllPreviouslyStoppedPipelines, 5, TimeUnit.SECONDS);
     LOG.info("Pipeline health check will run every {} seconds", HEALTH_CHECK_INTERVAL);
     healthCheckExecutorService.scheduleAtFixedRate(new PipelineHealthCheck(),
diff --git a/streampipes-backend/src/main/java/org/apache/streampipes/backend/StreamPipesEnvChecker.java b/streampipes-backend/src/main/java/org/apache/streampipes/backend/StreamPipesEnvChecker.java
new file mode 100644
index 000000000..db62d1a05
--- /dev/null
+++ b/streampipes-backend/src/main/java/org/apache/streampipes/backend/StreamPipesEnvChecker.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.backend;
+
+import org.apache.streampipes.commons.constants.Envs;
+import org.apache.streampipes.config.backend.BackendConfig;
+import org.apache.streampipes.config.backend.model.JwtSigningMode;
+import org.apache.streampipes.config.backend.model.LocalAuthConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+public class StreamPipesEnvChecker {
+
+  private static final Logger LOG = LoggerFactory.getLogger(StreamPipesEnvChecker.class);
+
+  BackendConfig coreConfig;
+
+  public void updateEnvironmentVariables() {
+    this.coreConfig = BackendConfig.INSTANCE;
+
+    LOG.info("Checking and updating environment variables...");
+    updateJwtSettings();
+  }
+
+  private void updateJwtSettings() {
+    LocalAuthConfig localAuthConfig = coreConfig.getLocalAuthConfig();
+    boolean incompleteConfig = false;
+    if (Envs.SP_JWT_SIGNING_MODE.exists()) {
+      localAuthConfig.setJwtSigningMode(JwtSigningMode.valueOf(Envs.SP_JWT_SIGNING_MODE.getValue()));
+    }
+    if (Envs.SP_JWT_SECRET.exists()) {
+      localAuthConfig.setTokenSecret(Envs.SP_JWT_SECRET.getValue());
+    }
+    if (Envs.SP_JWT_PUBLIC_KEY_LOC.exists()) {
+      try {
+        localAuthConfig.setPublicKey(readPublicKey(Envs.SP_JWT_PUBLIC_KEY_LOC.getValue()));
+      } catch (IOException e) {
+        incompleteConfig = true;
+        LOG.warn("Could not read public key at location " + Envs.SP_JWT_PUBLIC_KEY_LOC);
+      }
+    }
+
+    if (!Envs.SP_JWT_SIGNING_MODE.exists()) {
+      LOG.info("No JWT signing mode provided (using default settings), consult the docs to learn how to provide JWT settings");
+    } else if (localAuthConfig.getJwtSigningMode() == JwtSigningMode.HMAC && !Envs.SP_JWT_SECRET.exists()) {
+      LOG.warn("JWT signing mode set to HMAC but no secret provided (falling back to auto-generated secret), provide a {} variable",
+              Envs.SP_JWT_SECRET.getEnvVariableName());
+    } else if (localAuthConfig.getJwtSigningMode() == JwtSigningMode.RSA &&
+            ((!Envs.SP_JWT_PUBLIC_KEY_LOC.exists() || !Envs.SP_JWT_PRIVATE_KEY_LOC.exists()) || incompleteConfig)) {
+      LOG.warn("JWT signing mode set to RSA but no public or private key location provided, do you provide {} and {} variables?",
+              Envs.SP_JWT_PRIVATE_KEY_LOC.getEnvVariableName(),
+              Envs.SP_JWT_PUBLIC_KEY_LOC.getEnvVariableName());
+    }
+    if (!incompleteConfig) {
+      LOG.info("Updating local auth config with signing mode {}", localAuthConfig.getJwtSigningMode().name());
+      coreConfig.updateLocalAuthConfig(localAuthConfig);
+    }
+  }
+
+  private String readPublicKey(String publicKeyLocation) throws IOException {
+    return Files.readString(Paths.get(publicKeyLocation));
+  }
+}
diff --git a/streampipes-backend/src/main/resources/banner.txt b/streampipes-backend/src/main/resources/banner.txt
index 9ed3ea518..63d07b79d 100644
--- a/streampipes-backend/src/main/resources/banner.txt
+++ b/streampipes-backend/src/main/resources/banner.txt
@@ -3,4 +3,4 @@
 |__     |   _|   _|  -__|  _  |        ||    __/  ||  _  |  -__|__ --|
 |_______|____|__| |_____|___._|__|__|__||___|  |__||   __|_____|_____|
                                                    |__|
-** StreamPipes Pipeline Management **
+** StreamPipes Core **
diff --git a/streampipes-client/src/main/java/org/apache/streampipes/client/credentials/StreamPipesTokenCredentials.java b/streampipes-client/src/main/java/org/apache/streampipes/client/credentials/StreamPipesTokenCredentials.java
index b6aae0201..dd65a53df 100644
--- a/streampipes-client/src/main/java/org/apache/streampipes/client/credentials/StreamPipesTokenCredentials.java
+++ b/streampipes-client/src/main/java/org/apache/streampipes/client/credentials/StreamPipesTokenCredentials.java
@@ -19,7 +19,7 @@ package org.apache.streampipes.client.credentials;
 
 import org.apache.http.Header;
 import org.apache.streampipes.client.http.header.Headers;
-import org.apache.streampipes.security.jwt.JwtTokenUtils;
+import org.apache.streampipes.security.jwt.JwtTokenGenerator;
 
 import java.util.Collections;
 import java.util.Date;
@@ -46,7 +46,7 @@ public class StreamPipesTokenCredentials implements CredentialsProvider {
   }
 
   private String makeJwtToken() {
-    return JwtTokenUtils.makeJwtToken(username, tokenSecret, makeExpirationDate());
+    return JwtTokenGenerator.makeJwtToken(username, tokenSecret, makeExpirationDate());
   }
 
   private Date makeExpirationDate() {
diff --git a/streampipes-commons/src/main/java/org/apache/streampipes/commons/constants/Envs.java b/streampipes-commons/src/main/java/org/apache/streampipes/commons/constants/Envs.java
index 92aeed138..954d3d953 100644
--- a/streampipes-commons/src/main/java/org/apache/streampipes/commons/constants/Envs.java
+++ b/streampipes-commons/src/main/java/org/apache/streampipes/commons/constants/Envs.java
@@ -24,6 +24,9 @@ public enum Envs {
   SP_CONSUL_LOCATION("CONSUL_LOCATION"),
   SP_KAFKA_RETENTION_MS("SP_KAFKA_RETENTION_MS"),
   SP_JWT_SECRET("JWT_SECRET"),
+  SP_JWT_SIGNING_MODE("SP_JWT_SIGNING_MODE"),
+  SP_JWT_PRIVATE_KEY_LOC("SP_JWT_PRIVATE_KEY_LOC"),
+  SP_JWT_PUBLIC_KEY_LOC("SP_JWT_PUBLIC_KEY_LOC"),
   SP_INITIAL_ADMIN_EMAIL("SP_INITIAL_ADMIN_EMAIL"),
   SP_INITIAL_ADMIN_PASSWORD("SP_INITIAL_ADMIN_PASSWORD"),
   SP_INITIAL_SERVICE_USER("SP_INITIAL_SERVICE_USER"),
diff --git a/streampipes-config/src/main/java/org/apache/streampipes/config/backend/BackendConfig.java b/streampipes-config/src/main/java/org/apache/streampipes/config/backend/BackendConfig.java
index eef333e60..403ac4d66 100644
--- a/streampipes-config/src/main/java/org/apache/streampipes/config/backend/BackendConfig.java
+++ b/streampipes-config/src/main/java/org/apache/streampipes/config/backend/BackendConfig.java
@@ -253,6 +253,10 @@ public enum BackendConfig {
     config.setObject(BackendConfigKeys.GENERAL_CONFIG, generalConfig);
   }
 
+  public void updateLocalAuthConfig(LocalAuthConfig authConfig) {
+    config.setObject(BackendConfigKeys.LOCAL_AUTH_CONFIG, authConfig);
+  }
+
   public boolean isSetupRunning() {
     return config.getBoolean(BackendConfigKeys.IS_SETUP_RUNNING);
   }
diff --git a/streampipes-config/src/main/java/org/apache/streampipes/config/backend/model/JwtSigningMode.java b/streampipes-config/src/main/java/org/apache/streampipes/config/backend/model/JwtSigningMode.java
new file mode 100644
index 000000000..11a6df06c
--- /dev/null
+++ b/streampipes-config/src/main/java/org/apache/streampipes/config/backend/model/JwtSigningMode.java
@@ -0,0 +1,6 @@
+package org.apache.streampipes.config.backend.model;
+
+public enum JwtSigningMode {
+  HMAC,
+  RSA
+}
diff --git a/streampipes-config/src/main/java/org/apache/streampipes/config/backend/model/LocalAuthConfig.java b/streampipes-config/src/main/java/org/apache/streampipes/config/backend/model/LocalAuthConfig.java
index 03252331e..044eb0efc 100644
--- a/streampipes-config/src/main/java/org/apache/streampipes/config/backend/model/LocalAuthConfig.java
+++ b/streampipes-config/src/main/java/org/apache/streampipes/config/backend/model/LocalAuthConfig.java
@@ -23,11 +23,20 @@ public class LocalAuthConfig {
 
   private String tokenSecret;
   private long tokenExpirationTimeMillis;
+  private JwtSigningMode jwtSigningMode = JwtSigningMode.HMAC;
+
+  private String publicKey;
 
   public static LocalAuthConfig fromDefaults(String jwtSecret) {
     return new LocalAuthConfig(jwtSecret, TokenExpirationTimeMillisDefault);
   }
 
+  public static LocalAuthConfig fromDefaults(JwtSigningMode signingMode,
+                                             String key,
+                                             long tokenExpirationTimeMillis) {
+    return new LocalAuthConfig(signingMode, key, tokenExpirationTimeMillis);
+  }
+
   public static LocalAuthConfig from(String tokenSecret,
                                      long tokenExpirationTimeMillis) {
     return new LocalAuthConfig(tokenSecret, tokenExpirationTimeMillis);
@@ -37,9 +46,22 @@ public class LocalAuthConfig {
 
   }
 
+  private LocalAuthConfig(JwtSigningMode jwtSigningMode,
+                          String key,
+                          long tokenExpirationTimeMillis) {
+    this.jwtSigningMode = jwtSigningMode;
+    this.tokenExpirationTimeMillis = tokenExpirationTimeMillis;
+    if (jwtSigningMode == JwtSigningMode.HMAC) {
+      this.tokenSecret = key;
+    } else {
+      this.publicKey = key;
+    }
+  }
+
   private LocalAuthConfig(String tokenSecret,
                           long tokenExpirationTimeMillis) {
     this.tokenSecret = tokenSecret;
+    this.jwtSigningMode = JwtSigningMode.HMAC;
     this.tokenExpirationTimeMillis = tokenExpirationTimeMillis;
   }
 
@@ -58,4 +80,20 @@ public class LocalAuthConfig {
   public void setTokenExpirationTimeMillis(long tokenExpirationTimeMillis) {
     this.tokenExpirationTimeMillis = tokenExpirationTimeMillis;
   }
+
+  public JwtSigningMode getJwtSigningMode() {
+    return jwtSigningMode;
+  }
+
+  public void setJwtSigningMode(JwtSigningMode jwtSigningMode) {
+    this.jwtSigningMode = jwtSigningMode;
+  }
+
+  public String getPublicKey() {
+    return publicKey;
+  }
+
+  public void setPublicKey(String publicKey) {
+    this.publicKey = publicKey;
+  }
 }
diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/admin/GeneralConfigurationResource.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/admin/GeneralConfigurationResource.java
index 8761d6181..ad41cc80d 100644
--- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/admin/GeneralConfigurationResource.java
+++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/admin/GeneralConfigurationResource.java
@@ -22,12 +22,19 @@ import org.apache.streampipes.config.backend.model.GeneralConfig;
 import org.apache.streampipes.rest.core.base.impl.AbstractAuthGuardedRestResource;
 import org.apache.streampipes.rest.security.AuthConstants;
 import org.apache.streampipes.rest.shared.annotation.JacksonSerialized;
+import org.glassfish.jersey.media.multipart.BodyPart;
+import org.glassfish.jersey.media.multipart.MultiPart;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.stereotype.Component;
 
 import javax.ws.rs.*;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
+import java.io.StringWriter;
+import java.security.Key;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.util.Base64;
 
 @Path("/v2/admin/general-config")
 @Component
@@ -51,4 +58,33 @@ public class GeneralConfigurationResource extends AbstractAuthGuardedRestResourc
 
     return ok();
   }
+
+  @GET
+  @Path("keys")
+  @Produces("multipart/mixed")
+  @PreAuthorize(AuthConstants.IS_ADMIN_ROLE)
+  public Response generateKeyPair() throws Exception {
+    KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
+    kpg.initialize(2048);
+    KeyPair keyPair = kpg.genKeyPair();
+
+    String publicKeyPem = exportKeyAsPem(keyPair.getPublic(), "PUBLIC");
+    String privateKeyPem = exportKeyAsPem(keyPair.getPrivate(), "PRIVATE");
+
+    MultiPart multiPartEntity = new MultiPart()
+            .bodyPart(new BodyPart().entity(publicKeyPem))
+            .bodyPart(new BodyPart().entity(privateKeyPem));
+
+    return Response.ok(multiPartEntity).build();
+  }
+
+  private String exportKeyAsPem(Key key, String keyType) throws Exception {
+    StringWriter sw = new StringWriter();
+
+    sw.write("-----BEGIN " + keyType + " KEY-----\n");
+    sw.write(Base64.getEncoder().encodeToString(key.getEncoded()));
+    sw.write("\n-----END " + keyType + " KEY-----\n");
+
+    return sw.toString();
+  }
 }
diff --git a/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/JwtTokenGenerator.java b/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/JwtTokenGenerator.java
new file mode 100644
index 000000000..0424cd02c
--- /dev/null
+++ b/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/JwtTokenGenerator.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.security.jwt;
+
+import io.jsonwebtoken.JwtBuilder;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.security.Keys;
+
+import javax.crypto.SecretKey;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.Key;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Date;
+import java.util.Map;
+
+public class JwtTokenGenerator {
+
+  public static String makeJwtToken(String subject,
+                                    String tokenSecret,
+                                    Date expirationDate) {
+
+    return prepareJwtToken(subject, makeHmacKey(tokenSecret), expirationDate).compact();
+
+  }
+
+  public static String makeJwtToken(String subject,
+                                    Path keyFilePath,
+                                    Map<String, Object> claims,
+                                    Date expirationDate) throws NoSuchAlgorithmException, IOException, InvalidKeySpecException {
+
+    JwtBuilder builder = prepareJwtToken(subject, makeRsaKey(keyFilePath), expirationDate);
+
+    return builder.addClaims(claims).compact();
+  }
+
+  public static String makeJwtToken(String subject,
+                                    String tokenSecret,
+                                    Map<String, Object> claims,
+                                    Date expirationDate) {
+
+    JwtBuilder builder = prepareJwtToken(subject, makeHmacKey(tokenSecret), expirationDate);
+
+    return builder.addClaims(claims).compact();
+  }
+
+  private static SecretKey makeHmacKey(String tokenSecret) {
+    return Keys.hmacShaKeyFor(tokenSecret.getBytes(StandardCharsets.UTF_8));
+  }
+
+  private static RSAPrivateKey makeRsaKey(Path keyFilePath) throws NoSuchAlgorithmException, IOException, InvalidKeySpecException {
+    String key = Files.readString(keyFilePath, Charset.defaultCharset());
+
+    byte[] decoded = KeyUtils.extractPrivate(key);
+
+    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+    PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decoded);
+    return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
+  }
+
+  private static JwtBuilder prepareJwtToken(String subject,
+                                            Key key,
+                                            Date expirationDate) {
+    return Jwts
+            .builder()
+            .setSubject(subject)
+            .setIssuedAt(new Date())
+            .setExpiration(expirationDate)
+            .signWith(key);
+  }
+}
diff --git a/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/JwtTokenUtils.java b/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/JwtTokenUtils.java
index beafb4be2..6163c61d1 100644
--- a/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/JwtTokenUtils.java
+++ b/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/JwtTokenUtils.java
@@ -17,52 +17,15 @@
  */
 package org.apache.streampipes.security.jwt;
 
-import io.jsonwebtoken.*;
-import io.jsonwebtoken.security.Keys;
-import io.jsonwebtoken.security.WeakKeyException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.JwtParser;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SigningKeyResolver;
 
-import javax.crypto.SecretKey;
 import java.nio.charset.StandardCharsets;
-import java.util.Date;
-import java.util.Map;
 
 public class JwtTokenUtils {
 
-  private static final Logger LOG = LoggerFactory.getLogger(JwtTokenUtils.class);
-
-  public static String makeJwtToken(String subject,
-                                    String tokenSecret,
-                                    Date expirationDate) {
-
-    return prepareJwtToken(subject, tokenSecret, expirationDate).compact();
-
-  }
-
-  public static String makeJwtToken(String subject,
-                                    String tokenSecret,
-                                    Map<String, Object> claims,
-                                    Date expirationDate) {
-
-    JwtBuilder builder = prepareJwtToken(subject, tokenSecret, expirationDate);
-
-    return builder.addClaims(claims).compact();
-  }
-
-  private static JwtBuilder prepareJwtToken(String subject,
-                                            String tokenSecret,
-                                            Date expirationDate) {
-    SecretKey key = Keys.hmacShaKeyFor(tokenSecret.getBytes(StandardCharsets.UTF_8));
-
-    return Jwts
-            .builder()
-            .setSubject(subject)
-            .setIssuedAt(new Date())
-            .setExpiration(expirationDate)
-            .signWith(key);
-  }
-
   public static String getUserIdFromToken(String tokenSecret,
                                           String token) {
     Claims claims = jwtParser(tokenSecret).parseClaimsJws(token).getBody();
@@ -74,44 +37,16 @@ public class JwtTokenUtils {
     return jwtParser(resolver).parseClaimsJws(token).getBody().getSubject();
   }
 
-  private static JwtParser jwtParser(String tokenSecret) {
+  public static JwtParser jwtParser(String tokenSecret) {
     return Jwts.parserBuilder()
             .setSigningKey(tokenSecret.getBytes(StandardCharsets.UTF_8))
             .build();
   }
 
-  private static JwtParser jwtParser(SigningKeyResolver resolver) {
+  public static JwtParser jwtParser(SigningKeyResolver resolver) {
     return Jwts.parserBuilder()
             .setSigningKeyResolver(resolver)
             .build();
   }
 
-  public static boolean validateJwtToken(String jwtToken,
-                                         SigningKeyResolver resolver) {
-    return validateJwtToken(jwtParser(resolver), jwtToken);
-  }
-
-  public static boolean validateJwtToken(String tokenSecret,
-                                         String jwtToken) {
-    return validateJwtToken(jwtParser(tokenSecret), jwtToken);
-  }
-
-  private static boolean validateJwtToken(JwtParser parser,
-                                          String jwtToken) {
-    try {
-      parser.parseClaimsJws(jwtToken);
-      return true;
-    } catch (MalformedJwtException ex) {
-      LOG.error("Invalid JWT token");
-    } catch (ExpiredJwtException ex) {
-      LOG.error("Expired JWT token");
-    } catch (UnsupportedJwtException ex) {
-      LOG.error("Unsupported JWT token");
-    } catch (IllegalArgumentException ex) {
-      LOG.error("JWT claims are empty.");
-    } catch (WeakKeyException ex) {
-      LOG.error("Weak Key");
-    }
-    return false;
-  }
 }
diff --git a/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/JwtTokenValidator.java b/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/JwtTokenValidator.java
new file mode 100644
index 000000000..9b1237e0d
--- /dev/null
+++ b/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/JwtTokenValidator.java
@@ -0,0 +1,40 @@
+package org.apache.streampipes.security.jwt;
+
+import io.jsonwebtoken.*;
+import io.jsonwebtoken.security.WeakKeyException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class JwtTokenValidator {
+
+  private static final Logger LOG = LoggerFactory.getLogger(JwtTokenValidator.class);
+
+  public static boolean validateJwtToken(String jwtToken,
+                                         SigningKeyResolver resolver) {
+    return validateJwtToken(JwtTokenUtils.jwtParser(resolver), jwtToken);
+  }
+
+  public static boolean validateJwtToken(String tokenSecret,
+                                         String jwtToken) {
+    return validateJwtToken(JwtTokenUtils.jwtParser(tokenSecret), jwtToken);
+  }
+
+  private static boolean validateJwtToken(JwtParser parser,
+                                          String jwtToken) {
+    try {
+      parser.parseClaimsJws(jwtToken);
+      return true;
+    } catch (MalformedJwtException ex) {
+      LOG.error("Invalid JWT token");
+    } catch (ExpiredJwtException ex) {
+      LOG.error("Expired JWT token");
+    } catch (UnsupportedJwtException ex) {
+      LOG.error("Unsupported JWT token");
+    } catch (IllegalArgumentException ex) {
+      LOG.error("JWT claims are empty.");
+    } catch (WeakKeyException ex) {
+      LOG.error("Weak Key");
+    }
+    return false;
+  }
+}
diff --git a/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/KeyGenerator.java b/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/KeyGenerator.java
new file mode 100644
index 000000000..4faa39577
--- /dev/null
+++ b/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/KeyGenerator.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.security.jwt;
+
+import io.jsonwebtoken.security.Keys;
+import org.apache.streampipes.commons.constants.Envs;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.Key;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+
+public class KeyGenerator {
+
+  private static Logger LOG = LoggerFactory.getLogger(KeyGenerator.class);
+
+  public Key makeKeyForSecret(String tokenSecret) {
+    return Keys.hmacShaKeyFor(tokenSecret.getBytes(StandardCharsets.UTF_8));
+  }
+
+  public Key makeKeyForSecret(String alg,
+                              String tokenSecret) throws IOException {
+    return makeKeyForSecret(alg, tokenSecret, readKey());
+  }
+
+  public Key makeKeyForSecret(String alg,
+                              String tokenSecret,
+                              String pkContent) {
+    if (alg.equals("RS256")) {
+      try {
+        return makeKeyForRsa(pkContent);
+      } catch (IOException | InvalidKeySpecException | NoSuchAlgorithmException e) {
+        e.printStackTrace();
+        return makeKeyForSecret(tokenSecret);
+      }
+    } else {
+      LOG.warn("Could not properly create the provided key, defaulting to an HMAC token, which will most certainly lead to problems");
+      return makeKeyForSecret(tokenSecret);
+    }
+  }
+
+  public String readKey() throws IOException {
+   return Files.readString(Paths.get(Envs.SP_JWT_PUBLIC_KEY_LOC.getValue()), Charset.defaultCharset());
+  }
+
+  public Key makeKeyForRsa(String key) throws IOException, InvalidKeySpecException, NoSuchAlgorithmException {
+    byte[] decoded = KeyUtils.extractPublic(key);
+
+    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+    X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decoded);
+    return keyFactory.generatePublic(keySpec);
+  }
+}
diff --git a/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/KeyUtils.java b/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/KeyUtils.java
new file mode 100644
index 000000000..2cf49b107
--- /dev/null
+++ b/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/KeyUtils.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.security.jwt;
+
+import org.apache.commons.codec.binary.Base64;
+
+public class KeyUtils {
+
+  public static byte[] extractPrivate(String key) {
+    return decode(key
+      .replace("-----BEGIN PRIVATE KEY-----", "")
+      .replaceAll(System.lineSeparator(), "")
+      .replace("-----END PRIVATE KEY-----", ""));
+  }
+
+  public static byte[] extractPublic(String key) {
+    return decode(key
+            .replace("-----BEGIN PUBLIC KEY-----", "")
+            .replaceAll(System.lineSeparator(), "")
+            .replace("-----END PUBLIC KEY-----", ""));
+  }
+
+  private static byte[] decode(String key) {
+    return Base64.decodeBase64(key);
+  }
+}
diff --git a/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/jwt/JwtTokenProvider.java b/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/jwt/JwtTokenProvider.java
index 4cba6f804..779129a46 100644
--- a/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/jwt/JwtTokenProvider.java
+++ b/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/jwt/JwtTokenProvider.java
@@ -18,17 +18,28 @@
 
 package org.apache.streampipes.user.management.jwt;
 
+import org.apache.streampipes.commons.constants.Envs;
 import org.apache.streampipes.config.backend.BackendConfig;
+import org.apache.streampipes.config.backend.model.JwtSigningMode;
 import org.apache.streampipes.config.backend.model.LocalAuthConfig;
 import org.apache.streampipes.model.client.user.Principal;
 import org.apache.streampipes.model.client.user.UserAccount;
+import org.apache.streampipes.security.jwt.JwtTokenGenerator;
 import org.apache.streampipes.security.jwt.JwtTokenUtils;
+import org.apache.streampipes.security.jwt.JwtTokenValidator;
 import org.apache.streampipes.user.management.model.PrincipalUserDetails;
 import org.apache.streampipes.user.management.util.GrantedAuthoritiesBuilder;
 import org.apache.streampipes.user.management.util.UserInfoUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.GrantedAuthority;
 
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
@@ -37,6 +48,8 @@ import java.util.stream.Collectors;
 
 public class JwtTokenProvider {
 
+	private static final Logger LOG = LoggerFactory.getLogger(JwtTokenProvider.class);
+
 	public static final String CLAIM_USER = "user";
 
 	private BackendConfig config;
@@ -67,7 +80,16 @@ public class JwtTokenProvider {
 		Date tokenExpirationDate = makeExpirationDate();
 		Map<String, Object> claims = makeClaims(userPrincipal, roles);
 
-		return JwtTokenUtils.makeJwtToken(userPrincipal.getUsername(), tokenSecret(), claims, tokenExpirationDate);
+		if (authConfig().getJwtSigningMode() == JwtSigningMode.HMAC) {
+			return JwtTokenGenerator.makeJwtToken(userPrincipal.getUsername(), tokenSecret(), claims, tokenExpirationDate);
+		} else {
+			try {
+				return JwtTokenGenerator.makeJwtToken(userPrincipal.getUsername(), getKeyFilePath(), claims, tokenExpirationDate);
+			} catch (NoSuchAlgorithmException | IOException | InvalidKeySpecException e) {
+				LOG.warn("Could not create JWT token from private key location..defaulting to HMAC");
+				return JwtTokenGenerator.makeJwtToken(userPrincipal.getUsername(), tokenSecret(), claims, tokenExpirationDate);
+			}
+		}
 	}
 
 	private Map<String, Object> makeClaims(Principal principal,
@@ -83,18 +105,22 @@ public class JwtTokenProvider {
 	}
 
 	public boolean validateJwtToken(String jwtToken) {
-		return JwtTokenUtils.validateJwtToken(jwtToken, new SpKeyResolver(tokenSecret()));
+		return JwtTokenValidator.validateJwtToken(jwtToken, new SpKeyResolver(tokenSecret()));
 	}
 
 	public boolean validateJwtToken(String tokenSecret,
 																	String jwtToken) {
-		return JwtTokenUtils.validateJwtToken(tokenSecret, jwtToken);
+		return JwtTokenValidator.validateJwtToken(tokenSecret, jwtToken);
 	}
 
 	private String tokenSecret() {
 		return authConfig().getTokenSecret();
 	}
 
+	private Path getKeyFilePath() {
+		return Paths.get(Envs.SP_JWT_PRIVATE_KEY_LOC.getValue());
+	}
+
 	private LocalAuthConfig authConfig() {
 		return this.config.getLocalAuthConfig();
 	}
diff --git a/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/jwt/SpKeyResolver.java b/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/jwt/SpKeyResolver.java
index 24bbaea8e..99f9ea748 100644
--- a/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/jwt/SpKeyResolver.java
+++ b/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/jwt/SpKeyResolver.java
@@ -20,21 +20,21 @@ package org.apache.streampipes.user.management.jwt;
 import io.jsonwebtoken.Claims;
 import io.jsonwebtoken.JwsHeader;
 import io.jsonwebtoken.SigningKeyResolver;
-import io.jsonwebtoken.security.Keys;
+import org.apache.streampipes.config.backend.BackendConfig;
 import org.apache.streampipes.model.client.user.Principal;
 import org.apache.streampipes.model.client.user.ServiceAccount;
 import org.apache.streampipes.model.client.user.UserAccount;
+import org.apache.streampipes.security.jwt.KeyGenerator;
 import org.apache.streampipes.storage.api.IUserStorage;
 import org.apache.streampipes.storage.management.StorageDispatcher;
 import org.apache.streampipes.user.management.encryption.SecretEncryptionManager;
 
-import java.nio.charset.StandardCharsets;
 import java.security.Key;
 
 public class SpKeyResolver implements SigningKeyResolver {
 
-  private String tokenSecret;
-  private IUserStorage userStorage;
+  private final String tokenSecret;
+  private final IUserStorage userStorage;
 
   public SpKeyResolver(String tokenSecret) {
     this.tokenSecret = tokenSecret;
@@ -48,10 +48,10 @@ public class SpKeyResolver implements SigningKeyResolver {
     if (principal == null) {
       return null;
     } else if (isRealUser(principal)) {
-      return makeKeyForSecret(this.tokenSecret);
+      return new KeyGenerator().makeKeyForSecret(jwsHeader.getAlgorithm(), this.tokenSecret, getPublicKeyFromConfig());
     } else {
       String decryptedSecret = SecretEncryptionManager.decrypt(((ServiceAccount) principal).getClientSecret());
-      return makeKeyForSecret(decryptedSecret);
+      return new KeyGenerator().makeKeyForSecret(jwsHeader.getAlgorithm(), decryptedSecret, getPublicKeyFromConfig());
     }
   }
 
@@ -68,7 +68,9 @@ public class SpKeyResolver implements SigningKeyResolver {
     return principal instanceof UserAccount;
   }
 
-  private Key makeKeyForSecret(String tokenSecret) {
-    return Keys.hmacShaKeyFor(tokenSecret.getBytes(StandardCharsets.UTF_8));
+  public String getPublicKeyFromConfig() {
+    return BackendConfig.INSTANCE.getLocalAuthConfig().getPublicKey();
   }
+
+
 }
diff --git a/ui/src/app/configuration/configuration.module.ts b/ui/src/app/configuration/configuration.module.ts
index 969afcade..864d91863 100644
--- a/ui/src/app/configuration/configuration.module.ts
+++ b/ui/src/app/configuration/configuration.module.ts
@@ -53,50 +53,54 @@ import { SecurityUserGroupConfigComponent } from './security-configuration/user-
 import { EditGroupDialogComponent } from './security-configuration/edit-group-dialog/edit-group-dialog.component';
 import { EmailConfigurationComponent } from './email-configuration/email-configuration.component';
 import { GeneralConfigurationComponent } from './general-configuration/general-configuration.component';
+import {
+  SecurityAuthenticationConfigurationComponent
+} from './security-configuration/authentication-configuration/authentication-configuration.component';
 
 @NgModule({
-    imports: [
-        CommonModule,
-        CustomMaterialModule,
-        FlexLayoutModule,
-        MatGridListModule,
-        MatButtonModule,
-        MatProgressSpinnerModule,
-        MatIconModule,
-        MatInputModule,
-        MatCheckboxModule,
-        MatDividerModule,
-        MatTooltipModule,
-        FormsModule,
-        DragDropModule,
-        CoreUiModule,
-        ReactiveFormsModule,
-        PlatformServicesModule,
-    ],
-    declarations: [
-        ConfigurationComponent,
-        ConsulServiceComponent,
-        ConsulConfigsComponent,
-        ConsulConfigsTextComponent,
-        ConsulConfigsPasswordComponent,
-        ConsulConfigsBooleanComponent,
-        ConsulConfigsNumberComponent,
-        DeleteDatalakeIndexComponent,
-        EditUserDialogComponent,
-        EditGroupDialogComponent,
-        EmailConfigurationComponent,
-        GeneralConfigurationComponent,
-        PipelineElementConfigurationComponent,
-        SecurityConfigurationComponent,
-        SecurityUserConfigComponent,
-        SecurityUserGroupConfigComponent,
-        SecurityServiceConfigComponent,
-        MessagingConfigurationComponent,
-        DatalakeConfigurationComponent,
-    ],
-    providers: [
-        ConfigurationService,
-    ]
+  imports: [
+    CommonModule,
+    CustomMaterialModule,
+    FlexLayoutModule,
+    MatGridListModule,
+    MatButtonModule,
+    MatProgressSpinnerModule,
+    MatIconModule,
+    MatInputModule,
+    MatCheckboxModule,
+    MatDividerModule,
+    MatTooltipModule,
+    FormsModule,
+    DragDropModule,
+    CoreUiModule,
+    ReactiveFormsModule,
+    PlatformServicesModule,
+  ],
+  declarations: [
+    ConfigurationComponent,
+    ConsulServiceComponent,
+    ConsulConfigsComponent,
+    ConsulConfigsTextComponent,
+    ConsulConfigsPasswordComponent,
+    ConsulConfigsBooleanComponent,
+    ConsulConfigsNumberComponent,
+    DeleteDatalakeIndexComponent,
+    EditUserDialogComponent,
+    EditGroupDialogComponent,
+    EmailConfigurationComponent,
+    GeneralConfigurationComponent,
+    PipelineElementConfigurationComponent,
+    SecurityAuthenticationConfigurationComponent,
+    SecurityConfigurationComponent,
+    SecurityUserConfigComponent,
+    SecurityUserGroupConfigComponent,
+    SecurityServiceConfigComponent,
+    MessagingConfigurationComponent,
+    DatalakeConfigurationComponent,
+  ],
+  providers: [
+    ConfigurationService,
+  ]
 })
 export class ConfigurationModule {
 }
diff --git a/ui/src/app/configuration/security-configuration/authentication-configuration/authentication-configuration.component.html b/ui/src/app/configuration/security-configuration/authentication-configuration/authentication-configuration.component.html
new file mode 100644
index 000000000..d671fe2b8
--- /dev/null
+++ b/ui/src/app/configuration/security-configuration/authentication-configuration/authentication-configuration.component.html
@@ -0,0 +1,26 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~    http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  ~
+  -->
+
+<div fxFlex="100" fxLayout="column">
+    <div>
+        <button mat-button
+                mat-raised-button
+                color="accent"
+                (click)="generateKeyPair()">Generate and download new key pair</button>
+    </div>
+</div>
diff --git a/ui/src/app/configuration/security-configuration/authentication-configuration/authentication-configuration.component.scss b/ui/src/app/configuration/security-configuration/authentication-configuration/authentication-configuration.component.scss
new file mode 100644
index 000000000..13cbc4aac
--- /dev/null
+++ b/ui/src/app/configuration/security-configuration/authentication-configuration/authentication-configuration.component.scss
@@ -0,0 +1,17 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
diff --git a/ui/src/app/configuration/security-configuration/authentication-configuration/authentication-configuration.component.ts b/ui/src/app/configuration/security-configuration/authentication-configuration/authentication-configuration.component.ts
new file mode 100644
index 000000000..c205335df
--- /dev/null
+++ b/ui/src/app/configuration/security-configuration/authentication-configuration/authentication-configuration.component.ts
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+
+import { Component, OnInit } from '@angular/core';
+import { ConfigurationService } from '../../shared/configuration.service';
+import * as FileSaver from 'file-saver';
+
+@Component({
+  selector: 'sp-authentication-configuration',
+  templateUrl: './authentication-configuration.component.html',
+  styleUrls: ['./authentication-configuration.component.scss']
+})
+export class SecurityAuthenticationConfigurationComponent implements OnInit {
+
+  constructor(private configurationService: ConfigurationService) {
+
+  }
+
+  ngOnInit(): void {
+  }
+
+  generateKeyPair() {
+    this.configurationService.generateKeyPair().subscribe(result => {
+      console.log(result);
+      this.saveKeyfile('public.key', result[0]);
+      this.saveKeyfile('private.pem', result[1]);
+    });
+  }
+
+  saveKeyfile(filename: string,
+              content: string) {
+    const blob = new Blob([content], {type: 'text/plain'});
+    FileSaver.saveAs(blob, filename);
+  }
+
+}
diff --git a/ui/src/app/configuration/security-configuration/security-configuration.component.html b/ui/src/app/configuration/security-configuration/security-configuration.component.html
index 5db6583fb..3afffc9b6 100644
--- a/ui/src/app/configuration/security-configuration/security-configuration.component.html
+++ b/ui/src/app/configuration/security-configuration/security-configuration.component.html
@@ -41,4 +41,11 @@
         </sp-split-section>
     </div>
     <mat-divider></mat-divider>
+    <div fxFlex="100" fxLayout="column" fxLayoutAlign="start start">
+        <sp-split-section title="Authentication"
+                          subtitle="Auth & token settings">
+            <div class="subsection-title">JWT Signature</div>
+            <sp-authentication-configuration></sp-authentication-configuration>
+        </sp-split-section>
+    </div>
 </div>
diff --git a/ui/src/app/configuration/shared/configuration.service.ts b/ui/src/app/configuration/shared/configuration.service.ts
index e6dd7795d..fcfbb8e06 100644
--- a/ui/src/app/configuration/shared/configuration.service.ts
+++ b/ui/src/app/configuration/shared/configuration.service.ts
@@ -23,6 +23,7 @@ import { map } from 'rxjs/operators';
 
 import { StreampipesPeContainer } from './streampipes-pe-container.model';
 import { MessagingSettings } from './messaging-settings.model';
+import { MultipartUtils } from "./multipart-utils";
 
 @Injectable()
 export class ConfigurationService {
@@ -35,6 +36,13 @@ export class ConfigurationService {
         return '/streampipes-backend';
     }
 
+    generateKeyPair(): Observable<string[]> {
+      return this.http.get(this.getServerUrl() + '/api/v2/admin/general-config/keys', {responseType: 'text', observe: 'response'})
+        .pipe(map(response => {
+          return new MultipartUtils().extractMultipartPlainTextContent(response);
+      }));
+    }
+
     getMessagingSettings(): Observable<MessagingSettings> {
         return this.http.get(this.getServerUrl() + '/api/v2/consul/messaging')
             .pipe(
@@ -72,7 +80,6 @@ export class ConfigurationService {
         return this.http.post(this.getServerUrl() + '/api/v2/consul/messaging', messagingSettings);
     }
 
-
     adjustConfigurationKey(consulKey) {
 
             const removedKey = consulKey.substr(consulKey.lastIndexOf('/') + 1, consulKey.length);
diff --git a/ui/src/app/configuration/shared/multipart-utils.ts b/ui/src/app/configuration/shared/multipart-utils.ts
new file mode 100644
index 000000000..2a46b7942
--- /dev/null
+++ b/ui/src/app/configuration/shared/multipart-utils.ts
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { HttpResponse } from '@angular/common/http';
+
+export class MultipartUtils {
+
+  extractMultipartPlainTextContent(resp: HttpResponse<string>): string[] {
+
+    const boundary = this.getBoundary(resp);
+    const parts = resp.body.split(boundary);
+    parts.shift();
+    parts.pop();
+    return parts.map(part => part.replace('Content-Type: text/plain', ''));
+  }
+
+  getBoundary(resp: HttpResponse<string>) {
+    return '--' + this.getContentType(resp).split(';')[1].replace('boundary=', '');
+  }
+
+  getContentType(resp: HttpResponse<string>) {
+    return resp.headers.get('Content-type');
+  }
+}