diff --git a/src/main/java/org/olat/core/configuration/AbstractSpringModule.java b/src/main/java/org/olat/core/configuration/AbstractSpringModule.java
index 7e1ec8bb8e4d5219ea381006cb7537c5f0f7e798..1c7d64c6986115cd3564139dc24a1f28c7a459c7 100644
--- a/src/main/java/org/olat/core/configuration/AbstractSpringModule.java
+++ b/src/main/java/org/olat/core/configuration/AbstractSpringModule.java
@@ -22,6 +22,7 @@ package org.olat.core.configuration;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Properties;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import org.olat.core.gui.control.Event;
@@ -128,6 +129,10 @@ public abstract class AbstractSpringModule implements GenericEventListener, Init
 	protected Properties createPropertiesFromPersistedProperties() {
 		return moduleConfigProperties.createPropertiesFromPersistedProperties();
 	}
+	
+	protected Set<Object> getPropertyKeys() {
+		return moduleConfigProperties.getPropertyKeys();
+	}
 
 	/**
 	 * Return a string value for certain propertyName-parameter.
@@ -197,6 +202,12 @@ public abstract class AbstractSpringModule implements GenericEventListener, Init
 		moduleConfigProperties.setIntProperty(propertyName, value, saveConfiguration);
 		log.audit("change system property: " + propertyName, Integer.toString(value));
 	}
+	
+	protected void removeProperty(String propertyName, boolean saveConfiguration) {
+		moduleConfigProperties.removeProperty(propertyName, saveConfiguration);
+		log.audit("remove system property: " + propertyName, null);
+	}
+	
 	/**
 	 * Return a boolean value for certain propertyName
 	 * 
diff --git a/src/main/java/org/olat/core/configuration/PersistedProperties.java b/src/main/java/org/olat/core/configuration/PersistedProperties.java
index f0126d779e3947847a335c48c867175dd152db4a..3164b8c69339200311d74479d50ecd595c31789b 100644
--- a/src/main/java/org/olat/core/configuration/PersistedProperties.java
+++ b/src/main/java/org/olat/core/configuration/PersistedProperties.java
@@ -35,7 +35,9 @@ import java.io.OutputStream;
 import java.nio.file.Paths;
 import java.security.SecureRandom;
 import java.security.Security;
+import java.util.HashSet;
 import java.util.Properties;
+import java.util.Set;
 
 import javax.crypto.Cipher;
 import javax.crypto.CipherInputStream;
@@ -257,12 +259,24 @@ public class PersistedProperties extends LogDelegator implements Initializable,
 	 * Call this method when the PersitedProperties is not used anymore. Will
 	 * remove the event listener for change events on this class
 	 */
+	@Override
 	public final void destroy() {
 		if (propertiesChangedEventListener != null) {
 			coordinatorManager.getCoordinator().getEventBus().deregisterFor(propertiesChangedEventListener, PROPERTIES_CHANGED_EVENT_CHANNEL);
 			propertiesChangedEventListener = null;
 		}
 	}
+	
+	public Set<Object> getPropertyKeys() {
+		Set<Object> keys = new HashSet<>();
+		if(configuredProperties != null) {
+			keys.addAll(configuredProperties.keySet());
+		}
+		if(defaultProperties != null) {
+			keys.addAll(defaultProperties.keySet());
+		}	
+		return keys;
+	}
 
 	/**
 	 * Return an int value for a certain propertyName
@@ -426,6 +440,15 @@ public class PersistedProperties extends LogDelegator implements Initializable,
 	public void setBooleanPropertyDefault(String propertyName, boolean value) {
 		defaultProperties.setProperty(propertyName, Boolean.toString(value));
 	}
+	
+	public void removeProperty(String propertyName, boolean saveConfiguration) {
+		synchronized (configuredProperties) { // make read/write save in VM
+			configuredProperties.remove(propertyName);
+			if (saveConfiguration) {
+				savePropertiesAndFireChangedEvent();
+			}
+		}
+	}
 
 	/**
 	 * Save the properties configuration to disk and notify other nodes about
@@ -512,20 +535,19 @@ public class PersistedProperties extends LogDelegator implements Initializable,
 
   
 	private static final String salt = "A long, but constant phrase that will be used each time as the salt.";
-  private static final int iterations = 2000;
-  private static final int keyLength = 128;
-  private static final SecureRandom random = new SecureRandom();
+	private static final int iterations = 2000;
+	private static final int keyLength = 128;
+	private static final SecureRandom random = new SecureRandom();
   
 	private static SecretKey generateKey(String passphrase) throws Exception {
-    PBEKeySpec keySpec = new PBEKeySpec(passphrase.toCharArray(), salt.getBytes(), iterations, keyLength);
-    SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWITHSHA256AND128BITAES-CBC-BC");
-    return keyFactory.generateSecret(keySpec);
+		PBEKeySpec keySpec = new PBEKeySpec(passphrase.toCharArray(), salt.getBytes(), iterations, keyLength);
+		SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWITHSHA256AND128BITAES-CBC-BC");
+		return keyFactory.generateSecret(keySpec);
 	}
   
 	private static IvParameterSpec generateIV(Cipher cipher) throws Exception {
-    byte [] ivBytes = new byte[cipher.getBlockSize()];
-    random.nextBytes(ivBytes);
-    return new IvParameterSpec(ivBytes);
-  }
-
+		byte [] ivBytes = new byte[cipher.getBlockSize()];
+		random.nextBytes(ivBytes);
+		return new IvParameterSpec(ivBytes);
+	}
 }
diff --git a/src/main/java/org/olat/dispatcher/DMZDispatcher.java b/src/main/java/org/olat/dispatcher/DMZDispatcher.java
index 51e0b38c0a05ee537066110d52a5d9e7b15485f2..deebbe566225d403c9c49e89b7d879c9f4240c1e 100644
--- a/src/main/java/org/olat/dispatcher/DMZDispatcher.java
+++ b/src/main/java/org/olat/dispatcher/DMZDispatcher.java
@@ -235,12 +235,14 @@ public class DMZDispatcher implements Dispatcher {
 					I18nManager.updateLocaleInfoToThread(usess);//update locale infos
 					
 					OAuthLoginModule oauthModule = CoreSpringFactory.getImpl(OAuthLoginModule.class);
-					if(canRedirectOAuth(request, oauthModule)) {
+					if(canRedirectConfigurableOAuth(request, response, oauthModule)) {
+						return;
+					} else if(canRedirectOAuth(request, oauthModule)) {
 						OAuthSPI oauthSpi = oauthModule.getRootProvider();
 						HttpSession session = request.getSession();
 						OAuthResource.redirect(oauthSpi, response, session);
 						return;
-					}
+					} 
 					
 					// request new windows since it is a new usersession, the old one was purged
 					ws = Windows.getWindows(usess);
@@ -301,6 +303,19 @@ public class DMZDispatcher implements Dispatcher {
 		}
 		return canRedirect;
 	}
+	
+	private boolean canRedirectConfigurableOAuth(HttpServletRequest request, HttpServletResponse response, OAuthLoginModule oauthModule) {
+		String provider = request.getParameter("provider");
+		if(StringHelper.containsNonWhitespace(provider)) {
+			OAuthSPI spi = oauthModule.getProvider(provider);
+			if(spi != null) {
+				HttpSession session = request.getSession();
+				OAuthResource.redirect(spi, response, session);
+				return true;
+			}
+		}
+		return false;
+	}
 
 	/**
 	 * called by spring only
diff --git a/src/main/java/org/olat/login/LoginModule.java b/src/main/java/org/olat/login/LoginModule.java
index b4b08b735850cb14a20cac6cd97ecebe3d06d89a..1826c84eab03284d87182f3e0e8a97cae2d8efa9 100644
--- a/src/main/java/org/olat/login/LoginModule.java
+++ b/src/main/java/org/olat/login/LoginModule.java
@@ -192,6 +192,8 @@ public class LoginModule extends AbstractSpringModule {
 		for(AuthenticationProvider authProvider:authenticationProviders) {
 			if(authProvider.getName().equalsIgnoreCase(provider)) {
 				authenticationProvider = authProvider;
+			} else if(authProvider.accept(provider)) {
+				authenticationProvider = authProvider;
 			}
 		}
 		return authenticationProvider;
diff --git a/src/main/java/org/olat/login/auth/AuthenticationProvider.java b/src/main/java/org/olat/login/auth/AuthenticationProvider.java
index 44df8b621f8ba212706164ea3e8b544cb695fb89..6f40cefafa84693d6391c218b3be5dff1d7b1912 100644
--- a/src/main/java/org/olat/login/auth/AuthenticationProvider.java
+++ b/src/main/java/org/olat/login/auth/AuthenticationProvider.java
@@ -85,6 +85,10 @@ public class AuthenticationProvider implements ControllerCreator{
 		return isDefault;
 	}
 	
+	public boolean accept(@SuppressWarnings("unused") String subProviderName) {
+		return false;
+	}
+	
 	/**
 	 * @return Name used to identify this authprovider.
 	 */
diff --git a/src/main/java/org/olat/login/oauth/OAuthAuthenticationProvider.java b/src/main/java/org/olat/login/oauth/OAuthAuthenticationProvider.java
index c704a18d605cf41422b64d4b16176989838dd525..7b16dbce02c89170125e3d0357f384a66e8c2e9a 100644
--- a/src/main/java/org/olat/login/oauth/OAuthAuthenticationProvider.java
+++ b/src/main/java/org/olat/login/oauth/OAuthAuthenticationProvider.java
@@ -36,6 +36,18 @@ public class OAuthAuthenticationProvider extends AuthenticationProvider {
 		super(name, clazz, true, isDefault, iconCssClass);
 	}
 
+	@Override
+	public boolean accept(String subProviderName) {
+		OAuthLoginModule oauthLoginModule = CoreSpringFactory.getImpl(OAuthLoginModule.class);
+		List<OAuthSPI> spies = oauthLoginModule.getEnableSPIs();
+		for(OAuthSPI spi:spies) {
+			if(spi.getProviderName().equals(subProviderName)) {
+				return true;
+			}
+		}
+		return false;
+	}
+
 	@Override
 	public boolean isEnabled() {
 		OAuthLoginModule oauthLoginModule = CoreSpringFactory.getImpl(OAuthLoginModule.class);
diff --git a/src/main/java/org/olat/login/oauth/OAuthDisplayName.java b/src/main/java/org/olat/login/oauth/OAuthDisplayName.java
new file mode 100644
index 0000000000000000000000000000000000000000..18aeb65150ea93a6d78710841d08e4be1a47241e
--- /dev/null
+++ b/src/main/java/org/olat/login/oauth/OAuthDisplayName.java
@@ -0,0 +1,13 @@
+package org.olat.login.oauth;
+
+/**
+ * 
+ * Initial date: 6 oct. 2016<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public interface OAuthDisplayName {
+	
+	public String getDisplayName();
+
+}
diff --git a/src/main/java/org/olat/login/oauth/OAuthLoginModule.java b/src/main/java/org/olat/login/oauth/OAuthLoginModule.java
index d4dbff02d79e22144065485adc2b64093b8a1953..6b1f508b1651ae1d8ddc0a88c5b3e998ec4b59f1 100644
--- a/src/main/java/org/olat/login/oauth/OAuthLoginModule.java
+++ b/src/main/java/org/olat/login/oauth/OAuthLoginModule.java
@@ -21,9 +21,11 @@ package org.olat.login.oauth;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 import org.olat.core.configuration.AbstractSpringModule;
 import org.olat.core.util.coordinate.CoordinatorManager;
+import org.olat.login.oauth.spi.OpenIdConnectFullConfigurableProvider;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -36,6 +38,9 @@ import org.springframework.stereotype.Service;
 @Service
 public class OAuthLoginModule extends AbstractSpringModule {
 	
+	private static final String OPEN_ID_IF_START_MARKER = "openIdConnectIF.";
+	private static final String OPEN_ID_IF_END_MARKER = ".Enabled";
+	
 	private boolean allowUserCreation;
 	
 	private boolean linkedInEnabled;
@@ -71,6 +76,8 @@ public class OAuthLoginModule extends AbstractSpringModule {
 	@Autowired
 	private List<OAuthSPI> oauthSPIs;
 	
+	private List<OAuthSPI> configurableOauthSPIs;
+	
 	@Autowired
 	public OAuthLoginModule(CoordinatorManager coordinatorManager) {
 		super(coordinatorManager, true);
@@ -131,10 +138,60 @@ public class OAuthLoginModule extends AbstractSpringModule {
 		openIdConnectIFApiSecret = getStringPropertyValue("openIdConnectIFApiSecret", false);
 		openIdConnectIFIssuer = getStringPropertyValue("openIdConnectIFIssuer", false);
 		openIdConnectIFAuthorizationEndPoint = getStringPropertyValue("openIdConnectIFAuthorizationEndPoint", false);
+
+		Set<Object> allPropertyKeys = getPropertyKeys();
+		List<OAuthSPI> otherOAuthSPies = new ArrayList<>();
+		for(Object propertyKey:allPropertyKeys) {
+			if(propertyKey instanceof String) {
+				String key = (String)propertyKey;
+				if(key.startsWith(OPEN_ID_IF_START_MARKER) && key.endsWith(OPEN_ID_IF_END_MARKER)) {
+					 OAuthSPI spi = getAdditionalOpenIDConnectIF(key);
+					 if(spi != null) {
+						 otherOAuthSPies.add(spi);
+					 }
+				}
+			}
+		}
+		configurableOauthSPIs = otherOAuthSPies;
+	}
+	
+	private OAuthSPI getAdditionalOpenIDConnectIF(String enableKey) {
+		String providerName = enableKey.substring(OPEN_ID_IF_START_MARKER.length(), enableKey.length() - OPEN_ID_IF_END_MARKER.length());
+
+		String rootEnabledObj = getStringPropertyValue("openIdConnectIF." + providerName + ".RootEnabled", true);
+		boolean rootEnabled = "true".equals(rootEnabledObj);
+		String apiKey = getStringPropertyValue("openIdConnectIF." + providerName + ".ApiKey", true);
+		String apiSecret = getStringPropertyValue("openIdConnectIF." + providerName + ".ApiSecret", true);
+		String issuer = getStringPropertyValue("openIdConnectIF." + providerName + ".Issuer", true);
+		String endPoint = getStringPropertyValue("openIdConnectIF." + providerName + ".AuthorizationEndPoint", true);
+		String displayName = getStringPropertyValue("openIdConnectIF." + providerName + ".DisplayName", true);
+		
+		OpenIdConnectFullConfigurableProvider provider = new OpenIdConnectFullConfigurableProvider();
+		provider.setRootEnabled(rootEnabled);
+		provider.setName(providerName);
+		provider.setDisplayName(displayName);
+		provider.setProviderName(providerName);
+		provider.setAppKey(apiKey);
+		provider.setAppSecret(apiSecret);
+		provider.setIssuer(issuer);
+		provider.setEndPoint(endPoint);
+		return provider;
 	}
 	
 	public List<OAuthSPI> getAllSPIs() {
-		return new ArrayList<>(oauthSPIs);
+		List<OAuthSPI> spies = new ArrayList<>(oauthSPIs);
+		if(configurableOauthSPIs != null) {
+			spies.addAll(configurableOauthSPIs);
+		}
+		return spies;
+	}
+	
+	public List<OAuthSPI> getAllConfigurableSPIs() {
+		List<OAuthSPI> spies = new ArrayList<>();
+		if(configurableOauthSPIs != null) {
+			spies.addAll(configurableOauthSPIs);
+		}
+		return spies;
 	}
 	
 	public List<OAuthSPI> getEnableSPIs() {
@@ -146,6 +203,13 @@ public class OAuthLoginModule extends AbstractSpringModule {
 				}
 			}
 		}
+		if(configurableOauthSPIs != null) {
+			for(OAuthSPI spi:configurableOauthSPIs) {
+				if(spi.isEnabled()) {
+					enabledSpis.add(spi);
+				}
+			}
+		}
 		return enabledSpis;
 	}
 	
@@ -162,8 +226,34 @@ public class OAuthLoginModule extends AbstractSpringModule {
 				}
 			}
 		}
+		if(rootSpi == null && configurableOauthSPIs != null) {
+			for(OAuthSPI spi:configurableOauthSPIs) {
+				if(spi.isEnabled() && spi.isRootEnabled()) {
+					rootSpi = spi;
+				}
+			}
+		}
 		return rootSpi;
 	}
+	
+	public OAuthSPI getProvider(String providerName) {
+		OAuthSPI spi = null;
+		if(oauthSPIs != null) {
+			for(OAuthSPI oauthSpi:oauthSPIs) {
+				if(providerName.equals(oauthSpi.getProviderName())) {
+					spi = oauthSpi;
+				}
+			}
+		}
+		if(spi == null && configurableOauthSPIs != null) {
+			for(OAuthSPI oauthSpi:configurableOauthSPIs) {
+				if(providerName.equals(oauthSpi.getProviderName())) {
+					spi = oauthSpi;
+				}
+			}
+		}
+		return spi;
+	}
 
 	public boolean isAllowUserCreation() {
 		return allowUserCreation;
@@ -381,4 +471,24 @@ public class OAuthLoginModule extends AbstractSpringModule {
 		setStringProperty("openIdConnectIFAuthorizationEndPoint", openIdConnectIFAuthorizationEndPoint, true);
 	}
 	
+	public void setAdditionalOpenIDConnectIF(String providerName, String displayName, boolean rootEnabled, String issuer, String endPoint, String apiKey, String apiSecret) {
+		setStringProperty("openIdConnectIF." + providerName + ".Enabled", "true", false);
+		setStringProperty("openIdConnectIF." + providerName + ".RootEnabled", rootEnabled ? "true" : "false", false);
+		setStringProperty("openIdConnectIF." + providerName + ".ApiKey", apiKey, false);
+		setStringProperty("openIdConnectIF." + providerName + ".ApiSecret", apiSecret, false);
+		setStringProperty("openIdConnectIF." + providerName + ".Issuer", issuer, false);
+		setStringProperty("openIdConnectIF." + providerName + ".DisplayName", displayName, false);
+		setStringProperty("openIdConnectIF." + providerName + ".AuthorizationEndPoint", endPoint, true);
+		updateProperties();
+	}
+	
+	public void removeAdditionalOpenIDConnectIF(String providerName) {
+		removeProperty("openIdConnectIF." + providerName + ".Enabled", false);
+		removeProperty("openIdConnectIF." + providerName + ".ApiKey", false);
+		removeProperty("openIdConnectIF." + providerName + ".ApiSecret", false);
+		removeProperty("openIdConnectIF." + providerName + ".Issuer", false);
+		removeProperty("openIdConnectIF." + providerName + ".DisplayName", false);
+		removeProperty("openIdConnectIF." + providerName + ".AuthorizationEndPoint", true);
+		updateProperties();
+	}
 }
diff --git a/src/main/java/org/olat/login/oauth/OAuthSPI.java b/src/main/java/org/olat/login/oauth/OAuthSPI.java
index 2ab05a60317a88b55cb2058b6f4e7c433bdbafd9..f8490e8a8dac723ff3d5cdf8728e90a91dcfcdf8 100644
--- a/src/main/java/org/olat/login/oauth/OAuthSPI.java
+++ b/src/main/java/org/olat/login/oauth/OAuthSPI.java
@@ -33,7 +33,7 @@ import org.scribe.oauth.OAuthService;
  */
 public interface OAuthSPI extends ConfigOnOff {
 	
-	public Class<? extends Api> getScribeProvider();
+	public Api getScribeProvider();
 	
 	public String getName();
 	
diff --git a/src/main/java/org/olat/login/oauth/spi/ADFSProvider.java b/src/main/java/org/olat/login/oauth/spi/ADFSProvider.java
index 5cd5b7d33fc2bca47bff4e16928bb983436bbe1c..79dcb1ab7b4ba25403dc7ad4c20120e7364fd69a 100644
--- a/src/main/java/org/olat/login/oauth/spi/ADFSProvider.java
+++ b/src/main/java/org/olat/login/oauth/spi/ADFSProvider.java
@@ -63,8 +63,8 @@ public class ADFSProvider implements OAuthSPI {
 	}
 
 	@Override
-	public Class<? extends Api> getScribeProvider() {
-		return ADFSApi.class;
+	public Api getScribeProvider() {
+		return new ADFSApi();
 	}
 
 	@Override
diff --git a/src/main/java/org/olat/login/oauth/spi/FacebookProvider.java b/src/main/java/org/olat/login/oauth/spi/FacebookProvider.java
index 1a7df90a56d8d33096dc59f718f67845ae887911..3f77881cab5d0360d7865c1b67c0f0aafcea1cb3 100644
--- a/src/main/java/org/olat/login/oauth/spi/FacebookProvider.java
+++ b/src/main/java/org/olat/login/oauth/spi/FacebookProvider.java
@@ -67,8 +67,8 @@ public class FacebookProvider implements OAuthSPI {
 	}
 
 	@Override
-	public Class<? extends Api> getScribeProvider() {
-		return FacebookApi.class;
+	public Api getScribeProvider() {
+		return new FacebookApi();
 	}
 
 	@Override
diff --git a/src/main/java/org/olat/login/oauth/spi/Google2Provider.java b/src/main/java/org/olat/login/oauth/spi/Google2Provider.java
index 817314cb682c71b986877ceb1646b591a33bc475..e3cad3f4a76b36fc9cc3934ef77d987b8db3afa1 100644
--- a/src/main/java/org/olat/login/oauth/spi/Google2Provider.java
+++ b/src/main/java/org/olat/login/oauth/spi/Google2Provider.java
@@ -66,8 +66,8 @@ public class Google2Provider implements OAuthSPI {
 	}
 
 	@Override
-	public Class<? extends Api> getScribeProvider() {
-		return Google2Api.class;
+	public Api getScribeProvider() {
+		return new Google2Api();
 	}
 
 	@Override
diff --git a/src/main/java/org/olat/login/oauth/spi/LinkedInProvider.java b/src/main/java/org/olat/login/oauth/spi/LinkedInProvider.java
index 60cacd456402853a3ad2ec5128502567952680cf..7999287b877d3e699ba4c443943bd7cc6948b704 100644
--- a/src/main/java/org/olat/login/oauth/spi/LinkedInProvider.java
+++ b/src/main/java/org/olat/login/oauth/spi/LinkedInProvider.java
@@ -93,8 +93,8 @@ public class LinkedInProvider implements OAuthSPI {
 	}
 
 	@Override
-	public Class<? extends Api> getScribeProvider() {
-		return LinkedInApi.class;
+	public Api getScribeProvider() {
+		return new LinkedInApi();
 	}
 
 	@Override
diff --git a/src/main/java/org/olat/login/oauth/spi/OpenIdConnectFullConfigurableApi.java b/src/main/java/org/olat/login/oauth/spi/OpenIdConnectFullConfigurableApi.java
new file mode 100644
index 0000000000000000000000000000000000000000..6aff4fe904646a2915a75f5fb8a8f15c1e802541
--- /dev/null
+++ b/src/main/java/org/olat/login/oauth/spi/OpenIdConnectFullConfigurableApi.java
@@ -0,0 +1,129 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.login.oauth.spi;
+
+import java.util.UUID;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
+import org.scribe.builder.api.DefaultApi20;
+import org.scribe.extractors.AccessTokenExtractor;
+import org.scribe.model.OAuthConfig;
+import org.scribe.model.Token;
+import org.scribe.model.Verb;
+import org.scribe.model.Verifier;
+import org.scribe.oauth.OAuth20ServiceImpl;
+import org.scribe.oauth.OAuthService;
+import org.scribe.utils.OAuthEncoder;
+
+/**
+ * 
+ * Initial date: 6 oct. 2016<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class OpenIdConnectFullConfigurableApi extends DefaultApi20 {
+	
+	private static final OLog log = Tracing.createLoggerFor(OpenIdConnectFullConfigurableApi.class);
+	
+	private final OpenIdConnectFullConfigurableProvider provider;
+	
+	public OpenIdConnectFullConfigurableApi(OpenIdConnectFullConfigurableProvider provider) {
+		this.provider = provider;
+	}
+
+    @Override
+    public String getAccessTokenEndpoint() {
+        return null;
+    }
+    
+    @Override
+    public AccessTokenExtractor getAccessTokenExtractor() {
+        return null;
+    }
+
+    @Override
+    public String getAuthorizationUrl(OAuthConfig config) {
+    	String url = provider.getEndPoint();
+    	StringBuilder authorizeUrl = new StringBuilder();
+    	authorizeUrl
+    		.append(url).append("?")
+    		.append("response_type=").append(OAuthEncoder.encode("id_token token"))
+    		.append("&client_id=").append(config.getApiKey())
+    		.append("&redirect_uri=").append(OAuthEncoder.encode(config.getCallback()))
+    	    .append("&scope=").append(OAuthEncoder.encode("openid email"))
+    		.append("&state=").append(UUID.randomUUID().toString())
+    		.append("&nonce=").append(UUID.randomUUID().toString());		
+    	return authorizeUrl.toString();
+    }
+    
+    @Override
+    public Verb getAccessTokenVerb() {
+        return Verb.POST;
+    }
+    
+    @Override
+    public OAuthService createService(OAuthConfig config) {
+        return new OpenIdConnectFullConfigurableService(this, config);
+    }
+    
+    public class OpenIdConnectFullConfigurableService extends OAuth20ServiceImpl {
+
+        public OpenIdConnectFullConfigurableService(DefaultApi20 api, OAuthConfig config) {
+            super(api, config);
+        }
+        
+        @Override
+        public Token getAccessToken(Token requestToken, Verifier verifier) {
+        	try {
+				OpenIDVerifier oVerifier = (OpenIDVerifier)verifier;
+				String idToken = oVerifier.getIdToken();
+				JSONObject idJson = JSONWebToken.parse(idToken).getJsonPayload();
+				JSONObject accessJson = JSONWebToken.parse(oVerifier.getAccessToken()).getJsonPayload();
+				
+				boolean allOk = true;
+				if(!provider.getIssuer().equals(idJson.get("iss"))) {
+					allOk &= false;
+				}
+				if(!provider.getIssuer().equals(accessJson.get("iss"))) {
+					allOk &= false;
+				}
+				
+				if(!provider.getAppKey().equals(idJson.get("aud"))) {
+					allOk &= false;
+				}
+				if(!oVerifier.getState().equals(oVerifier.getSessionState())) {
+					allOk &= false;
+				}
+				
+				if(!oVerifier.getSessionNonce().equals(idJson.get("nonce"))) {
+					allOk &= false;
+				}
+				
+				return allOk ? new Token(idToken, oVerifier.getState()) : null;
+			} catch (JSONException e) {
+				log.error("", e);
+				return null;
+			}
+        }
+    }
+}
diff --git a/src/main/java/org/olat/login/oauth/spi/OpenIdConnectFullConfigurableProvider.java b/src/main/java/org/olat/login/oauth/spi/OpenIdConnectFullConfigurableProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..72c1098ffa4245c5b24bf98ba6de1e46bfe96937
--- /dev/null
+++ b/src/main/java/org/olat/login/oauth/spi/OpenIdConnectFullConfigurableProvider.java
@@ -0,0 +1,179 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.login.oauth.spi;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
+import org.olat.core.util.StringHelper;
+import org.olat.login.oauth.OAuthDisplayName;
+import org.olat.login.oauth.OAuthSPI;
+import org.olat.login.oauth.model.OAuthUser;
+import org.scribe.builder.api.Api;
+import org.scribe.model.Token;
+import org.scribe.oauth.OAuthService;
+
+/**
+ * 
+ * Initial date: 6 oct. 2016<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class OpenIdConnectFullConfigurableProvider implements OAuthSPI, OAuthDisplayName {
+	
+	private static final OLog log = Tracing.createLoggerFor(Google2Provider.class);
+
+	private String name;
+	private String displayName;
+	private String providerName;
+	private String appKey;
+	private String appSecret;
+	private String issuer;
+	private String endPoint;
+	
+	private boolean rootEnabled;
+	
+	@Override
+	public boolean isEnabled() {
+		return true;
+	}
+	
+	@Override
+	public boolean isRootEnabled() {
+		return rootEnabled;
+	}
+	
+	public void setRootEnabled(boolean rootEnabled) {
+		this.rootEnabled = rootEnabled;
+	}
+	
+	@Override
+	public boolean isImplicitWorkflow() {
+		return true;
+	}
+
+	@Override
+	public Api getScribeProvider() {
+		return new OpenIdConnectFullConfigurableApi(this);
+	}
+
+	@Override
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	@Override
+	public String getDisplayName() {
+		return displayName;
+	}
+
+	public void setDisplayName(String displayName) {
+		this.displayName = displayName;
+	}
+
+	@Override
+	public String getProviderName() {
+		return providerName;
+	}
+	
+	public void setProviderName(String providerName) {
+		this.providerName = providerName;
+	}
+
+	@Override
+	public String getIconCSS() {
+		return "o_icon o_icon_provider_" + name;
+	}
+
+	@Override
+	public String getAppKey() {
+		return appKey;
+	}
+	
+	public void setAppKey(String appKey) {
+		this.appKey = appKey;
+	}
+
+	@Override
+	public String getAppSecret() {
+		return appSecret;
+	}
+	
+	public void setAppSecret(String appSecret) {
+		this.appSecret = appSecret;
+	}
+
+	public String getIssuer() {
+		return issuer;
+	}
+
+	public void setIssuer(String issuer) {
+		this.issuer = issuer;
+	}
+
+	public String getEndPoint() {
+		return endPoint;
+	}
+
+	public void setEndPoint(String endPoint) {
+		this.endPoint = endPoint;
+	}
+
+	@Override
+	public String[] getScopes() {
+		return new String[] { "openid", "email" };
+	}
+
+	@Override
+	public OAuthUser getUser(OAuthService service, Token accessToken) {
+		try {
+			String idToken = accessToken.getToken();
+			JSONWebToken token = JSONWebToken.parse(idToken);
+			return parseInfos(token.getPayload());
+		} catch (JSONException e) {
+			log.error("", e);
+			return null;
+		}
+	}
+	
+	public OAuthUser parseInfos(String body) {
+		OAuthUser user = new OAuthUser();
+		
+		try {
+			JSONObject obj = new JSONObject(body);
+			user.setId(getValue(obj, "sub"));
+			user.setEmail(getValue(obj, "sub"));
+		} catch (JSONException e) {
+			log.error("", e);
+		}
+		
+		return user;
+	}
+	
+	private String getValue(JSONObject obj, String property) {
+		String value = obj.optString(property);
+		return StringHelper.containsNonWhitespace(value) ? value : null;
+	}
+}
diff --git a/src/main/java/org/olat/login/oauth/spi/OpenIdConnectProvider.java b/src/main/java/org/olat/login/oauth/spi/OpenIdConnectProvider.java
index 5fa47dd1bb113d4cfabfcb7898d1ea5e331ebc43..97c4340bad64f723c32693cf06de5da400a628fc 100644
--- a/src/main/java/org/olat/login/oauth/spi/OpenIdConnectProvider.java
+++ b/src/main/java/org/olat/login/oauth/spi/OpenIdConnectProvider.java
@@ -63,8 +63,8 @@ public class OpenIdConnectProvider implements OAuthSPI {
 	}
 
 	@Override
-	public Class<? extends Api> getScribeProvider() {
-		return OpenIdConnectApi.class;
+	public Api getScribeProvider() {
+		return new OpenIdConnectApi();
 	}
 
 	@Override
diff --git a/src/main/java/org/olat/login/oauth/spi/TwitterProvider.java b/src/main/java/org/olat/login/oauth/spi/TwitterProvider.java
index 81a0dc30e8813bcdc4ca89de6ff383a832e0a181..0c03269eb3c6f3c5612ebfaede13ca47c6606280 100644
--- a/src/main/java/org/olat/login/oauth/spi/TwitterProvider.java
+++ b/src/main/java/org/olat/login/oauth/spi/TwitterProvider.java
@@ -67,8 +67,8 @@ public class TwitterProvider implements OAuthSPI {
 	}
 
 	@Override
-	public Class<? extends Api> getScribeProvider() {
-		return TwitterApi.SSL.class;
+	public Api getScribeProvider() {
+		return new TwitterApi.SSL();
 	}
 
 	@Override
diff --git a/src/main/java/org/olat/login/oauth/ui/AddOpenIDConnectIFFullConfigurableController.java b/src/main/java/org/olat/login/oauth/ui/AddOpenIDConnectIFFullConfigurableController.java
new file mode 100644
index 0000000000000000000000000000000000000000..9359aec042fdb411e2306436b3acf988dbe37497
--- /dev/null
+++ b/src/main/java/org/olat/login/oauth/ui/AddOpenIDConnectIFFullConfigurableController.java
@@ -0,0 +1,124 @@
+package org.olat.login.oauth.ui;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement;
+import org.olat.core.gui.components.form.flexible.elements.TextElement;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.components.form.flexible.impl.FormEvent;
+import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.Event;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.util.StringHelper;
+import org.olat.login.oauth.OAuthLoginModule;
+import org.olat.login.oauth.OAuthSPI;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 6 oct. 2016<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class AddOpenIDConnectIFFullConfigurableController extends FormBasicController {
+
+	private static final String[] keys = new String[]{ "on" };
+	private static final String[] values = new String[] { "" };
+
+	private TextElement openIdConnectIFName;
+	private TextElement openIdConnectIFDisplayName;
+	private TextElement openIdConnectIFApiKeyEl;
+	private TextElement openIdConnectIFApiSecretEl;
+	private TextElement openIdConnectIFIssuerEl;
+	private TextElement openIdConnectIFAuthorizationEndPointEl;
+	
+	private MultipleSelectionElement openIdConnectIFDefaultEl;
+
+	@Autowired
+	private OAuthLoginModule oauthModule;
+	
+	public AddOpenIDConnectIFFullConfigurableController(UserRequest ureq, WindowControl wControl) {
+		super(ureq, wControl);
+		
+		initForm(ureq);
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		openIdConnectIFDefaultEl = uifactory.addCheckboxesHorizontal("openidconnectif.default.enabled", formLayout, keys, values);
+		openIdConnectIFDefaultEl.addActionListener(FormEvent.ONCHANGE);
+		
+		openIdConnectIFName = uifactory.addTextElement("openidconnectif.name", "openidconnectif.name", 256, "", formLayout);
+		openIdConnectIFDisplayName = uifactory.addTextElement("openidconnectif.displayname", "openidconnectif.displayname", 256, "", formLayout);
+
+		openIdConnectIFApiKeyEl = uifactory.addTextElement("openidconnectif.id", "openidconnectif.api.id", 256, "", formLayout);
+		openIdConnectIFApiSecretEl = uifactory.addTextElement("openidconnectif.secret", "openidconnectif.api.secret", 256, "", formLayout);
+		
+		openIdConnectIFIssuerEl = uifactory.addTextElement("openidconnectif.issuer", "openidconnectif.issuer", 256, "", formLayout);
+		openIdConnectIFIssuerEl.setExampleKey("openidconnectif.issuer.example", null);
+
+		openIdConnectIFAuthorizationEndPointEl = uifactory.addTextElement("openidconnectif.authorization.endpoint", "openidconnectif.authorization.endpoint", 256, "", formLayout);
+		openIdConnectIFAuthorizationEndPointEl.setExampleKey("openidconnectif.authorization.endpoint.example", null);
+	
+		FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("button_layout", getTranslator());
+		formLayout.add(buttonLayout);
+		uifactory.addFormCancelButton("cancel", buttonLayout, ureq, getWindowControl());
+		uifactory.addFormSubmitButton("save", buttonLayout);
+	}
+	
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	protected boolean validateFormLogic(UserRequest ureq) {
+		boolean allOk = true;
+		allOk &= validate(openIdConnectIFName);
+		allOk &= validate(openIdConnectIFDisplayName);
+		allOk &= validate(openIdConnectIFApiKeyEl);
+		allOk &= validate(openIdConnectIFApiSecretEl);
+		allOk &= validate(openIdConnectIFIssuerEl);
+		allOk &= validate(openIdConnectIFAuthorizationEndPointEl);
+		
+		String providerName = openIdConnectIFName.getValue();
+		if(StringHelper.containsNonWhitespace(providerName)) {
+			OAuthSPI existingSpi = oauthModule.getProvider(providerName);
+			if(existingSpi != null) {
+				openIdConnectIFName.setErrorKey("error.duplicate.provider", null);
+				allOk &= false;
+			}
+		}
+
+		return allOk & super.validateFormLogic(ureq);
+	}
+	
+	private boolean validate(TextElement el) {
+		boolean allOk = true;
+		el.clearError();
+		if(!StringHelper.containsNonWhitespace(el.getValue())) {
+			el.setErrorKey("form.legende.mandatory", null);
+			allOk &= false;
+		}
+		return allOk;
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		String providerName = openIdConnectIFName.getValue();
+		String displayName = openIdConnectIFDisplayName.getValue();
+		String issuer = openIdConnectIFIssuerEl.getValue();
+		String endPoint = openIdConnectIFAuthorizationEndPointEl.getValue();
+		String apiKey = openIdConnectIFApiKeyEl.getValue();
+		String apiSecret = openIdConnectIFApiSecretEl.getValue();
+		boolean rootEnabled = openIdConnectIFDefaultEl.isAtLeastSelected(1);
+		oauthModule.setAdditionalOpenIDConnectIF(providerName, displayName, rootEnabled, issuer, endPoint, apiKey, apiSecret);
+		fireEvent(ureq, Event.DONE_EVENT);
+	}
+
+	@Override
+	protected void formCancelled(UserRequest ureq) {
+		fireEvent(ureq, Event.CANCELLED_EVENT);
+	}
+}
diff --git a/src/main/java/org/olat/login/oauth/ui/OAuthAdminController.java b/src/main/java/org/olat/login/oauth/ui/OAuthAdminController.java
index 46b66e0e9b1cb57944abfb55199767acb0681376..1a2cee4a8df1580afaacbcca8465bf54e0f68d4c 100644
--- a/src/main/java/org/olat/login/oauth/ui/OAuthAdminController.java
+++ b/src/main/java/org/olat/login/oauth/ui/OAuthAdminController.java
@@ -19,18 +19,30 @@
  */
 package org.olat.login.oauth.ui;
 
+import java.util.ArrayList;
+import java.util.List;
+
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.form.flexible.FormItem;
 import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.elements.FormLink;
 import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement;
 import org.olat.core.gui.components.form.flexible.elements.TextElement;
 import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
 import org.olat.core.gui.components.form.flexible.impl.FormEvent;
 import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
+import org.olat.core.gui.components.link.Link;
 import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.Event;
 import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController;
+import org.olat.core.gui.control.generic.modal.DialogBoxController;
+import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory;
+import org.olat.core.util.CodeHelper;
 import org.olat.core.util.StringHelper;
 import org.olat.login.oauth.OAuthLoginModule;
+import org.olat.login.oauth.OAuthSPI;
+import org.olat.login.oauth.spi.OpenIdConnectFullConfigurableProvider;
 import org.springframework.beans.factory.annotation.Autowired;
 
 /**
@@ -44,6 +56,7 @@ public class OAuthAdminController extends FormBasicController {
 	private static final String[] keys = new String[]{ "on" };
 	private static final String[] values = new String[] { "" };
 	
+	private FormLink addProviderLink;
 
 	private MultipleSelectionElement userCreationEl;
 	
@@ -75,6 +88,14 @@ public class OAuthAdminController extends FormBasicController {
 	private TextElement openIdConnectIFIssuerEl;
 	private TextElement openIdConnectIFAuthorizationEndPointEl;
 	
+	private FormLayoutContainer customProvidersCont;
+	
+	private CloseableModalController cmc;
+	private DialogBoxController confirmDeleteCtrl;
+	private AddOpenIDConnectIFFullConfigurableController addConfigCtrl;
+	
+	private List<ConfigurableProviderWrapper> providerWrappers = new ArrayList<>();
+	
 	@Autowired
 	private OAuthLoginModule oauthModule;
 	
@@ -250,6 +271,13 @@ public class OAuthAdminController extends FormBasicController {
 			openIdConnectIFDefaultEl.select(keys[0], true);
 		}
 		
+		customProvidersCont = FormLayoutContainer.createBareBoneFormLayout("custom.providers", getTranslator());
+		customProvidersCont.setRootForm(mainForm);
+		formLayout.add(customProvidersCont);
+		
+		//highly configurable providers
+		initCustomProviders();
+		
 		//buttons
 		FormLayoutContainer buttonBonesCont = FormLayoutContainer.createDefaultFormLayout("button_bones", getTranslator());
 		buttonBonesCont.setRootForm(mainForm);
@@ -258,14 +286,39 @@ public class OAuthAdminController extends FormBasicController {
 		FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("button_layout", getTranslator());
 		buttonBonesCont.add(buttonLayout);
 		uifactory.addFormSubmitButton("save", buttonLayout);
+		addProviderLink = uifactory.addFormLink("add.openidconnectif.custom", buttonLayout, Link.BUTTON);
+	}
+	
+	private void initCustomProviders() {
+		// remove old ones
+		for(ConfigurableProviderWrapper providerWrapper:providerWrappers) {
+			FormItemContainer layoutCont = providerWrapper.getLayoutCont();
+			customProvidersCont.remove(layoutCont);	
+		}
+
+		providerWrappers.clear();
+		List<OAuthSPI> configurableSpies = oauthModule.getAllConfigurableSPIs();
+		for(OAuthSPI configurableSpi:configurableSpies) {
+			if(configurableSpi instanceof OpenIdConnectFullConfigurableProvider) {
+				ConfigurableProviderWrapper wrapper =
+						initOpenIDConnectIFFullConfigurableProviders(customProvidersCont, (OpenIdConnectFullConfigurableProvider)configurableSpi);
+				if(wrapper != null) {
+					providerWrappers.add(wrapper);
+				}
+			}
+		}
+	}
+
+	private ConfigurableProviderWrapper initOpenIDConnectIFFullConfigurableProviders(FormItemContainer formLayout, OpenIdConnectFullConfigurableProvider provider) {
+		ConfigurableProviderWrapper wrapper = new ConfigurableProviderWrapper(provider);
+		wrapper.initForm(formLayout);
+		return wrapper;
 	}
 	
 	@Override
 	protected void doDispose() {
 		//
 	}
-	
-	
 
 	@Override
 	protected boolean validateFormLogic(UserRequest ureq) {
@@ -282,6 +335,10 @@ public class OAuthAdminController extends FormBasicController {
 		allOk &= mandatory(adfsEl, adfsApiKeyEl, adfsOAuth2EndpointEl);
 		//open id connect
 		allOk &= mandatory(openIdConnectIFEl, openIdConnectIFAuthorizationEndPointEl, openIdConnectIFApiKeyEl, openIdConnectIFApiSecretEl);
+		
+		for(ConfigurableProviderWrapper wrapper:providerWrappers) {
+			allOk &= wrapper.validateFormLogic();
+		}
 
 		return allOk & super.validateFormLogic(ureq);
 	}
@@ -306,6 +363,25 @@ public class OAuthAdminController extends FormBasicController {
 		
 		return allOk;
 	}
+	
+	private boolean mandatory(TextElement... textEls) {
+		boolean allOk = true;
+		
+		if(textEls != null) {
+			for(int i=textEls.length; i-->0; ) {
+				TextElement textEl = textEls[i];
+				if(textEl != null) {
+					textEl.clearError();
+					if(!StringHelper.containsNonWhitespace(textEl.getValue())) {
+						textEl.setErrorKey("form.legende.mandatory", null);
+						allOk &= false;
+					}
+				}
+			}
+		}
+		
+		return allOk;
+	}
 
 	@Override
 	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
@@ -331,6 +407,14 @@ public class OAuthAdminController extends FormBasicController {
 			openIdConnectIFDefaultEl.setVisible(openIdConnectIFEl.isAtLeastSelected(1));
 			openIdConnectIFApiSecretEl.setVisible(openIdConnectIFEl.isAtLeastSelected(1));
 			openIdConnectIFAuthorizationEndPointEl.setVisible(openIdConnectIFEl.isAtLeastSelected(1));
+		} else if(addProviderLink == source) {
+			doAddOpenIDConnectIFCustom(ureq);
+		} else if(source instanceof FormLink) {
+			FormLink link = (FormLink)source;
+			if("delete".equals(link.getCmd())) {
+				ConfigurableProviderWrapper providerWrapper = (ConfigurableProviderWrapper)link.getUserObject();
+				doConfirmDelete(ureq, providerWrapper);
+			}	
 		}
 		super.formInnerEvent(ureq, source, event);
 	}
@@ -406,5 +490,140 @@ public class OAuthAdminController extends FormBasicController {
 			oauthModule.setOpenIdConnectIFIssuer("");
 			oauthModule.setOpenIdConnectIFAuthorizationEndPoint("");
 		}
+		
+		for(ConfigurableProviderWrapper wrapper:providerWrappers) {
+			wrapper.commit();
+		}
+	}
+	
+	@Override
+	protected void event(UserRequest ureq, Controller source, Event event) {
+		if(addConfigCtrl == source) {
+			if(event == Event.DONE_EVENT) {
+				initCustomProviders();
+			}
+			cmc.deactivate();
+			cleanUp();
+		} else if(confirmDeleteCtrl == source) {
+			if(DialogBoxUIFactory.isOkEvent(event) || DialogBoxUIFactory.isYesEvent(event)) {
+				ConfigurableProviderWrapper providerWrapper = (ConfigurableProviderWrapper)confirmDeleteCtrl.getUserObject();
+				doDelete(providerWrapper);
+			}
+		} else if(cmc == source) {
+			cleanUp();
+		}
+		super.event(ureq, source, event);
+	}
+	
+	private void cleanUp() {
+		removeAsListenerAndDispose(addConfigCtrl);
+		removeAsListenerAndDispose(cmc);
+		addConfigCtrl = null;
+		cmc = null;
+	}
+
+	private void doAddOpenIDConnectIFCustom(UserRequest ureq) {
+		addConfigCtrl = new AddOpenIDConnectIFFullConfigurableController(ureq, getWindowControl());
+		listenTo(addConfigCtrl);
+
+		String title = translate("add.openidconnectif.custom");
+		cmc = new CloseableModalController(getWindowControl(), null, addConfigCtrl.getInitialComponent(), true, title, true);
+		listenTo(cmc);
+		cmc.activate();
+	}
+	
+	private void doConfirmDelete(UserRequest ureq, ConfigurableProviderWrapper providerWrapper) {
+		OAuthSPI spi = providerWrapper.getSpi();
+		String title = translate("confirm.delete.provider.title", new String[]{ spi.getProviderName() });
+		String text = translate("confirm.delete.provider.text", new String[]{ spi.getProviderName() });
+		confirmDeleteCtrl = activateOkCancelDialog(ureq, title, text, confirmDeleteCtrl);
+		confirmDeleteCtrl.setUserObject(providerWrapper);
+	}
+	
+	private void doDelete(ConfigurableProviderWrapper providerWrapper) {
+		OAuthSPI spi = providerWrapper.getSpi();
+		if(spi instanceof OpenIdConnectFullConfigurableProvider) {
+			oauthModule.removeAdditionalOpenIDConnectIF(spi.getProviderName());
+		}
+		initCustomProviders();
+	}
+	
+	public class ConfigurableProviderWrapper {
+		
+		private FormLayoutContainer openIdConnectIFCont;
+		
+		private FormLink deleteButton;
+		private TextElement openIdConnectIFConfName;
+		private TextElement openIdConnectIFConfDisplayName;
+		private TextElement openIdConnectIFConfApiKeyEl;
+		private TextElement openIdConnectIFConfApiSecretEl;
+		private TextElement openIdConnectIFConfIssuerEl;
+		private TextElement openIdConnectIFConfAuthorizationEndPointEl;
+		
+		private final OpenIdConnectFullConfigurableProvider spi;
+		
+		public ConfigurableProviderWrapper(OpenIdConnectFullConfigurableProvider spi) {
+			this.spi = spi;
+		}
+		
+		public OpenIdConnectFullConfigurableProvider getSpi() {
+			return spi;
+		}
+		
+		public FormItemContainer getLayoutCont() {
+			return openIdConnectIFCont;
+		}
+		
+		public void initForm(FormItemContainer container) {
+			String counter = Long.toString(CodeHelper.getRAMUniqueID());
+			openIdConnectIFCont = FormLayoutContainer.createDefaultFormLayout("openidconnectif." + counter, getTranslator());
+			openIdConnectIFCont.setFormTitle(translate("openidconnectif.admin.custom.title", new String[]{ spi.getProviderName() }));
+			openIdConnectIFCont.setFormTitleIconCss("o_icon o_icon_provider_openid");
+			openIdConnectIFCont.setRootForm(mainForm);
+			container.add(openIdConnectIFCont);
+			openIdConnectIFDefaultEl = uifactory.addCheckboxesHorizontal("openidconnectif." + counter + ".default.enabled", "openidconnectif.default.enabled", openIdConnectIFCont, keys, values);
+			if(spi.isRootEnabled()) {
+				openIdConnectIFDefaultEl.select(keys[0], true);
+			}
+			
+			String providerName = spi.getProviderName();
+			openIdConnectIFConfName = uifactory.addTextElement("openidconnectif." + counter + ".name", "openidconnectif.name", 256, providerName, openIdConnectIFCont);
+			openIdConnectIFConfName.setEnabled(false);
+			
+			String displayName = spi.getDisplayName();
+			openIdConnectIFConfDisplayName = uifactory.addTextElement("openidconnectif." + counter + ".displayname", "openidconnectif.displayname", 256, displayName, openIdConnectIFCont);
+			String apiKey = spi.getAppKey();
+			openIdConnectIFConfApiKeyEl = uifactory.addTextElement("openidconnectif." + counter + ".id", "openidconnectif.api.id", 256, apiKey, openIdConnectIFCont);
+			String apiSecret = spi.getAppSecret();
+			openIdConnectIFConfApiSecretEl = uifactory.addTextElement("openidconnectif." + counter + ".secret", "openidconnectif.api.secret", 256, apiSecret, openIdConnectIFCont);
+			String issuer = spi.getIssuer();
+			openIdConnectIFConfIssuerEl = uifactory.addTextElement("openidconnectif." + counter + ".issuer", "openidconnectif.issuer", 256, issuer, openIdConnectIFCont);
+			openIdConnectIFConfIssuerEl.setExampleKey("openidconnectif.issuer.example", null);
+			String endPoint = spi.getEndPoint();
+			openIdConnectIFConfAuthorizationEndPointEl = uifactory.addTextElement("openidconnectif." + counter + ".authorization.endpoint", "openidconnectif.authorization.endpoint", 256, endPoint, openIdConnectIFCont);
+			openIdConnectIFConfAuthorizationEndPointEl.setExampleKey("openidconnectif.authorization.endpoint.example", null);
+
+			deleteButton  = uifactory.addFormLink("delete.".concat(counter), "delete", "delete", null, openIdConnectIFCont, Link.BUTTON);
+			deleteButton.setUserObject(this);
+		}
+
+		protected boolean validateFormLogic() {
+			boolean allOk = true;
+			
+			allOk &= mandatory(openIdConnectIFConfName, openIdConnectIFConfDisplayName, openIdConnectIFConfApiKeyEl, openIdConnectIFConfApiSecretEl,
+					openIdConnectIFConfIssuerEl, openIdConnectIFConfAuthorizationEndPointEl);
+			
+			return allOk;
+		}
+		
+		protected void commit() {
+			String displayName = openIdConnectIFConfDisplayName.getValue();
+			String issuer = openIdConnectIFConfIssuerEl.getValue();
+			String endPoint = openIdConnectIFConfAuthorizationEndPointEl.getValue();
+			String apiKey = openIdConnectIFConfApiKeyEl.getValue();
+			String apiSecret = openIdConnectIFConfApiSecretEl.getValue();
+			boolean rootEnabled = openIdConnectIFDefaultEl.isAtLeastSelected(1);
+			oauthModule.setAdditionalOpenIDConnectIF(spi.getProviderName(), displayName, rootEnabled, issuer, endPoint, apiKey, apiSecret);
+		}
 	}
 }
diff --git a/src/main/java/org/olat/login/oauth/ui/OAuthAuthenticationController.java b/src/main/java/org/olat/login/oauth/ui/OAuthAuthenticationController.java
index 2ba5fdd9ce8ff29c5e417533c3c81bb31f91801e..e0ff4190af60ff62f5cdfdbeecbbc1161b437256 100644
--- a/src/main/java/org/olat/login/oauth/ui/OAuthAuthenticationController.java
+++ b/src/main/java/org/olat/login/oauth/ui/OAuthAuthenticationController.java
@@ -19,6 +19,8 @@
  */
 package org.olat.login.oauth.ui;
 
+import java.util.List;
+
 import javax.servlet.http.HttpSession;
 
 import org.olat.core.gui.UserRequest;
@@ -30,7 +32,12 @@ import org.olat.core.gui.components.form.flexible.impl.FormEvent;
 import org.olat.core.gui.components.link.Link;
 import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.generic.dtabs.Activateable2;
 import org.olat.core.gui.media.MediaResource;
+import org.olat.core.id.context.ContextEntry;
+import org.olat.core.id.context.StateEntry;
+import org.olat.core.util.StringHelper;
+import org.olat.login.oauth.OAuthDisplayName;
 import org.olat.login.oauth.OAuthLoginModule;
 import org.olat.login.oauth.OAuthResource;
 import org.olat.login.oauth.OAuthSPI;
@@ -42,7 +49,7 @@ import org.springframework.beans.factory.annotation.Autowired;
  * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
  *
  */
-public class OAuthAuthenticationController extends FormBasicController {
+public class OAuthAuthenticationController extends FormBasicController implements Activateable2 {
 	
 	@Autowired
 	private OAuthLoginModule oauthModule;
@@ -50,13 +57,30 @@ public class OAuthAuthenticationController extends FormBasicController {
 	public OAuthAuthenticationController(UserRequest ureq, WindowControl wControl) {
 		super(ureq, wControl, "login");
 		initForm(ureq);
+
+		String provider = ureq.getParameter("provider");
+		if(StringHelper.containsNonWhitespace(provider)) {
+			OAuthSPI spi = oauthModule.getProvider(provider);
+			if(spi != null) {
+				redirect(ureq, spi);
+			}
+		}
 	}
 
 	@Override
 	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
 		for(OAuthSPI spi:  oauthModule.getEnableSPIs()) {
-			String spiName = spi.getName();
-			FormLink button = uifactory.addFormLink(spiName, "login", "login." + spiName, null, formLayout, Link.BUTTON);
+			String spiName;
+			int presentation;
+			if(spi instanceof OAuthDisplayName) {
+				spiName = ((OAuthDisplayName)spi).getDisplayName();
+				presentation = Link.BUTTON | Link.NONTRANSLATED;
+			} else {
+				spiName	= "login.".concat(spi.getName());
+				presentation = Link.BUTTON;
+			}
+		
+			FormLink button = uifactory.addFormLink(spiName, "login", spiName, null, formLayout, presentation);
 			button.setIconLeftCSS(spi.getIconCSS());
 			button.setElementCssClass("o_sel_auth_" + spiName);
 			button.setUserObject(spi);
@@ -68,6 +92,11 @@ public class OAuthAuthenticationController extends FormBasicController {
 		//
 	}
 
+	@Override
+	public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) {
+		System.out.println("");
+	}
+
 	@Override
 	protected void formOK(UserRequest ureq) {
 		//
@@ -79,11 +108,15 @@ public class OAuthAuthenticationController extends FormBasicController {
 			FormLink button = (FormLink)source;
 			if("login".equals(button.getCmd())) {
 				OAuthSPI provider = (OAuthSPI)source.getUserObject();
-				HttpSession session = ureq.getHttpReq().getSession();
-				MediaResource redirectResource = new OAuthResource(provider, session);
-				ureq.getDispatchResult().setResultingMediaResource(redirectResource);
+				redirect(ureq, provider);
 			}
 		}
 		super.formInnerEvent(ureq, source, event);
 	}
+	
+	private void redirect(UserRequest ureq, OAuthSPI provider) {
+		HttpSession session = ureq.getHttpReq().getSession();
+		MediaResource redirectResource = new OAuthResource(provider, session);
+		ureq.getDispatchResult().setResultingMediaResource(redirectResource);
+	}
 }
\ No newline at end of file
diff --git a/src/main/java/org/olat/login/oauth/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/login/oauth/ui/_i18n/LocalStrings_de.properties
index 52bff0035b5c6eb133c672643e998cca23f50b9e..dcce2b06681dd5b7a8bcafb5990e87663c18942c 100644
--- a/src/main/java/org/olat/login/oauth/ui/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/login/oauth/ui/_i18n/LocalStrings_de.properties
@@ -1,4 +1,5 @@
 #Thu Jul 21 17:55:01 CEST 2016
+add.openidconnectif.custom=Custom Open ID Connect erstellen
 adfs.admin.title=Active directory federation system
 adfs.admin.title.alt=Active directory federation system
 adfs.api.id=Client ID
@@ -10,9 +11,12 @@ admin.menu.title=Soziale Netzwerke
 admin.menu.title.alt=OAuth und Soziale Netzwerke
 authentication.provider.description=Melden Sie sich \u00FCber Ihr Konto auf einem Ihrer sozialen Netzwerk an
 authentication.provider.linkText=Soziale Netzwerke
+confirm.delete.provider.title=Provider "{0}" l\u00F6schen
+confirm.delete.provider.text=Wollen Sie wirklichen den Provider "{0}" l\u00F6schen?
 disclaimer.title=Disclaimer
 error.access.denied=Sie sind nicht berechtigt auf den OpenOLAT Dienst zuzugreifen (access_denied). Bitte kontaktieren Sie den Systemadministrator.
 error.account.creation=Ihr Konto wurde auf dem OpenOLAT Dienst noch nicht angelegt, dies ist notwendig um sich anmelden zu k\u00F6nnen. Bitte kontaktieren Sie den Systemadministrator. 
+error.duplicate.provider=Dieser Provider existiert schon.
 error.generic=Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es sp\u00E4ter noch einmal.
 error.invalid.grant=Sie sind nicht berechtigt auf den OpenOLAT Dienst zuzugreifen (invalid_grant). Bitte kontaktieren Sie den Systemadministrator.
 error.no.id=Sie konnten nicht identifiziert werden. Bitte kontaktieren Sie den Systemadministrator.
@@ -39,6 +43,7 @@ login.linkedin=LinkedIn
 login.panther=My Hamilton
 login.twitter=Twitter
 oauth.admin.title=OAuth Konfiguration
+openidconnectif.admin.custom.title="{0}" OpenID Connect Implicit Flow
 openidconnectif.admin.title=OpenID Connect Implicit Flow
 openidconnectif.admin.title.alt=OpenID Connect with Implicit Flow
 openidconnectif.api.id=Client Id
@@ -49,6 +54,8 @@ openidconnectif.default.enabled=$\:adfs.default.enabled
 openidconnectif.enabled=Open ID Connect
 openidconnectif.issuer=Issuer
 openidconnectif.issuer.example=https\://frentix.com
+openidconnectif.name=Provider name
+openidconnectif.displayname=Display name
 openidconnectif.wait.message=Sie werden in wenigen Augenblicken zum OpenOLAT Dienst weitergeleitet.
 twitter.admin.title=Twitter Konfiguration
 twitter.api.id=Consumer key (App key)
diff --git a/src/main/java/org/olat/login/oauth/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/login/oauth/ui/_i18n/LocalStrings_en.properties
index 04c86c714f61d8b8ab651b45e7fda35b2baaa54b..67ad49999aa1d7eb9d1dbdae2e410702d9de472f 100644
--- a/src/main/java/org/olat/login/oauth/ui/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/login/oauth/ui/_i18n/LocalStrings_en.properties
@@ -1,4 +1,5 @@
 #Thu Jul 21 17:54:47 CEST 2016
+add.openidconnectif.custom=Add custom Open ID Connect
 adfs.admin.title=Active directory federation system
 adfs.admin.title.alt=Active directory federation system
 adfs.api.id=Client ID
@@ -10,9 +11,12 @@ admin.menu.title=Social providers
 admin.menu.title.alt=OAuth and social providers
 authentication.provider.description=Login with your favorite social media
 authentication.provider.linkText=Social logins
+confirm.delete.provider.title=Delete provider "{0}"
+confirm.delete.provider.text=Do you really want to delete the following provider "{0}"?
 disclaimer.title=Disclaimer
 error.access.denied=You are not authorized to use the OpenOLAT service (access_denied). Please contact the system administrator.
 error.account.creation=Your account is not yet created on the OpenOLAT service. This is required for beeing able to log in. Please contact the system administrator.
+error.duplicate.provider=This provider already exists.
 error.generic=An unexpected error occurred. Please try it again later.
 error.invalid.grant=You are not allowed to access the OpenOLAT service (invalid_grant). Please contact the system administrator.
 error.no.id=We were not able to identify you. Please contact the system administrator.
@@ -49,6 +53,8 @@ openidconnectif.default.enabled=$\:adfs.default.enabled
 openidconnectif.enabled=Open ID Connect
 openidconnectif.issuer=Issuer
 openidconnectif.issuer.example=https\://frentix.com
+openidconnectif.name=Provider name
+openidconnectif.displayname=Display name
 openidconnectif.wait.message=You will be redirected to OpenOLAT momentarily.
 twitter.admin.title=Twitter configuration
 twitter.api.id=Consumer key (App key)