You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2020/04/10 08:28:04 UTC

[syncope] 02/08: [SYNCOPE-160] AuthModule service

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

ilgrosso pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git

commit 426b2f2a332f2246b2888aabd3537bfeb903704d
Author: Matteo Alessandroni <sk...@apache.org>
AuthorDate: Thu Apr 9 12:58:10 2020 +0200

    [SYNCOPE-160] AuthModule service
---
 .../common/lib/auth/AbstractAuthModuleConf.java    |  48 ++
 .../syncope/common/lib/auth/AuthModuleConf.java    |  35 ++
 .../common/lib/auth/GoogleMfaAuthModuleConf.java   |  99 ++++
 .../common/lib/auth/JDBCAuthModuleConf.java        |  99 ++++
 .../common/lib/auth/JaasAuthModuleConf.java        |  88 +++
 .../common/lib/auth/LDAPAuthModuleConf.java        | 126 +++++
 .../common/lib/auth/OIDCAuthModuleConf.java        | 182 +++++++
 .../common/lib/auth/RadiusAuthModuleConf.java      | 172 ++++++
 .../common/lib/auth/SAML2IdPAuthModuleConf.java    | 430 +++++++++++++++
 .../common/lib/auth/StaticAuthModuleConf.java      |  47 ++
 .../common/lib/auth/SyncopeAuthModuleConf.java     |  56 ++
 .../syncope/common/lib/auth/U2FAuthModuleConf.java |  69 +++
 .../syncope/common/lib/auth/package-info.java      |  30 ++
 .../apache/syncope/common/lib/to/AuthModuleTO.java | 132 +++++
 .../common/rest/api/service/AuthModuleService.java | 126 +++++
 .../apache/syncope/core/logic/AuthModuleLogic.java | 131 +++++
 .../rest/cxf/service/AuthModuleServiceImpl.java    |  65 +++
 .../persistence/api/dao/auth/AuthModuleDAO.java    |  37 ++
 .../persistence/api/entity/auth/AuthModule.java    |  48 ++
 .../api/entity/auth/AuthModuleItem.java            |  25 +
 .../persistence/jpa/dao/auth/JPAAuthModuleDAO.java |  68 +++
 .../persistence/jpa/entity/auth/JPAAuthModule.java | 104 ++++
 .../jpa/entity/auth/JPAAuthModuleItem.java         |  69 +++
 .../core/persistence/jpa/inner/AuthModuleTest.java | 502 ++++++++++++++++++
 .../api/data/AuthModuleDataBinder.java             |  32 ++
 .../java/data/AuthModuleDataBinderImpl.java        |  79 +++
 .../apache/syncope/fit/core/AuthModuleITCase.java  | 589 +++++++++++++++++++++
 27 files changed, 3488 insertions(+)

diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/AbstractAuthModuleConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/AbstractAuthModuleConf.java
new file mode 100644
index 0000000..a4399d6
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/AbstractAuthModuleConf.java
@@ -0,0 +1,48 @@
+/*
+ * 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.syncope.common.lib.auth;
+
+import java.io.Serializable;
+import javax.xml.bind.annotation.XmlSeeAlso;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlType
+@XmlSeeAlso({ JaasAuthModuleConf.class, StaticAuthModuleConf.class, LDAPAuthModuleConf.class,
+    OIDCAuthModuleConf.class, GoogleMfaAuthModuleConf.class, SAML2IdPAuthModuleConf.class, U2FAuthModuleConf.class,
+    JDBCAuthModuleConf.class, SyncopeAuthModuleConf.class, RadiusAuthModuleConf.class })
+public abstract class AbstractAuthModuleConf implements Serializable, AuthModuleConf {
+
+    private static final long serialVersionUID = 4153200197344709778L;
+
+    private String name;
+
+    public AbstractAuthModuleConf() {
+        setName(getClass().getName());
+    }
+
+    @Override
+    public final String getName() {
+        return name;
+    }
+
+    public final void setName(final String name) {
+        this.name = name;
+    }
+
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/AuthModuleConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/AuthModuleConf.java
new file mode 100644
index 0000000..28c1357
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/AuthModuleConf.java
@@ -0,0 +1,35 @@
+/*
+ * 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.syncope.common.lib.auth;
+
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import java.io.Serializable;
+import javax.xml.bind.annotation.XmlTransient;
+
+@XmlTransient
+@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
+public interface AuthModuleConf extends Serializable {
+
+    /**
+     * Given name of related authentication module instance.
+     *
+     * @return name of this authentication module instance
+     */
+    String getName();
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/GoogleMfaAuthModuleConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/GoogleMfaAuthModuleConf.java
new file mode 100644
index 0000000..a2e4168
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/GoogleMfaAuthModuleConf.java
@@ -0,0 +1,99 @@
+/*
+ * 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.syncope.common.lib.auth;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "googleMfaAuthModuleConf")
+@XmlType
+public class GoogleMfaAuthModuleConf extends AbstractAuthModuleConf {
+
+    private static final long serialVersionUID = -7883257599139312426L;
+
+    /**
+     * Issuer used in the barcode when dealing with device registration events.
+     * Used in the registration URL to identify CAS.
+     */
+    private String issuer = "Syncope";
+
+    /**
+     * Label used in the barcode when dealing with device registration events.
+     * Used in the registration URL to identify CAS.
+     */
+    private String label = "Syncope";
+
+    /**
+     * Length of the generated code.
+     */
+    private int codeDigits = 6;
+
+    /**
+     * The expiration time of the generated code in seconds.
+     */
+    private long timeStepSize = 30;
+
+    /**
+     * Since TOTP passwords are time-based, it is essential that
+     * the clock of both the server and
+     * the client are synchronised within
+     * the tolerance defined here as the window size.
+     */
+    private int windowSize = 3;
+
+    public String getIssuer() {
+        return issuer;
+    }
+
+    public void setIssuer(final String issuer) {
+        this.issuer = issuer;
+    }
+
+    public String getLabel() {
+        return label;
+    }
+
+    public void setLabel(final String label) {
+        this.label = label;
+    }
+
+    public int getCodeDigits() {
+        return codeDigits;
+    }
+
+    public void setCodeDigits(final int codeDigits) {
+        this.codeDigits = codeDigits;
+    }
+
+    public long getTimeStepSize() {
+        return timeStepSize;
+    }
+
+    public void setTimeStepSize(final long timeStepSize) {
+        this.timeStepSize = timeStepSize;
+    }
+
+    public int getWindowSize() {
+        return windowSize;
+    }
+
+    public void setWindowSize(final int windowSize) {
+        this.windowSize = windowSize;
+    }
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/JDBCAuthModuleConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/JDBCAuthModuleConf.java
new file mode 100644
index 0000000..0141078
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/JDBCAuthModuleConf.java
@@ -0,0 +1,99 @@
+/*
+ * 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.syncope.common.lib.auth;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import javax.xml.bind.annotation.XmlType;
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement(name = "jdbcAuthModuleConf")
+@XmlType
+public class JDBCAuthModuleConf extends AbstractAuthModuleConf {
+
+    private static final long serialVersionUID = 8383233437907219385L;
+
+    /**
+     * SQL query to execute. Example: {@code SELECT * FROM table WHERE name=?}.
+     */
+    private String sql;
+
+    /**
+     * Password field/column name to retrieve.
+     */
+    private String fieldPassword;
+
+    /**
+     * Boolean field that should indicate whether the account is expired.
+     */
+    private String fieldExpired;
+
+    /**
+     * Boolean field that should indicate whether the account is disabled.
+     */
+    private String fieldDisabled;
+
+    /**
+     * List of column names to fetch as user attributes.
+     */
+    private final List<String> principalAttributeList = new ArrayList<>();
+
+    public String getSql() {
+        return sql;
+    }
+
+    public void setSql(final String sql) {
+        this.sql = sql;
+    }
+
+    public String getFieldPassword() {
+        return fieldPassword;
+    }
+
+    public void setFieldPassword(final String fieldPassword) {
+        this.fieldPassword = fieldPassword;
+    }
+
+    public String getFieldExpired() {
+        return fieldExpired;
+    }
+
+    public void setFieldExpired(final String fieldExpired) {
+        this.fieldExpired = fieldExpired;
+    }
+
+    public String getFieldDisabled() {
+        return fieldDisabled;
+    }
+
+    public void setFieldDisabled(final String fieldDisabled) {
+        this.fieldDisabled = fieldDisabled;
+    }
+
+    @XmlElementWrapper(name = "principalAttributeList")
+    @XmlElement(name = "principalAttributeList")
+    @JsonProperty("principalAttributeList")
+    public List<String> getPrincipalAttributeList() {
+        return principalAttributeList;
+    }
+
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/JaasAuthModuleConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/JaasAuthModuleConf.java
new file mode 100644
index 0000000..b8627ab
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/JaasAuthModuleConf.java
@@ -0,0 +1,88 @@
+/*
+ * 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.syncope.common.lib.auth;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "jaasAuthModuleConf")
+@XmlType
+public class JaasAuthModuleConf extends AbstractAuthModuleConf {
+
+    private static final long serialVersionUID = -7775771400318503131L;
+
+    /**
+     * The realm that contains the login module information.
+     */
+    private String realm;
+
+    /**
+     * System property value to overwrite the realm in krb5 config.
+     */
+    private String kerberosRealmSystemProperty;
+
+    /**
+     * System property value to overwrite the kdc in krb5 config.
+     */
+    private String kerberosKdcSystemProperty;
+
+    private String loginConfigType;
+
+    private String loginConfigurationFile;
+
+    public String getRealm() {
+        return realm;
+    }
+
+    public void setRealm(final String realm) {
+        this.realm = realm;
+    }
+
+    public String getKerberosRealmSystemProperty() {
+        return kerberosRealmSystemProperty;
+    }
+
+    public void setKerberosRealmSystemProperty(final String kerberosRealmSystemProperty) {
+        this.kerberosRealmSystemProperty = kerberosRealmSystemProperty;
+    }
+
+    public String getKerberosKdcSystemProperty() {
+        return kerberosKdcSystemProperty;
+    }
+
+    public void setKerberosKdcSystemProperty(final String kerberosKdcSystemProperty) {
+        this.kerberosKdcSystemProperty = kerberosKdcSystemProperty;
+    }
+
+    public String getLoginConfigType() {
+        return loginConfigType;
+    }
+
+    public void setLoginConfigType(final String loginConfigType) {
+        this.loginConfigType = loginConfigType;
+    }
+
+    public String getLoginConfigurationFile() {
+        return loginConfigurationFile;
+    }
+
+    public void setLoginConfigurationFile(final String loginConfigurationFile) {
+        this.loginConfigurationFile = loginConfigurationFile;
+    }
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/LDAPAuthModuleConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/LDAPAuthModuleConf.java
new file mode 100644
index 0000000..afd7f90
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/LDAPAuthModuleConf.java
@@ -0,0 +1,126 @@
+/*
+ * 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.syncope.common.lib.auth;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "ldapAuthModuleConf")
+@XmlType
+public class LDAPAuthModuleConf extends AbstractAuthModuleConf {
+
+    private static final long serialVersionUID = -471527731042579422L;
+
+    protected String searchFilter;
+
+    /**
+     * The attribute value that should be used
+     * for the authenticated username, upon a successful authentication
+     * attempt.
+     */
+    private String userIdAttribute;
+
+    /**
+     * Whether subtree searching is allowed.
+     */
+    private boolean subtreeSearch = true;
+
+    private String ldapUrl;
+
+    /**
+     * The bind DN to use when connecting to LDAP.
+     * LDAP connection configuration injected into the LDAP connection pool
+     * can be initialized with the following parameters:
+     * <ul>
+     * <li>{@code bindDn/bindCredential} provided - Use the provided credentials
+     * to bind when initializing connections.</li>
+     * <li>{@code bindDn/bindCredential} set to {@code *} - Use a fast-bind
+     * strategy to initialize the pool.</li>
+     * <li>{@code bindDn/bindCredential} set to blank - Skip connection
+     * initializing; perform operations anonymously.</li>
+     * <li>SASL mechanism provided - Use the given SASL mechanism
+     * to bind when initializing connections. </li>
+     * </ul>
+     */
+    private String bindDn;
+
+    /**
+     * The bind credential to use when connecting to LDAP.
+     */
+    private String bindCredential;
+
+    private String baseDn;
+
+    public String getSearchFilter() {
+        return searchFilter;
+    }
+
+    public void setSearchFilter(final String searchFilter) {
+        this.searchFilter = searchFilter;
+    }
+
+    public String getUserIdAttribute() {
+        return userIdAttribute;
+    }
+
+    public void setUserIdAttribute(final String userIdAttribute) {
+        this.userIdAttribute = userIdAttribute;
+    }
+
+    public boolean isSubtreeSearch() {
+        return subtreeSearch;
+    }
+
+    public void setSubtreeSearch(final boolean subtreeSearch) {
+        this.subtreeSearch = subtreeSearch;
+    }
+
+    public String getLdapUrl() {
+        return ldapUrl;
+    }
+
+    public void setLdapUrl(final String ldapUrl) {
+        this.ldapUrl = ldapUrl;
+    }
+
+    public String getBindDn() {
+        return bindDn;
+    }
+
+    public void setBindDn(final String bindDn) {
+        this.bindDn = bindDn;
+    }
+
+    public String getBindCredential() {
+        return bindCredential;
+    }
+
+    public void setBindCredential(final String bindCredential) {
+        this.bindCredential = bindCredential;
+    }
+
+    public String getBaseDn() {
+        return baseDn;
+    }
+
+    public void setBaseDn(final String baseDn) {
+        this.baseDn = baseDn;
+    }
+
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/OIDCAuthModuleConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/OIDCAuthModuleConf.java
new file mode 100644
index 0000000..d59b34d
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/OIDCAuthModuleConf.java
@@ -0,0 +1,182 @@
+/*
+ * 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.syncope.common.lib.auth;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import javax.xml.bind.annotation.XmlType;
+import java.util.HashMap;
+import java.util.Map;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import org.apache.syncope.common.lib.jaxb.XmlGenericMapAdapter;
+
+@XmlRootElement(name = "oidcAuthModuleConf")
+@XmlType
+public class OIDCAuthModuleConf extends AbstractAuthModuleConf {
+
+    private static final long serialVersionUID = -471527731042579422L;
+
+    /**
+     * The client id.
+     */
+    private String id;
+
+    /**
+     * The client secret.
+     */
+    private String secret;
+
+    /**
+     * The attribute value that should be used
+     * for the authenticated username, upon a successful authentication
+     * attempt.
+     */
+    private String userIdAttribute;
+
+    private String discoveryUri;
+
+    /**
+     * Whether an initial nonce should be to used
+     * initially for replay attack mitigation.
+     */
+    private boolean useNonce;
+
+    /**
+     * Requested scope(s).
+     */
+    private String scope;
+
+    /**
+     * The JWS algorithm to use forcefully when validating ID tokens.
+     * If none is defined, the first algorithm from metadata will be used.
+     */
+    private String preferredJwsAlgorithm;
+
+    /**
+     * Clock skew in order to account for drift, when validating id tokens.
+     */
+    private int maxClockSkew;
+
+    /**
+     * Custom parameters to send along in authZ requests, etc.
+     */
+    @XmlJavaTypeAdapter(XmlGenericMapAdapter.class)
+    private final Map<String, String> customParams = new HashMap<>(0);
+
+    /**
+     * The response mode specifies how the result of the authorization request is formatted.
+     * Possible values includes "query", "fragment", "form_post", or "web_message"
+     */
+    private String responseMode;
+
+    /**
+     * The response type tells the authorization server which grant to execute.
+     * Possibles values includes "code", "token" or "id_token".
+     */
+    private String responseType;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(final String id) {
+        this.id = id;
+    }
+
+    public String getSecret() {
+        return secret;
+    }
+
+    public void setSecret(final String secret) {
+        this.secret = secret;
+    }
+
+    public String getUserIdAttribute() {
+        return userIdAttribute;
+    }
+
+    public void setUserIdAttribute(final String userIdAttribute) {
+        this.userIdAttribute = userIdAttribute;
+    }
+
+    public String getDiscoveryUri() {
+        return discoveryUri;
+    }
+
+    public void setDiscoveryUri(final String discoveryUri) {
+        this.discoveryUri = discoveryUri;
+    }
+
+    public boolean isUseNonce() {
+        return useNonce;
+    }
+
+    public void setUseNonce(final boolean useNonce) {
+        this.useNonce = useNonce;
+    }
+
+    public String getScope() {
+        return scope;
+    }
+
+    public void setScope(final String scope) {
+        this.scope = scope;
+    }
+
+    public String getPreferredJwsAlgorithm() {
+        return preferredJwsAlgorithm;
+    }
+
+    public void setPreferredJwsAlgorithm(final String preferredJwsAlgorithm) {
+        this.preferredJwsAlgorithm = preferredJwsAlgorithm;
+    }
+
+    public int getMaxClockSkew() {
+        return maxClockSkew;
+    }
+
+    public void setMaxClockSkew(final int maxClockSkew) {
+        this.maxClockSkew = maxClockSkew;
+    }
+
+    @XmlElementWrapper(name = "customParams")
+    @XmlElement(name = "customParam")
+    @JsonProperty("customParams")
+    public Map<String, String> getCustomParams() {
+        return customParams;
+    }
+
+    public String getResponseMode() {
+        return responseMode;
+    }
+
+    public void setResponseMode(final String responseMode) {
+        this.responseMode = responseMode;
+    }
+
+    public String getResponseType() {
+        return responseType;
+    }
+
+    public void setResponseType(final String responseType) {
+        this.responseType = responseType;
+    }
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/RadiusAuthModuleConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/RadiusAuthModuleConf.java
new file mode 100644
index 0000000..7cd543c
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/RadiusAuthModuleConf.java
@@ -0,0 +1,172 @@
+/*
+ * 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.syncope.common.lib.auth;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "radiusAuthModuleConf")
+@XmlType
+public class RadiusAuthModuleConf extends AbstractAuthModuleConf {
+
+    private static final long serialVersionUID = -2235771400318503131L;
+
+    /**
+     * Radius protocol to use when communicating with the server.
+     */
+    private String protocol = "EAP_MSCHAPv2";
+
+    private String inetAddress;
+
+    private String sharedSecret;
+
+    private int socketTimeout;
+
+    private int authenticationPort = 1812;
+
+    private int accountingPort = 1813;
+
+    private int retries = 3;
+
+    private String nasIdentifier;
+
+    private long nasPort = -1;
+
+    private long nasPortId = -1;
+
+    private long nasRealPort = -1;
+
+    private int nasPortType = -1;
+
+    private String nasIpAddress;
+
+    private String nasIpv6Address;
+
+    public String getProtocol() {
+        return protocol;
+    }
+
+    public void setProtocol(final String protocol) {
+        this.protocol = protocol;
+    }
+
+    public int getRetries() {
+        return retries;
+    }
+
+    public void setRetries(final int retries) {
+        this.retries = retries;
+    }
+
+    public String getNasIdentifier() {
+        return nasIdentifier;
+    }
+
+    public void setNasIdentifier(final String nasIdentifier) {
+        this.nasIdentifier = nasIdentifier;
+    }
+
+    public long getNasPort() {
+        return nasPort;
+    }
+
+    public void setNasPort(final long nasPort) {
+        this.nasPort = nasPort;
+    }
+
+    public long getNasPortId() {
+        return nasPortId;
+    }
+
+    public void setNasPortId(final long nasPortId) {
+        this.nasPortId = nasPortId;
+    }
+
+    public long getNasRealPort() {
+        return nasRealPort;
+    }
+
+    public void setNasRealPort(final long nasRealPort) {
+        this.nasRealPort = nasRealPort;
+    }
+
+    public int getNasPortType() {
+        return nasPortType;
+    }
+
+    public void setNasPortType(final int nasPortType) {
+        this.nasPortType = nasPortType;
+    }
+
+    public String getNasIpAddress() {
+        return nasIpAddress;
+    }
+
+    public void setNasIpAddress(final String nasIpAddress) {
+        this.nasIpAddress = nasIpAddress;
+    }
+
+    public String getNasIpv6Address() {
+        return nasIpv6Address;
+    }
+
+    public void setNasIpv6Address(final String nasIpv6Address) {
+        this.nasIpv6Address = nasIpv6Address;
+    }
+
+    public String getInetAddress() {
+        return inetAddress;
+    }
+
+    public void setInetAddress(final String inetAddress) {
+        this.inetAddress = inetAddress;
+    }
+
+    public String getSharedSecret() {
+        return sharedSecret;
+    }
+
+    public void setSharedSecret(final String sharedSecret) {
+        this.sharedSecret = sharedSecret;
+    }
+
+    public int getSocketTimeout() {
+        return socketTimeout;
+    }
+
+    public void setSocketTimeout(final int socketTimeout) {
+        this.socketTimeout = socketTimeout;
+    }
+
+    public int getAuthenticationPort() {
+        return authenticationPort;
+    }
+
+    public void setAuthenticationPort(final int authenticationPort) {
+        this.authenticationPort = authenticationPort;
+    }
+
+    public int getAccountingPort() {
+        return accountingPort;
+    }
+
+    public void setAccountingPort(final int accountingPort) {
+        this.accountingPort = accountingPort;
+    }
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/SAML2IdPAuthModuleConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/SAML2IdPAuthModuleConf.java
new file mode 100644
index 0000000..03b6aed
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/SAML2IdPAuthModuleConf.java
@@ -0,0 +1,430 @@
+/*
+ * 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.syncope.common.lib.auth;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "saml2IdPAuthModuleConf")
+@XmlType
+public class SAML2IdPAuthModuleConf extends AbstractAuthModuleConf {
+
+    private static final long serialVersionUID = -471527731042579422L;
+
+    /**
+     * The attribute value that should be used
+     * for the authenticated username, upon a successful authentication
+     * attempt.
+     */
+    private String userIdAttribute;
+
+    /**
+     * The destination binding to use
+     * when creating authentication requests.
+     */
+    private String destinationBinding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect";
+
+    /**
+     * The password to use when generating the SP keystore.
+     */
+    private String keystorePassword;
+
+    /**
+     * The password to use when generating the private key for the SP keystore.
+     */
+    private String privateKeyPassword;
+
+    /**
+     * Location of the keystore to use and generate the SP keystore.
+     */
+    private String keystorePath;
+
+    /**
+     * The metadata location of the identity provider that is to handle authentications.
+     */
+    private String identityProviderMetadataPath;
+
+    /**
+     * Flag to indicate whether the allow-create flags
+     * for nameid policies should be set to true, false or ignored/defined.
+     * Accepted values are true, false or undefined.
+     */
+    private String nameIdPolicyAllowCreate = "undefined";
+
+    /**
+     * Once you have an authenticated session on the identity provider, usually it won't prompt you again to enter your
+     * credentials and it will automatically generate a new assertion for you. By default, the SAML client
+     * will accept assertions based on a previous authentication for one hour.
+     * You can adjust this behavior by modifying this setting. The unit of time here is seconds.
+     */
+    private int maximumAuthenticationLifetime = 3600;
+
+    /**
+     * Maximum skew in seconds between SP and IDP clocks.
+     * This skew is added onto the {@code NotOnOrAfter} field in seconds
+     * for the SAML response validation.
+     */
+    private int acceptedSkew = 300;
+
+    /**
+     * The entity id of the SP that is used in the SP metadata generation process.
+     */
+    private String serviceProviderEntityId;
+
+    /**
+     * Location of the SP metadata to use and generate.
+     */
+    private String serviceProviderMetadataPath;
+
+    /**
+     * Whether authentication requests should be tagged as forced auth.
+     */
+    private boolean forceAuth;
+
+    /**
+     * Whether authentication requests should be tagged as passive.
+     */
+    private boolean passive;
+
+    /**
+     * Requested authentication context class in authn requests.
+     */
+    private final List<String> authnContextClassRefs = new ArrayList<>(0);
+
+    /**
+     * Specifies the comparison rule that should be used to evaluate the specified authentication methods.
+     * For example, if exact is specified, the authentication method used must match one of the authentication
+     * methods specified by the AuthnContextClassRef elements.
+     * AuthContextClassRef element require comparison rule to be used to evaluate the specified
+     * authentication methods. If not explicitly specified "exact" rule will be used by default.
+     * Other acceptable values are minimum, maximum, better.
+     */
+    private String authnContextComparisonType = "exact";
+
+    /**
+     * The key alias used in the keystore.
+     */
+    private String keystoreAlias;
+
+    /**
+     * NameID policy to request in the authentication requests.
+     */
+    private String nameIdPolicyFormat;
+
+    /**
+     * Whether metadata should be marked to request sign assertions.
+     */
+    private boolean wantsAssertionsSigned;
+
+    /**
+     * AttributeConsumingServiceIndex attribute of AuthnRequest element.
+     * The given index points out a specific AttributeConsumingService structure, declared into the
+     * Service Provider (SP)'s metadata, to be used to specify all the attributes that the Service Provider
+     * is asking to be released within the authentication assertion returned by the Identity Provider (IdP).
+     * This attribute won't be sent with the request unless a positive value (including 0) is defined.
+     */
+    private int attributeConsumingServiceIndex;
+
+    /**
+     * Allows the SAML client to select a specific ACS url from the metadata, if defined.
+     * A negative value de-activates the selection process and is the default.
+     */
+    private int assertionConsumerServiceIndex = -1;
+
+    /**
+     * Whether name qualifiers should be produced
+     * in the final saml response.
+     */
+    private boolean useNameQualifier = true;
+
+    /**
+     * Whether or not SAML SP metadata should be signed when generated.
+     */
+    private boolean signServiceProviderMetadata;
+
+    /**
+     * Whether or not the authnRequest should be signed.
+     */
+    private boolean signAuthnRequest;
+
+    /**
+     * Whether or not the Logout Request sent from the SP should be signed.
+     */
+    private boolean signServiceProviderLogoutRequest;
+
+    /**
+     * Collection of signing signature blacklisted algorithms, if any, to override the global defaults.
+     */
+    private final List<String> blackListedSignatureSigningAlgorithms = new ArrayList<>(0);
+
+    /**
+     * Collection of signing signature algorithms, if any, to override the global defaults.
+     */
+    private final List<String> signatureAlgorithms = new ArrayList<>(0);
+
+    /**
+     * Collection of signing signature reference digest methods, if any, to override the global defaults.
+     */
+    private final List<String> signatureReferenceDigestMethods = new ArrayList<>(0);
+
+    /**
+     * The signing signature canonicalization algorithm, if any, to override the global defaults.
+     */
+    private String signatureCanonicalizationAlgorithm;
+
+    /**
+     * Provider name set for the saml authentication request.
+     * Sets the human-readable name of the requester for use by
+     * the presenter's user agent or the identity provider.
+     */
+    private String providerName;
+
+    public String getUserIdAttribute() {
+        return userIdAttribute;
+    }
+
+    public void setUserIdAttribute(final String userIdAttribute) {
+        this.userIdAttribute = userIdAttribute;
+    }
+
+    public String getDestinationBinding() {
+        return destinationBinding;
+    }
+
+    public void setDestinationBinding(final String destinationBinding) {
+        this.destinationBinding = destinationBinding;
+    }
+
+    public String getKeystorePassword() {
+        return keystorePassword;
+    }
+
+    public void setKeystorePassword(final String keystorePassword) {
+        this.keystorePassword = keystorePassword;
+    }
+
+    public String getPrivateKeyPassword() {
+        return privateKeyPassword;
+    }
+
+    public void setPrivateKeyPassword(final String privateKeyPassword) {
+        this.privateKeyPassword = privateKeyPassword;
+    }
+
+    public String getKeystorePath() {
+        return keystorePath;
+    }
+
+    public void setKeystorePath(final String keystorePath) {
+        this.keystorePath = keystorePath;
+    }
+
+    public String getIdentityProviderMetadataPath() {
+        return identityProviderMetadataPath;
+    }
+
+    public void setIdentityProviderMetadataPath(final String identityProviderMetadataPath) {
+        this.identityProviderMetadataPath = identityProviderMetadataPath;
+    }
+
+    public int getMaximumAuthenticationLifetime() {
+        return maximumAuthenticationLifetime;
+    }
+
+    public void setMaximumAuthenticationLifetime(final int maximumAuthenticationLifetime) {
+        this.maximumAuthenticationLifetime = maximumAuthenticationLifetime;
+    }
+
+    public int getAcceptedSkew() {
+        return acceptedSkew;
+    }
+
+    public void setAcceptedSkew(final int acceptedSkew) {
+        this.acceptedSkew = acceptedSkew;
+    }
+
+    public String getServiceProviderEntityId() {
+        return serviceProviderEntityId;
+    }
+
+    public void setServiceProviderEntityId(final String serviceProviderEntityId) {
+        this.serviceProviderEntityId = serviceProviderEntityId;
+    }
+
+    public String getServiceProviderMetadataPath() {
+        return serviceProviderMetadataPath;
+    }
+
+    public void setServiceProviderMetadataPath(final String serviceProviderMetadataPath) {
+        this.serviceProviderMetadataPath = serviceProviderMetadataPath;
+    }
+
+    public boolean isForceAuth() {
+        return forceAuth;
+    }
+
+    public void setForceAuth(final boolean forceAuth) {
+        this.forceAuth = forceAuth;
+    }
+
+    public boolean isPassive() {
+        return passive;
+    }
+
+    public void setPassive(final boolean passive) {
+        this.passive = passive;
+    }
+
+    public String getNameIdPolicyAllowCreate() {
+        return nameIdPolicyAllowCreate;
+    }
+
+    public void setNameIdPolicyAllowCreate(final String nameIdPolicyAllowCreate) {
+        this.nameIdPolicyAllowCreate = nameIdPolicyAllowCreate;
+    }
+
+    @XmlElementWrapper(name = "authnContextClassRefs")
+    @XmlElement(name = "authnContextClassRef")
+    @JsonProperty("authnContextClassRefs")
+    public List<String> getAuthnContextClassRefs() {
+        return authnContextClassRefs;
+    }
+
+    public String getAuthnContextComparisonType() {
+        return authnContextComparisonType;
+    }
+
+    public void setAuthnContextComparisonType(final String authnContextComparisonType) {
+        this.authnContextComparisonType = authnContextComparisonType;
+    }
+
+    public String getKeystoreAlias() {
+        return keystoreAlias;
+    }
+
+    public void setKeystoreAlias(final String keystoreAlias) {
+        this.keystoreAlias = keystoreAlias;
+    }
+
+    public String getNameIdPolicyFormat() {
+        return nameIdPolicyFormat;
+    }
+
+    public void setNameIdPolicyFormat(final String nameIdPolicyFormat) {
+        this.nameIdPolicyFormat = nameIdPolicyFormat;
+    }
+
+    public boolean isWantsAssertionsSigned() {
+        return wantsAssertionsSigned;
+    }
+
+    public void setWantsAssertionsSigned(final boolean wantsAssertionsSigned) {
+        this.wantsAssertionsSigned = wantsAssertionsSigned;
+    }
+
+    public int getAttributeConsumingServiceIndex() {
+        return attributeConsumingServiceIndex;
+    }
+
+    public void setAttributeConsumingServiceIndex(final int attributeConsumingServiceIndex) {
+        this.attributeConsumingServiceIndex = attributeConsumingServiceIndex;
+    }
+
+    public int getAssertionConsumerServiceIndex() {
+        return assertionConsumerServiceIndex;
+    }
+
+    public void setAssertionConsumerServiceIndex(final int assertionConsumerServiceIndex) {
+        this.assertionConsumerServiceIndex = assertionConsumerServiceIndex;
+    }
+
+    public boolean isUseNameQualifier() {
+        return useNameQualifier;
+    }
+
+    public void setUseNameQualifier(final boolean useNameQualifier) {
+        this.useNameQualifier = useNameQualifier;
+    }
+
+    public boolean isSignServiceProviderMetadata() {
+        return signServiceProviderMetadata;
+    }
+
+    public void setSignServiceProviderMetadata(final boolean signServiceProviderMetadata) {
+        this.signServiceProviderMetadata = signServiceProviderMetadata;
+    }
+
+    public boolean isSignAuthnRequest() {
+        return signAuthnRequest;
+    }
+
+    public void setSignAuthnRequest(final boolean signAuthnRequest) {
+        this.signAuthnRequest = signAuthnRequest;
+    }
+
+    public boolean isSignServiceProviderLogoutRequest() {
+        return signServiceProviderLogoutRequest;
+    }
+
+    public void setSignServiceProviderLogoutRequest(final boolean signServiceProviderLogoutRequest) {
+        this.signServiceProviderLogoutRequest = signServiceProviderLogoutRequest;
+    }
+
+    @XmlElementWrapper(name = "blackListedSignatureSigningAlgorithms")
+    @XmlElement(name = "blackListedSignatureSigningAlgorithm")
+    @JsonProperty("blackListedSignatureSigningAlgorithms")
+    public List<String> getBlackListedSignatureSigningAlgorithms() {
+        return blackListedSignatureSigningAlgorithms;
+    }
+
+    @XmlElementWrapper(name = "signatureAlgorithms")
+    @XmlElement(name = "signatureAlgorithm")
+    @JsonProperty("signatureAlgorithms")
+    public List<String> getSignatureAlgorithms() {
+        return signatureAlgorithms;
+    }
+
+    @XmlElementWrapper(name = "signatureReferenceDigestMethods")
+    @XmlElement(name = "signatureReferenceDigestMethod")
+    @JsonProperty("signatureReferenceDigestMethods")
+    public List<String> getSignatureReferenceDigestMethods() {
+        return signatureReferenceDigestMethods;
+    }
+
+    public String getSignatureCanonicalizationAlgorithm() {
+        return signatureCanonicalizationAlgorithm;
+    }
+
+    public void setSignatureCanonicalizationAlgorithm(final String signatureCanonicalizationAlgorithm) {
+        this.signatureCanonicalizationAlgorithm = signatureCanonicalizationAlgorithm;
+    }
+
+    public String getProviderName() {
+        return providerName;
+    }
+
+    public void setProviderName(final String providerName) {
+        this.providerName = providerName;
+    }
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/StaticAuthModuleConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/StaticAuthModuleConf.java
new file mode 100644
index 0000000..60d2751
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/StaticAuthModuleConf.java
@@ -0,0 +1,47 @@
+/*
+ * 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.syncope.common.lib.auth;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import javax.xml.bind.annotation.XmlType;
+import java.util.HashMap;
+import java.util.Map;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import org.apache.syncope.common.lib.jaxb.XmlGenericMapAdapter;
+
+@XmlRootElement(name = "staticAuthModuleConf")
+@XmlType
+public class StaticAuthModuleConf extends AbstractAuthModuleConf {
+
+    private static final long serialVersionUID = -7775771400318503131L;
+
+    @XmlJavaTypeAdapter(XmlGenericMapAdapter.class)
+    private final Map<String, String> users = new HashMap<>();
+
+    @XmlElementWrapper(name = "users")
+    @XmlElement(name = "user")
+    @JsonProperty("users")
+    public Map<String, String> getUsers() {
+        return users;
+    }
+
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/SyncopeAuthModuleConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/SyncopeAuthModuleConf.java
new file mode 100644
index 0000000..2ac2192
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/SyncopeAuthModuleConf.java
@@ -0,0 +1,56 @@
+/*
+ * 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.syncope.common.lib.auth;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "syncopeAuthModuleConf")
+@XmlType
+public class SyncopeAuthModuleConf extends AbstractAuthModuleConf {
+
+    private static final long serialVersionUID = -3334329948161152222L;
+
+    /**
+     * Syncope domain used for authentication, etc.
+     */
+    private String domain = "Master";
+
+    /**
+     * Syncope instance URL primary used for REST.
+     */
+    private String url;
+
+    public String getDomain() {
+        return domain;
+    }
+
+    public void setDomain(final String domain) {
+        this.domain = domain;
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+    public void setUrl(final String url) {
+        this.url = url;
+    }
+
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/U2FAuthModuleConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/U2FAuthModuleConf.java
new file mode 100644
index 0000000..cc127da
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/U2FAuthModuleConf.java
@@ -0,0 +1,69 @@
+/*
+ * 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.syncope.common.lib.auth;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "u2fAuthModuleConf")
+@XmlType
+public class U2FAuthModuleConf extends AbstractAuthModuleConf {
+
+    private static final long serialVersionUID = -1235771400318503131L;
+
+    private long expireRegistrations = 30;
+
+    private String expireRegistrationsTimeUnit = "SECONDS";
+
+    private long expireDevices = 30;
+
+    private String expireDevicesTimeUnit = "DAYS";
+
+    public long getExpireRegistrations() {
+        return expireRegistrations;
+    }
+
+    public void setExpireRegistrations(final long expireRegistrations) {
+        this.expireRegistrations = expireRegistrations;
+    }
+
+    public String getExpireRegistrationsTimeUnit() {
+        return expireRegistrationsTimeUnit;
+    }
+
+    public void setExpireRegistrationsTimeUnit(final String expireRegistrationsTimeUnit) {
+        this.expireRegistrationsTimeUnit = expireRegistrationsTimeUnit;
+    }
+
+    public long getExpireDevices() {
+        return expireDevices;
+    }
+
+    public void setExpireDevices(final long expireDevices) {
+        this.expireDevices = expireDevices;
+    }
+
+    public String getExpireDevicesTimeUnit() {
+        return expireDevicesTimeUnit;
+    }
+
+    public void setExpireDevicesTimeUnit(final String expireDevicesTimeUnit) {
+        this.expireDevicesTimeUnit = expireDevicesTimeUnit;
+    }
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/package-info.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/package-info.java
new file mode 100644
index 0000000..e3765a0
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/package-info.java
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+@XmlSchema(namespace = SyncopeConstants.NS)
+@XmlJavaTypeAdapters({ @XmlJavaTypeAdapter(type = Date.class, value = DateAdapter.class), })
+package org.apache.syncope.common.lib.auth;
+
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.jaxb.DateAdapter;
+
+import javax.xml.bind.annotation.XmlSchema;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
+
+import java.util.Date;
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/AuthModuleTO.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/AuthModuleTO.java
new file mode 100644
index 0000000..492227f
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/AuthModuleTO.java
@@ -0,0 +1,132 @@
+/*
+ * 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.syncope.common.lib.to;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.syncope.common.lib.BaseBean;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.auth.AuthModuleConf;
+
+@XmlRootElement(name = "authModule")
+@XmlType
+public class AuthModuleTO extends BaseBean implements EntityTO {
+
+    private static final long serialVersionUID = -7490425997956703057L;
+
+    private String key;
+
+    private String name;
+
+    private String description;
+
+    private final List<ItemTO> profileItems = new ArrayList<>();
+
+    private AuthModuleConf conf;
+
+    @Override
+    public String getKey() {
+        return key;
+    }
+
+    @Override
+    public void setKey(final String key) {
+        this.key = key;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(final String name) {
+        this.name = name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(final String description) {
+        this.description = description;
+    }
+
+    public AuthModuleConf getConf() {
+        return conf;
+    }
+
+    public void setConf(final AuthModuleConf conf) {
+        this.conf = conf;
+    }
+
+    @XmlElementWrapper(name = "profileItems")
+    @XmlElement(name = "profileItem")
+    @JsonProperty("profileItems")
+    public List<ItemTO> getProfileItems() {
+        return profileItems;
+    }
+
+    public boolean add(final ItemTO item) {
+        return Optional.ofNullable(item)
+                .filter(itemTO -> this.profileItems.contains(itemTO) || this.profileItems.add(itemTO)).isPresent();
+    }
+
+    public boolean remove(final ItemTO item) {
+        return this.profileItems.remove(item);
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        AuthModuleTO other = (AuthModuleTO) obj;
+        return new EqualsBuilder().
+                append(key, other.key).
+                append(name, other.name).
+                append(description, other.description).
+                append(profileItems, other.profileItems).
+                append(conf, other.conf).
+                build();
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder().
+                append(key).
+                append(name).
+                append(description).
+                append(profileItems).
+                append(conf).
+                build();
+    }
+
+}
diff --git a/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/AuthModuleService.java b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/AuthModuleService.java
new file mode 100644
index 0000000..83e6015
--- /dev/null
+++ b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/AuthModuleService.java
@@ -0,0 +1,126 @@
+/*
+ * 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.syncope.common.rest.api.service;
+
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.enums.ParameterIn;
+import io.swagger.v3.oas.annotations.headers.Header;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
+import io.swagger.v3.oas.annotations.security.SecurityRequirements;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import java.util.List;
+import javax.validation.constraints.NotNull;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import org.apache.syncope.common.lib.to.AuthModuleTO;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+
+/**
+ * REST operations for authentication modules.
+ */
+@Tag(name = "AuthModules")
+@SecurityRequirements({
+    @SecurityRequirement(name = "BasicAuthentication"),
+    @SecurityRequirement(name = "Bearer") })
+@Path("authModules")
+public interface AuthModuleService extends JAXRSService {
+
+    /**
+     * Returns the authentication module matching the given key.
+     *
+     * @param key key of requested authentication module
+     * @return authentication module with matching id
+     */
+    @GET
+    @Path("{key}")
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    AuthModuleTO read(
+            @NotNull @PathParam("key") String key);
+
+    /**
+     * Returns a list of authentication modules of the matching type.
+     *
+     * @return list of authentication modules with matching type
+     */
+    @GET
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    List<AuthModuleTO> list();
+
+    /**
+     * Create a new authentication module.
+     *
+     * @param authModuleTO AuthModule to be created (needs to match type)
+     * @return Response object featuring Location header of created authentication module
+     */
+    @ApiResponses(
+            @ApiResponse(responseCode = "201",
+                    description = "AuthModule successfully created", headers = {
+                @Header(name = RESTHeaders.RESOURCE_KEY, schema =
+                        @Schema(type = "string"),
+                        description = "UUID generated for the entity created"),
+                @Header(name = HttpHeaders.LOCATION, schema =
+                        @Schema(type = "string"),
+                        description = "URL of the entity created") }))
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    Response create(
+            @NotNull AuthModuleTO authModuleTO);
+
+    /**
+     * Updates authentication module matching the given key.
+     *
+     * @param authModuleTO AuthModule to replace existing authentication module
+     */
+    @Parameter(name = "key", description = "AuthModule's key", in = ParameterIn.PATH, schema =
+            @Schema(type = "string"))
+    @ApiResponses(
+            @ApiResponse(responseCode = "204", description = "Operation was successful"))
+    @PUT
+    @Path("{key}")
+    @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    void update(
+            @NotNull AuthModuleTO authModuleTO);
+
+    /**
+     * Delete authentication module matching the given key.
+     *
+     * @param key key of authentication module to be deleted
+     */
+    @ApiResponses(
+            @ApiResponse(responseCode = "204", description = "Operation was successful"))
+    @DELETE
+    @Path("{key}")
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    void delete(
+            @NotNull @PathParam("key") String key);
+}
diff --git a/core/am/logic/src/main/java/org/apache/syncope/core/logic/AuthModuleLogic.java b/core/am/logic/src/main/java/org/apache/syncope/core/logic/AuthModuleLogic.java
new file mode 100644
index 0000000..538dc82
--- /dev/null
+++ b/core/am/logic/src/main/java/org/apache/syncope/core/logic/AuthModuleLogic.java
@@ -0,0 +1,131 @@
+/*
+ * 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.syncope.core.logic;
+
+import static org.apache.syncope.core.logic.AbstractLogic.LOG;
+
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.syncope.common.lib.to.AuthModuleTO;
+import org.apache.syncope.common.lib.types.AMEntitlement;
+import org.apache.syncope.core.persistence.api.dao.NotFoundException;
+import org.apache.syncope.core.persistence.api.dao.auth.AuthModuleDAO;
+import org.apache.syncope.core.persistence.api.entity.auth.AuthModule;
+import org.apache.syncope.core.provisioning.api.data.AuthModuleDataBinder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+public class AuthModuleLogic extends AbstractTransactionalLogic<AuthModuleTO> {
+
+    @Autowired
+    private AuthModuleDataBinder binder;
+
+    @Autowired
+    private AuthModuleDAO authModuleDAO;
+
+    @PreAuthorize("hasRole('" + AMEntitlement.AUTH_MODULE_CREATE + "')")
+    public AuthModuleTO create(final AuthModuleTO authModuleTO) {
+        return binder.getAuthModuleTO(authModuleDAO.save(binder.create(authModuleTO)));
+    }
+
+    @PreAuthorize("hasRole('" + AMEntitlement.AUTH_MODULE_UPDATE + "')")
+    public AuthModuleTO update(final AuthModuleTO authModuleTO) {
+        AuthModule authModule = authModuleDAO.find(authModuleTO.getKey());
+        if (authModule == null) {
+            throw new NotFoundException("AuthModule " + authModuleTO.getKey() + " not found");
+        }
+
+        return binder.getAuthModuleTO(authModuleDAO.save(binder.update(authModule, authModuleTO)));
+    }
+
+    @PreAuthorize("hasRole('" + AMEntitlement.AUTH_MODULE_LIST + "')")
+    @Transactional(readOnly = true)
+    public List<AuthModuleTO> list() {
+        return authModuleDAO.findAll().stream().
+                filter(Objects::nonNull).
+                map(authModule -> {
+                    AuthModuleTO result = null;
+                    try {
+                        result = binder.getAuthModuleTO(authModule);
+                    } catch (NotFoundException e) {
+                        LOG.error("Authentication module '{}' not found", authModule.getName());
+                    }
+
+                    return result;
+                }).collect(Collectors.toList());
+    }
+
+    @PreAuthorize("hasRole('" + AMEntitlement.AUTH_MODULE_READ + "')")
+    @Transactional(readOnly = true)
+    public AuthModuleTO read(final String key) {
+        AuthModule authModule = authModuleDAO.find(key);
+        if (authModule == null) {
+            throw new NotFoundException("AuthModule " + key + " not found");
+        }
+
+        return binder.getAuthModuleTO(authModule);
+    }
+
+    @PreAuthorize("hasRole('" + AMEntitlement.AUTH_MODULE_DELETE + "')")
+    public AuthModuleTO delete(final String key) {
+        AuthModule authModule = authModuleDAO.find(key);
+        if (authModule == null) {
+            throw new NotFoundException("AuthModule " + key + " not found");
+        }
+
+        AuthModuleTO deleted = binder.getAuthModuleTO(authModule);
+        authModuleDAO.delete(authModule);
+
+        return deleted;
+    }
+
+    @Override
+    protected AuthModuleTO resolveReference(final Method method, final Object... args)
+            throws UnresolvedReferenceException {
+
+        String key = null;
+
+        if (ArrayUtils.isNotEmpty(args)) {
+            for (int i = 0; key == null && i < args.length; i++) {
+                if (args[i] instanceof String) {
+                    key = (String) args[i];
+                } else if (args[i] instanceof AuthModuleTO) {
+                    key = ((AuthModuleTO) args[i]).getKey();
+                }
+            }
+        }
+
+        if (key != null) {
+            try {
+                return binder.getAuthModuleTO(authModuleDAO.find(key));
+            } catch (Throwable ignore) {
+                LOG.debug("Unresolved reference", ignore);
+                throw new UnresolvedReferenceException(ignore);
+            }
+        }
+
+        throw new UnresolvedReferenceException();
+    }
+}
diff --git a/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AuthModuleServiceImpl.java b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AuthModuleServiceImpl.java
new file mode 100644
index 0000000..68bdd90
--- /dev/null
+++ b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AuthModuleServiceImpl.java
@@ -0,0 +1,65 @@
+/*
+ * 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.syncope.core.rest.cxf.service;
+
+import java.net.URI;
+import java.util.List;
+import javax.ws.rs.core.Response;
+import org.apache.syncope.common.lib.to.AuthModuleTO;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.apache.syncope.common.rest.api.service.AuthModuleService;
+import org.apache.syncope.core.logic.AuthModuleLogic;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class AuthModuleServiceImpl extends AbstractServiceImpl implements AuthModuleService {
+
+    @Autowired
+    private AuthModuleLogic logic;
+
+    @Override
+    public Response create(final AuthModuleTO authModuleTO) {
+        AuthModuleTO authModule = logic.create(authModuleTO);
+        URI location = uriInfo.getAbsolutePathBuilder().path(authModule.getKey()).build();
+        return Response.created(location).
+                header(RESTHeaders.RESOURCE_KEY, authModule.getKey()).
+                build();
+    }
+
+    @Override
+    public void delete(final String key) {
+        logic.delete(key);
+    }
+
+    @Override
+    public List<AuthModuleTO> list() {
+        return logic.list();
+    }
+
+    @Override
+    public AuthModuleTO read(final String key) {
+        return logic.read(key);
+    }
+
+    @Override
+    public void update(final AuthModuleTO authModuleTO) {
+        logic.update(authModuleTO);
+    }
+}
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/auth/AuthModuleDAO.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/auth/AuthModuleDAO.java
new file mode 100644
index 0000000..fc5e18c
--- /dev/null
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/auth/AuthModuleDAO.java
@@ -0,0 +1,37 @@
+/*
+ * 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.syncope.core.persistence.api.dao.auth;
+
+import org.apache.syncope.core.persistence.api.dao.DAO;
+import java.util.List;
+import org.apache.syncope.core.persistence.api.entity.auth.AuthModule;
+
+public interface AuthModuleDAO extends DAO<AuthModule> {
+
+    AuthModule find(String key);
+
+    List<AuthModule> findAll();
+
+    AuthModule save(AuthModule authModule);
+
+    void delete(String key);
+
+    void delete(AuthModule authModule);
+
+}
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/AuthModule.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/AuthModule.java
new file mode 100644
index 0000000..5d2393e
--- /dev/null
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/AuthModule.java
@@ -0,0 +1,48 @@
+/*
+ * 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.syncope.core.persistence.api.entity.auth;
+
+import java.util.List;
+import org.apache.syncope.common.lib.auth.AuthModuleConf;
+import org.apache.syncope.core.persistence.api.entity.Entity;
+import org.apache.syncope.core.persistence.api.entity.resource.Item;
+
+public interface AuthModule extends Entity {
+
+    String getName();
+
+    void setName(String name);
+
+    String getDescription();
+
+    void setDescription(String description);
+
+    /**
+     * Specify the mapping items for the attributes fetched from the source.
+     *
+     * @return list of mapping items
+     */
+    List<? extends Item> getProfileItems();
+
+    boolean add(Item profileItem);
+
+    AuthModuleConf getConf();
+
+    void setConf(AuthModuleConf description);
+}
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/AuthModuleItem.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/AuthModuleItem.java
new file mode 100644
index 0000000..9f249e4
--- /dev/null
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/AuthModuleItem.java
@@ -0,0 +1,25 @@
+/*
+ * 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.syncope.core.persistence.api.entity.auth;
+
+import org.apache.syncope.core.persistence.api.entity.resource.Item;
+
+public interface AuthModuleItem extends Item {
+
+}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/auth/JPAAuthModuleDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/auth/JPAAuthModuleDAO.java
new file mode 100644
index 0000000..dd765ad
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/auth/JPAAuthModuleDAO.java
@@ -0,0 +1,68 @@
+/*
+ * 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.syncope.core.persistence.jpa.dao.auth;
+
+import org.apache.syncope.core.persistence.jpa.dao.AbstractDAO;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+import javax.persistence.TypedQuery;
+import java.util.List;
+import org.apache.syncope.core.persistence.api.dao.auth.AuthModuleDAO;
+import org.apache.syncope.core.persistence.api.entity.auth.AuthModule;
+import org.apache.syncope.core.persistence.jpa.entity.auth.JPAAuthModule;
+
+@Repository
+public class JPAAuthModuleDAO extends AbstractDAO<AuthModule> implements AuthModuleDAO {
+
+    @Override
+    public AuthModule find(final String key) {
+        return entityManager().find(JPAAuthModule.class, key);
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public List<AuthModule> findAll() {
+        TypedQuery<AuthModule> query = entityManager().createQuery("SELECT e FROM " + JPAAuthModule.class.
+                getSimpleName() + " e", AuthModule.class);
+
+        return query.getResultList();
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public AuthModule save(final AuthModule authModule) {
+        return entityManager().merge(authModule);
+    }
+
+    @Override
+    public void delete(final String key) {
+        AuthModule authModule = find(key);
+        if (authModule == null) {
+            return;
+        }
+
+        delete(authModule);
+    }
+
+    @Override
+    public void delete(final AuthModule authModule) {
+        entityManager().remove(authModule);
+    }
+
+}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAAuthModule.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAAuthModule.java
new file mode 100644
index 0000000..e2b6586
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAAuthModule.java
@@ -0,0 +1,104 @@
+/*
+ * 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.syncope.core.persistence.jpa.entity.auth;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Lob;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.auth.AuthModuleConf;
+import org.apache.syncope.core.persistence.api.entity.auth.AuthModule;
+import org.apache.syncope.core.persistence.api.entity.resource.Item;
+import org.apache.syncope.core.persistence.jpa.entity.AbstractGeneratedKeyEntity;
+import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
+
+@Entity
+@Table(name = JPAAuthModule.TABLE)
+public class JPAAuthModule extends AbstractGeneratedKeyEntity implements AuthModule {
+
+    public static final String TABLE = "AuthModule";
+
+    private static final long serialVersionUID = 5681033638234853077L;
+
+    @Column(unique = true, nullable = false)
+    private String name;
+
+    @Column(nullable = false)
+    private String description;
+
+    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, mappedBy = "mapping")
+    private final List<JPAAuthModuleItem> profileItems = new ArrayList<>();
+
+    @Lob
+    private String jsonConf;
+
+    @Override
+    public String getDescription() {
+        return description;
+    }
+
+    @Override
+    public void setDescription(final String description) {
+        this.description = description;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public void setName(final String name) {
+        this.name = name;
+    }
+
+    @Override
+    public List<? extends Item> getProfileItems() {
+        return profileItems;
+    }
+
+    @Override
+    public boolean add(final Item profileItem) {
+        checkType(profileItem, JPAAuthModuleItem.class);
+        return profileItems.contains((JPAAuthModuleItem) profileItem)
+                || profileItems.add((JPAAuthModuleItem) profileItem);
+    }
+
+    @Override
+    public AuthModuleConf getConf() {
+        AuthModuleConf conf = null;
+        if (!StringUtils.isBlank(jsonConf)) {
+            conf = POJOHelper.deserialize(jsonConf, AuthModuleConf.class);
+        }
+
+        return conf;
+    }
+
+    @Override
+    public void setConf(final AuthModuleConf conf) {
+        jsonConf = POJOHelper.serialize(conf);
+    }
+
+}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAAuthModuleItem.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAAuthModuleItem.java
new file mode 100644
index 0000000..bc92d49
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAAuthModuleItem.java
@@ -0,0 +1,69 @@
+/*
+ * 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.syncope.core.persistence.jpa.entity.auth;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.persistence.Cacheable;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.JoinTable;
+import javax.persistence.ManyToMany;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+import org.apache.syncope.common.lib.types.IdMImplementationType;
+import org.apache.syncope.core.persistence.api.entity.Implementation;
+import org.apache.syncope.core.persistence.api.entity.auth.AuthModuleItem;
+import org.apache.syncope.core.persistence.jpa.entity.JPAImplementation;
+import org.apache.syncope.core.persistence.jpa.entity.resource.AbstractItem;
+
+@Entity
+@Table(name = JPAAuthModuleItem.TABLE)
+@Cacheable
+public class JPAAuthModuleItem extends AbstractItem implements AuthModuleItem {
+
+    public static final String TABLE = "AuthModuleItem";
+
+    private static final long serialVersionUID = 3165440920144995781L;
+
+    @ManyToMany(fetch = FetchType.EAGER)
+    @JoinTable(name = TABLE + "Transformer",
+            joinColumns =
+            @JoinColumn(name = "item_id"),
+            inverseJoinColumns =
+            @JoinColumn(name = "implementation_id"),
+            uniqueConstraints =
+            @UniqueConstraint(columnNames = { "item_id", "implementation_id" }))
+    private final List<JPAImplementation> transformers = new ArrayList<>();
+
+    @Override
+    public boolean add(final Implementation transformer) {
+        checkType(transformer, JPAImplementation.class);
+        checkImplementationType(transformer, IdMImplementationType.ITEM_TRANSFORMER);
+        return transformers.contains((JPAImplementation) transformer)
+                || this.transformers.add((JPAImplementation) transformer);
+    }
+
+    @Override
+    public List<? extends Implementation> getTransformers() {
+        return transformers;
+    }
+
+}
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AuthModuleTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AuthModuleTest.java
new file mode 100644
index 0000000..27c0d22
--- /dev/null
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AuthModuleTest.java
@@ -0,0 +1,502 @@
+/*
+ * 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.syncope.core.persistence.jpa.inner;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.List;
+import java.util.UUID;
+import org.apache.commons.lang3.ClassUtils;
+import org.apache.syncope.common.lib.auth.AuthModuleConf;
+import org.apache.syncope.common.lib.auth.GoogleMfaAuthModuleConf;
+import org.apache.syncope.common.lib.auth.JDBCAuthModuleConf;
+import org.apache.syncope.common.lib.auth.JaasAuthModuleConf;
+import org.apache.syncope.common.lib.auth.LDAPAuthModuleConf;
+import org.apache.syncope.common.lib.auth.OIDCAuthModuleConf;
+import org.apache.syncope.common.lib.auth.RadiusAuthModuleConf;
+import org.apache.syncope.common.lib.auth.SAML2IdPAuthModuleConf;
+import org.apache.syncope.common.lib.auth.StaticAuthModuleConf;
+import org.apache.syncope.common.lib.auth.SyncopeAuthModuleConf;
+import org.apache.syncope.common.lib.auth.U2FAuthModuleConf;
+import org.apache.syncope.core.persistence.api.dao.auth.AuthModuleDAO;
+import org.apache.syncope.core.persistence.jpa.AbstractTest;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+import org.apache.syncope.core.persistence.api.entity.auth.AuthModule;
+import org.apache.syncope.core.persistence.api.entity.auth.AuthModuleItem;
+
+@Transactional("Master")
+public class AuthModuleTest extends AbstractTest {
+
+    @Autowired
+    private AuthModuleDAO authModuleDAO;
+
+    @Test
+    public void find() {
+        AuthModule authModule = authModuleDAO.find("be456831-593d-4003-b273-4c3fb61700df");
+        assertNotNull(authModule);
+
+        authModule = authModuleDAO.find("4c3ed8f6-7008-11ea-bc55-0242ac130003");
+        assertNotNull(authModule);
+
+        authModule = authModuleDAO.find("4c3edbbc-7008-11ea-bc55-0242ac130003");
+        assertNotNull(authModule);
+
+        authModule = authModuleDAO.find("4c3ed7e8-7008-11ea-bc55-0242ac130003");
+        assertNotNull(authModule);
+
+        authModule = authModuleDAO.find("4c3ed4e6-7008-11ea-bc55-0242ac130003");
+        assertNotNull(authModule);
+
+        authModule = authModuleDAO.find("4c3edc98-7008-11ea-bc55-0242ac130003");
+        assertNotNull(authModule);
+
+        authModule = authModuleDAO.find("4c3ed9d2-7008-11ea-bc55-0242ac130003");
+        assertNotNull(authModule);
+
+        authModule = authModuleDAO.find("4c3edd60-7008-11ea-bc55-0242ac130003");
+        assertNotNull(authModule);
+
+        authModule = authModuleDAO.find("07c528f3-63b4-4dc1-a4da-87f35b8bdec8");
+        assertNotNull(authModule);
+
+        authModule = authModuleDAO.find("f6e1288d-50d9-45fe-82ee-597c42242205");
+        assertNotNull(authModule);
+
+        authModule = authModuleDAO.find(UUID.randomUUID().toString());
+        assertNull(authModule);
+    }
+
+    @Test
+    public void findAll() {
+        List<AuthModule> modules = authModuleDAO.findAll();
+        assertNotNull(modules);
+        assertFalse(modules.isEmpty());
+        assertTrue(modules.size() >= 10);
+    }
+
+    @Test
+    public void findByAuthModuleImpl() {
+        AuthModule ldapAuthModule = authModuleDAO.find("be456831-593d-4003-b273-4c3fb61700df");
+        assertNotNull(ldapAuthModule);
+        AuthModule jdbcAuthModule = authModuleDAO.find("4c3ed7e8-7008-11ea-bc55-0242ac130003");
+        assertNotNull(jdbcAuthModule);
+        AuthModule googleMfaAuthModule = authModuleDAO.find("4c3ed4e6-7008-11ea-bc55-0242ac130003");
+        assertNotNull(googleMfaAuthModule);
+        AuthModule oidcAuthModule = authModuleDAO.find("4c3ed8f6-7008-11ea-bc55-0242ac130003");
+        assertNotNull(oidcAuthModule);
+        AuthModule saml2IdPAuthModule = authModuleDAO.find("4c3ed9d2-7008-11ea-bc55-0242ac130003");
+        assertNotNull(saml2IdPAuthModule);
+        AuthModule jaasAuthModule = authModuleDAO.find("4c3edbbc-7008-11ea-bc55-0242ac130003");
+        assertNotNull(jaasAuthModule);
+        AuthModule staticAuthModule = authModuleDAO.find("4c3edc98-7008-11ea-bc55-0242ac130003");
+        assertNotNull(staticAuthModule);
+        AuthModule syncopeAuthModule = authModuleDAO.find("4c3edd60-7008-11ea-bc55-0242ac130003");
+        assertNotNull(syncopeAuthModule);
+        AuthModule radiusAuthModule = authModuleDAO.find("07c528f3-63b4-4dc1-a4da-87f35b8bdec8");
+        assertNotNull(radiusAuthModule);
+        AuthModule u2fAuthModule = authModuleDAO.find("f6e1288d-50d9-45fe-82ee-597c42242205");
+        assertNotNull(u2fAuthModule);
+
+        assertTrue(isSpecificConf(ldapAuthModule.getConf(), LDAPAuthModuleConf.class));
+        assertFalse(isSpecificConf(ldapAuthModule.getConf(), JDBCAuthModuleConf.class));
+
+        assertTrue(isSpecificConf(jdbcAuthModule.getConf(), JDBCAuthModuleConf.class));
+        assertFalse(isSpecificConf(jdbcAuthModule.getConf(), GoogleMfaAuthModuleConf.class));
+
+        assertTrue(isSpecificConf(googleMfaAuthModule.getConf(), GoogleMfaAuthModuleConf.class));
+        assertFalse(isSpecificConf(googleMfaAuthModule.getConf(), OIDCAuthModuleConf.class));
+
+        assertTrue(isSpecificConf(oidcAuthModule.getConf(), OIDCAuthModuleConf.class));
+        assertFalse(isSpecificConf(oidcAuthModule.getConf(), SAML2IdPAuthModuleConf.class));
+
+        assertTrue(isSpecificConf(saml2IdPAuthModule.getConf(), SAML2IdPAuthModuleConf.class));
+        assertFalse(isSpecificConf(saml2IdPAuthModule.getConf(), JaasAuthModuleConf.class));
+
+        assertTrue(isSpecificConf(jaasAuthModule.getConf(), JaasAuthModuleConf.class));
+        assertFalse(isSpecificConf(jaasAuthModule.getConf(), StaticAuthModuleConf.class));
+
+        assertTrue(isSpecificConf(staticAuthModule.getConf(), StaticAuthModuleConf.class));
+        assertFalse(isSpecificConf(staticAuthModule.getConf(), SyncopeAuthModuleConf.class));
+
+        assertTrue(isSpecificConf(syncopeAuthModule.getConf(), SyncopeAuthModuleConf.class));
+        assertFalse(isSpecificConf(syncopeAuthModule.getConf(), RadiusAuthModuleConf.class));
+
+        assertTrue(isSpecificConf(radiusAuthModule.getConf(), RadiusAuthModuleConf.class));
+        assertFalse(isSpecificConf(radiusAuthModule.getConf(), U2FAuthModuleConf.class));
+
+        assertTrue(isSpecificConf(u2fAuthModule.getConf(), U2FAuthModuleConf.class));
+        assertFalse(isSpecificConf(u2fAuthModule.getConf(), LDAPAuthModuleConf.class));
+    }
+
+    @Test
+    public void findByType() {
+        List<AuthModule> authModules = authModuleDAO.findAll();
+        assertTrue(authModules.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), LDAPAuthModuleConf.class)
+                && authModule.getName().equals("DefaultLDAPAuthModule")));
+        assertTrue(authModules.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), JDBCAuthModuleConf.class)
+                && authModule.getName().equals("DefaultJDBCAuthModule")));
+        assertTrue(authModules.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), GoogleMfaAuthModuleConf.class)
+                && authModule.getName().equals("DefaultGoogleMfaAuthModule")));
+        assertTrue(authModules.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), OIDCAuthModuleConf.class)
+                && authModule.getName().equals("DefaultOIDCAuthModule")));
+        assertTrue(authModules.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), SAML2IdPAuthModuleConf.class)
+                && authModule.getName().equals("DefaultSAML2IdPAuthModule")));
+        assertTrue(authModules.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), JaasAuthModuleConf.class)
+                && authModule.getName().equals("DefaultJaasAuthModule")));
+        assertTrue(authModules.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), StaticAuthModuleConf.class)
+                && authModule.getName().equals("DefaultStaticAuthModule")));
+        assertTrue(authModules.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), SyncopeAuthModuleConf.class)
+                && authModule.getName().equals("DefaultSyncopeAuthModule")));
+        assertTrue(authModules.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), RadiusAuthModuleConf.class)
+                && authModule.getName().equals("DefaultRadiusAuthModule")));
+        assertTrue(authModules.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), U2FAuthModuleConf.class)
+                && authModule.getName().equals("DefaultU2FAuthModule")));
+    }
+
+    @Test
+    public void saveWithStaticModule() {
+        StaticAuthModuleConf conf = new StaticAuthModuleConf();
+        conf.getUsers().put("user1", UUID.randomUUID().toString());
+        conf.getUsers().put("user2", "user2Password123");
+
+        saveAuthModule("StaticAuthModuleTest", conf);
+    }
+
+    @Test
+    public void saveWithJaasModule() {
+        JaasAuthModuleConf conf = new JaasAuthModuleConf();
+        conf.setKerberosKdcSystemProperty("sample-value");
+        conf.setKerberosRealmSystemProperty("sample-value");
+        conf.setLoginConfigType("JavaLoginConfig");
+        conf.setRealm("SYNCOPE");
+        conf.setLoginConfigurationFile("/opt/jaas/login.conf");
+
+        saveAuthModule("JaasAuthModuleTest", conf);
+    }
+
+    @Test
+    public void saveWithLdapModule() {
+        LDAPAuthModuleConf conf = new LDAPAuthModuleConf();
+        conf.setBaseDn("dc=example,dc=org");
+        conf.setSearchFilter("cn={user}");
+        conf.setSubtreeSearch(true);
+        conf.setLdapUrl("ldap://localhost:1389");
+        conf.setUserIdAttribute("uid");
+        conf.setBindCredential("Password");
+
+        saveAuthModule("LDAPAuthModuleTest", conf);
+    }
+
+    @Test
+    public void saveWithGoogleAuthenticatorModule() {
+        GoogleMfaAuthModuleConf conf = new GoogleMfaAuthModuleConf();
+        conf.setCodeDigits(6);
+        conf.setIssuer("SyncopeTest");
+        conf.setLabel("Syncope");
+        conf.setTimeStepSize(30);
+        conf.setWindowSize(3);
+
+        saveAuthModule("GoogleMfaAuthModuleTest", conf);
+    }
+
+    @Test
+    public void saveWithOIDCAuthModule() {
+        OIDCAuthModuleConf conf = new OIDCAuthModuleConf();
+        conf.setId("OIDCTestId");
+        conf.setDiscoveryUri("www.testurl.com");
+        conf.setUserIdAttribute("username");
+        conf.setResponseType("code");
+        conf.setScope("openid email profile");
+
+        saveAuthModule("OIDCAuthModuleTest", conf);
+    }
+
+    @Test
+    public void saveWithJDBCModule() {
+        JDBCAuthModuleConf conf = new JDBCAuthModuleConf();
+        conf.setSql("SELECT * FROM table WHERE name=?");
+        conf.setFieldPassword("password");
+        conf.getPrincipalAttributeList().addAll(List.of("sn", "cn:commonName", "givenName"));
+
+        saveAuthModule("JDBCAuthModuleTest", conf);
+    }
+
+    @Test
+    public void saveWithSyncopeModule() {
+        SyncopeAuthModuleConf conf = new SyncopeAuthModuleConf();
+        conf.setDomain("Master");
+        conf.setUrl("http://mydomain.com/syncope/rest");
+
+        saveAuthModule("SyncopeAuthModuleTest", conf);
+    }
+
+    @Test
+    public void saveWithSAML2IdPModule() {
+        SAML2IdPAuthModuleConf conf = new SAML2IdPAuthModuleConf();
+        conf.setServiceProviderEntityId("testEntityId");
+        conf.setProviderName("testProviderName");
+        conf.setServiceProviderMetadataPath("file:/etc/metadata");
+
+        saveAuthModule("SAML2IdPAuthModuleTest", conf);
+    }
+
+    @Test
+    public void saveWithRadiusModule() {
+        RadiusAuthModuleConf conf = new RadiusAuthModuleConf();
+        conf.setProtocol("MSCHAPv2");
+        conf.setInetAddress("1.2.3.4");
+        conf.setSharedSecret("xyz");
+        conf.setSocketTimeout(40);
+
+        saveAuthModule("RadiusAuthModuleTest", conf);
+    }
+
+    @Test
+    public void saveWithU2FModule() {
+        U2FAuthModuleConf conf = new U2FAuthModuleConf();
+        conf.setExpireDevices(50);
+
+        saveAuthModule("U2FAuthModuleTest", conf);
+    }
+
+    @Test
+    public void updateWithLDAPModule() {
+        AuthModule module = authModuleDAO.find("be456831-593d-4003-b273-4c3fb61700df");
+        assertNotNull(module);
+        AuthModuleConf conf = module.getConf();
+        LDAPAuthModuleConf.class.cast(conf).setBaseDn("dc=example2,dc=org");
+        LDAPAuthModuleConf.class.cast(conf).setSearchFilter("cn={user2}");
+        module.setConf(conf);
+
+        module = authModuleDAO.save(module);
+        assertNotNull(module);
+        assertNotNull(module.getKey());
+        AuthModule found = authModuleDAO.find(module.getKey());
+        assertNotNull(found);
+        assertEquals("dc=example2,dc=org", LDAPAuthModuleConf.class.cast(found.getConf()).getBaseDn());
+        assertEquals("cn={user2}", LDAPAuthModuleConf.class.cast(found.getConf()).getSearchFilter());
+    }
+
+    @Test
+    public void updateWithJDBCModule() {
+        AuthModule module = authModuleDAO.find("4c3ed7e8-7008-11ea-bc55-0242ac130003");
+        assertNotNull(module);
+        AuthModuleConf conf = module.getConf();
+        JDBCAuthModuleConf.class.cast(conf).setSql("SELECT * FROM otherTable WHERE name=?");
+        module.setConf(conf);
+
+        module = authModuleDAO.save(module);
+        assertNotNull(module);
+        assertNotNull(module.getKey());
+        AuthModule found = authModuleDAO.find(module.getKey());
+        assertNotNull(found);
+        assertEquals("SELECT * FROM otherTable WHERE name=?",
+                JDBCAuthModuleConf.class.cast(found.getConf()).getSql());
+    }
+
+    @Test
+    public void updateWithGoogleMfaModule() {
+        AuthModule module = authModuleDAO.find("4c3ed4e6-7008-11ea-bc55-0242ac130003");
+        assertNotNull(module);
+        AuthModuleConf conf = module.getConf();
+        GoogleMfaAuthModuleConf.class.cast(conf).setLabel("newLabel");
+        module.setConf(conf);
+
+        module = authModuleDAO.save(module);
+        assertNotNull(module);
+        assertNotNull(module.getKey());
+        AuthModule found = authModuleDAO.find(module.getKey());
+        assertNotNull(found);
+        assertEquals("newLabel", GoogleMfaAuthModuleConf.class.cast(found.getConf()).getLabel());
+    }
+
+    @Test
+    public void updateWithSAML2IdPModule() {
+        AuthModule module = authModuleDAO.find("4c3ed9d2-7008-11ea-bc55-0242ac130003");
+        assertNotNull(module);
+        AuthModuleConf conf = module.getConf();
+        SAML2IdPAuthModuleConf.class.cast(conf).setServiceProviderEntityId("newEntityId");
+        module.setConf(conf);
+
+        module = authModuleDAO.save(module);
+        assertNotNull(module);
+        assertNotNull(module.getKey());
+        AuthModule found = authModuleDAO.find(module.getKey());
+        assertNotNull(found);
+        assertEquals("newEntityId", SAML2IdPAuthModuleConf.class.cast(found.getConf()).getServiceProviderEntityId());
+    }
+
+    @Test
+    public void updateWithOIDCModule() {
+        AuthModule module = authModuleDAO.find("4c3ed8f6-7008-11ea-bc55-0242ac130003");
+        assertNotNull(module);
+        AuthModuleConf conf = module.getConf();
+        OIDCAuthModuleConf.class.cast(conf).setResponseType("newCode");
+        module.setConf(conf);
+
+        module = authModuleDAO.save(module);
+        assertNotNull(module);
+        assertNotNull(module.getKey());
+        AuthModule found = authModuleDAO.find(module.getKey());
+        assertNotNull(found);
+        assertEquals("newCode", OIDCAuthModuleConf.class.cast(found.getConf()).getResponseType());
+    }
+
+    @Test
+    public void updateWithJaasModule() {
+        AuthModule module = authModuleDAO.find("4c3edbbc-7008-11ea-bc55-0242ac130003");
+        assertNotNull(module);
+        AuthModuleConf conf = module.getConf();
+        JaasAuthModuleConf.class.cast(conf).setRealm("SYNCOPE_NEW");
+        module.setConf(conf);
+
+        module = authModuleDAO.save(module);
+        assertNotNull(module);
+        assertNotNull(module.getKey());
+        AuthModule found = authModuleDAO.find(module.getKey());
+        assertNotNull(found);
+        assertEquals("SYNCOPE_NEW", JaasAuthModuleConf.class.cast(found.getConf()).getRealm());
+    }
+
+    @Test
+    public void updateWithStaticModule() {
+        AuthModule module = authModuleDAO.find("4c3edc98-7008-11ea-bc55-0242ac130003");
+        assertNotNull(module);
+        assertEquals(1, StaticAuthModuleConf.class.cast(module.getConf()).getUsers().size());
+        AuthModuleConf conf = module.getConf();
+        StaticAuthModuleConf.class.cast(conf).getUsers().put("user3", "user3Password123");
+        module.setConf(conf);
+
+        module = authModuleDAO.save(module);
+        assertNotNull(module);
+        assertNotNull(module.getKey());
+        AuthModule found = authModuleDAO.find(module.getKey());
+        assertNotNull(found);
+        assertEquals(2, StaticAuthModuleConf.class.cast(found.getConf()).getUsers().size());
+    }
+
+    @Test
+    public void updateWithRadiusModule() {
+        AuthModule module = authModuleDAO.find("07c528f3-63b4-4dc1-a4da-87f35b8bdec8");
+        assertNotNull(module);
+        AuthModuleConf conf = module.getConf();
+        RadiusAuthModuleConf.class.cast(conf).setSocketTimeout(45);
+        module.setConf(conf);
+
+        module = authModuleDAO.save(module);
+        assertNotNull(module);
+        assertNotNull(module.getKey());
+        AuthModule found = authModuleDAO.find(module.getKey());
+        assertNotNull(found);
+        assertEquals(45, RadiusAuthModuleConf.class.cast(found.getConf()).getSocketTimeout());
+    }
+
+    @Test
+    public void updateWithU2fModule() {
+        AuthModule module = authModuleDAO.find("f6e1288d-50d9-45fe-82ee-597c42242205");
+        assertNotNull(module);
+        AuthModuleConf conf = module.getConf();
+        U2FAuthModuleConf.class.cast(conf).setExpireDevices(24);
+        module.setConf(conf);
+
+        module = authModuleDAO.save(module);
+        assertNotNull(module);
+        assertNotNull(module.getKey());
+        AuthModule found = authModuleDAO.find(module.getKey());
+        assertNotNull(found);
+        assertEquals(24, U2FAuthModuleConf.class.cast(found.getConf()).getExpireDevices());
+    }
+
+    @Test
+    public void updateWithSyncopeModule() {
+        AuthModule module = authModuleDAO.find("4c3edd60-7008-11ea-bc55-0242ac130003");
+        assertNotNull(module);
+        AuthModuleConf conf = module.getConf();
+        SyncopeAuthModuleConf.class.cast(conf).setDomain("Two");
+        module.setConf(conf);
+
+        module = authModuleDAO.save(module);
+        assertNotNull(module);
+        assertNotNull(module.getKey());
+        AuthModule found = authModuleDAO.find(module.getKey());
+        assertNotNull(found);
+        assertEquals("Two", SyncopeAuthModuleConf.class.cast(found.getConf()).getDomain());
+    }
+
+    @Test
+    public void delete() {
+        testDelete("be456831-593d-4003-b273-4c3fb61700df");
+        testDelete("4c3ed8f6-7008-11ea-bc55-0242ac130003");
+        testDelete("4c3edbbc-7008-11ea-bc55-0242ac130003");
+        testDelete("4c3ed7e8-7008-11ea-bc55-0242ac130003");
+        testDelete("4c3ed4e6-7008-11ea-bc55-0242ac130003");
+        testDelete("4c3edc98-7008-11ea-bc55-0242ac130003");
+        testDelete("4c3ed9d2-7008-11ea-bc55-0242ac130003");
+        testDelete("4c3edd60-7008-11ea-bc55-0242ac130003");
+        testDelete("07c528f3-63b4-4dc1-a4da-87f35b8bdec8");
+        testDelete("f6e1288d-50d9-45fe-82ee-597c42242205");
+    }
+
+    private void saveAuthModule(final String name, final AuthModuleConf conf) {
+        AuthModule module = entityFactory.newEntity(AuthModule.class);
+        module.setName(name);
+        module.setDescription("An authentication module");
+        module.setConf(conf);
+        AuthModuleItem keyMapping = entityFactory.newEntity(AuthModuleItem.class);
+        keyMapping.setIntAttrName("uid");
+        keyMapping.setExtAttrName("username");
+        AuthModuleItem fullnameMapping = entityFactory.newEntity(AuthModuleItem.class);
+        fullnameMapping.setIntAttrName("cn");
+        fullnameMapping.setExtAttrName("fullname");
+        module.add(keyMapping);
+        module.add(fullnameMapping);
+        authModuleDAO.save(module);
+        assertNotNull(module);
+        assertNotNull(module.getKey());
+        assertNotNull(authModuleDAO.find(module.getKey()));
+    }
+
+    private void testDelete(final String key) {
+        AuthModule authModule = authModuleDAO.find(key);
+        assertNotNull(authModule);
+        authModuleDAO.delete(key);
+        authModule = authModuleDAO.find(key);
+        assertNull(authModule);
+    }
+
+    private boolean isSpecificConf(final AuthModuleConf conf, final Class<? extends AuthModuleConf> clazz) {
+        return ClassUtils.isAssignable(clazz, conf.getClass());
+    }
+}
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/AuthModuleDataBinder.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/AuthModuleDataBinder.java
new file mode 100644
index 0000000..1eab9f4
--- /dev/null
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/AuthModuleDataBinder.java
@@ -0,0 +1,32 @@
+/*
+ * 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.syncope.core.provisioning.api.data;
+
+import org.apache.syncope.common.lib.to.AuthModuleTO;
+import org.apache.syncope.core.persistence.api.entity.auth.AuthModule;
+
+public interface AuthModuleDataBinder {
+
+    AuthModule create(AuthModuleTO authModuleTO);
+
+    AuthModule update(AuthModule authModule, AuthModuleTO authModuleTO);
+
+    AuthModuleTO getAuthModuleTO(AuthModule authModule);
+
+}
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AuthModuleDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AuthModuleDataBinderImpl.java
new file mode 100644
index 0000000..0b0e38b
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AuthModuleDataBinderImpl.java
@@ -0,0 +1,79 @@
+/*
+ * 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.syncope.core.provisioning.java.data;
+
+import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.apache.syncope.common.lib.to.AuthModuleTO;
+import org.apache.syncope.core.persistence.api.entity.auth.AuthModule;
+import org.apache.syncope.core.provisioning.api.data.AuthModuleDataBinder;
+
+@Component
+public class AuthModuleDataBinderImpl implements AuthModuleDataBinder {
+
+    @Autowired
+    private EntityFactory entityFactory;
+
+    private AuthModule getAuthModule(final AuthModule authModule, final AuthModuleTO authModuleTO) {
+        AuthModule result = authModule;
+
+        if (result == null) {
+            result = entityFactory.newEntity(AuthModule.class);
+        }
+
+        AuthModule authenticationModule = AuthModule.class.cast(result);
+        AuthModuleTO authenticationModuleTO = AuthModuleTO.class.cast(authModuleTO);
+
+        authenticationModule.setName(authenticationModuleTO.getName());
+        authenticationModule.setConf(authenticationModuleTO.getConf());
+        authenticationModule.setDescription(authenticationModuleTO.getDescription());
+        // remove all profile items not contained in the TO
+        authenticationModule.getProfileItems().
+                removeIf(item -> !authenticationModuleTO.getProfileItems().stream().
+                anyMatch(otherItem -> item.getKey().equals(otherItem.getKey())));
+
+        return result;
+    }
+
+    @Override
+    public AuthModule create(final AuthModuleTO authModuleTO) {
+        return getAuthModule(null, authModuleTO);
+    }
+
+    @Override
+    public AuthModule update(final AuthModule authModule, final AuthModuleTO authModuleTO) {
+        return getAuthModule(authModule, authModuleTO);
+    }
+
+    @Override
+    public AuthModuleTO getAuthModuleTO(final AuthModule authModule) {
+        AuthModuleTO authModuleTO = new AuthModuleTO();
+
+        authModuleTO.setName(authModule.getName());
+        authModuleTO.setKey(authModule.getKey());
+        authModuleTO.setDescription(authModule.getDescription());
+        authModuleTO.setConf(authModule.getConf());
+        authModuleTO.getProfileItems().forEach(item -> {
+            authModuleTO.add(item);
+        });
+
+        return authModuleTO;
+    }
+}
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuthModuleITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuthModuleITCase.java
new file mode 100644
index 0000000..a13c4fa
--- /dev/null
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuthModuleITCase.java
@@ -0,0 +1,589 @@
+/*
+ * 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.syncope.fit.core;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.fit.AbstractITCase;
+import org.junit.jupiter.api.Test;
+import java.io.IOException;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.UUID;
+import org.apache.commons.lang3.ClassUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.auth.AuthModuleConf;
+import org.apache.syncope.common.lib.auth.GoogleMfaAuthModuleConf;
+import org.apache.syncope.common.lib.auth.JDBCAuthModuleConf;
+import org.apache.syncope.common.lib.auth.JaasAuthModuleConf;
+import org.apache.syncope.common.lib.auth.LDAPAuthModuleConf;
+import org.apache.syncope.common.lib.auth.OIDCAuthModuleConf;
+import org.apache.syncope.common.lib.auth.RadiusAuthModuleConf;
+import org.apache.syncope.common.lib.auth.SAML2IdPAuthModuleConf;
+import org.apache.syncope.common.lib.auth.StaticAuthModuleConf;
+import org.apache.syncope.common.lib.auth.SyncopeAuthModuleConf;
+import org.apache.syncope.common.lib.auth.U2FAuthModuleConf;
+import org.apache.syncope.common.lib.to.AuthModuleTO;
+
+public class AuthModuleITCase extends AbstractITCase {
+
+    private enum AuthModuleSupportedType {
+        GOOGLE_MFA,
+        SAML2_IDP,
+        STATIC,
+        SYNCOPE,
+        LDAP,
+        JAAS,
+        JDBC,
+        U2F,
+        RADIUS,
+        OIDC;
+
+    };
+
+    private static AuthModuleTO buildAuthModuleTO(final AuthModuleSupportedType type) {
+        AuthModuleTO authModuleTO = new AuthModuleTO();
+        authModuleTO.setName("Test" + type + "AuthenticationModule" + getUUIDString());
+        authModuleTO.setDescription("A test " + type + " Authentication Module");
+        AuthModuleConf conf;
+
+        switch (type) {
+            case LDAP:
+                conf = new LDAPAuthModuleConf();
+                LDAPAuthModuleConf.class.cast(conf).setName("TestConf" + getUUIDString());
+                LDAPAuthModuleConf.class.cast(conf).setBaseDn("dc=example,dc=org");
+                LDAPAuthModuleConf.class.cast(conf).setSearchFilter("cn={user}");
+                LDAPAuthModuleConf.class.cast(conf).setSubtreeSearch(true);
+                LDAPAuthModuleConf.class.cast(conf).setLdapUrl("ldap://localhost:1389");
+                LDAPAuthModuleConf.class.cast(conf).setUserIdAttribute("uid");
+                LDAPAuthModuleConf.class.cast(conf).setBaseDn("cn=Directory Manager,dc=example,dc=org");
+                LDAPAuthModuleConf.class.cast(conf).setBindCredential("Password");
+                break;
+
+            case GOOGLE_MFA:
+                conf = new GoogleMfaAuthModuleConf();
+                GoogleMfaAuthModuleConf.class.cast(conf).setName("TestConf" + getUUIDString());
+                GoogleMfaAuthModuleConf.class.cast(conf).setCodeDigits(6);
+                GoogleMfaAuthModuleConf.class.cast(conf).setIssuer("SyncopeTest");
+                GoogleMfaAuthModuleConf.class.cast(conf).setLabel("Syncope");
+                GoogleMfaAuthModuleConf.class.cast(conf).setTimeStepSize(30);
+                GoogleMfaAuthModuleConf.class.cast(conf).setWindowSize(3);
+                break;
+
+            case JAAS:
+                conf = new JaasAuthModuleConf();
+                JaasAuthModuleConf.class.cast(conf).setName("TestConf" + getUUIDString());
+                JaasAuthModuleConf.class.cast(conf).setKerberosKdcSystemProperty("sample-value");
+                JaasAuthModuleConf.class.cast(conf).setKerberosRealmSystemProperty("sample-value");
+                JaasAuthModuleConf.class.cast(conf).setLoginConfigType("JavaLoginConfig");
+                JaasAuthModuleConf.class.cast(conf).setRealm("SYNCOPE");
+                JaasAuthModuleConf.class.cast(conf).setLoginConfigurationFile("/opt/jaas/login.conf");
+                break;
+
+            case JDBC:
+                conf = new JDBCAuthModuleConf();
+                JDBCAuthModuleConf.class.cast(conf).setName("TestConf" + getUUIDString());
+                JDBCAuthModuleConf.class.cast(conf).setSql("SELECT * FROM table WHERE name=?");
+                JDBCAuthModuleConf.class.cast(conf).setFieldPassword("password");
+                JDBCAuthModuleConf.class.cast(conf).getPrincipalAttributeList().addAll(
+                        List.of("sn", "cn:commonName", "givenName"));
+                break;
+
+            case OIDC:
+                conf = new OIDCAuthModuleConf();
+                OIDCAuthModuleConf.class.cast(conf).setName("TestConf" + getUUIDString());
+                OIDCAuthModuleConf.class.cast(conf).setId("OIDCTestId");
+                OIDCAuthModuleConf.class.cast(conf).setDiscoveryUri("www.testurl.com");
+                OIDCAuthModuleConf.class.cast(conf).setUserIdAttribute("username");
+                OIDCAuthModuleConf.class.cast(conf).setResponseType("code");
+                OIDCAuthModuleConf.class.cast(conf).setScope("openid email profile");
+                break;
+
+            case SAML2_IDP:
+                conf = new SAML2IdPAuthModuleConf();
+                SAML2IdPAuthModuleConf.class.cast(conf).setName("TestConf" + getUUIDString());
+                SAML2IdPAuthModuleConf.class.cast(conf).setServiceProviderEntityId("testEntityId");
+                SAML2IdPAuthModuleConf.class.cast(conf).setProviderName("testProviderName");
+                SAML2IdPAuthModuleConf.class.cast(conf).setServiceProviderMetadataPath(
+                        "file:/etc/metadata");
+                break;
+
+            case SYNCOPE:
+                conf = new SyncopeAuthModuleConf();
+                SyncopeAuthModuleConf.class.cast(conf).setName("TestConf" + getUUIDString());
+                SyncopeAuthModuleConf.class.cast(conf).setDomain("Master");
+                SyncopeAuthModuleConf.class.cast(conf).setUrl("http://mydomain.com/syncope/rest");
+                break;
+
+            case U2F:
+                conf = new U2FAuthModuleConf();
+                U2FAuthModuleConf.class.cast(conf).setName("TestConf" + getUUIDString());
+                U2FAuthModuleConf.class.cast(conf).setExpireDevices(50);
+                break;
+
+            case RADIUS:
+                conf = new RadiusAuthModuleConf();
+                RadiusAuthModuleConf.class.cast(conf).setName("TestConf" + getUUIDString());
+                RadiusAuthModuleConf.class.cast(conf).setProtocol("MSCHAPv2");
+                RadiusAuthModuleConf.class.cast(conf).setInetAddress("1.2.3.4");
+                RadiusAuthModuleConf.class.cast(conf).setSharedSecret("xyz");
+                RadiusAuthModuleConf.class.cast(conf).setSocketTimeout(40);
+                break;
+
+            case STATIC:
+            default:
+                conf = new StaticAuthModuleConf();
+                StaticAuthModuleConf.class.cast(conf).setName("TestConf" + getUUIDString());
+                StaticAuthModuleConf.class.cast(conf).getUsers().put("user1", UUID.randomUUID().toString());
+                StaticAuthModuleConf.class.cast(conf).getUsers().put("user2", "user2Password123");
+                break;
+        }
+        authModuleTO.setConf(conf);
+
+        return authModuleTO;
+    }
+
+    @Test
+    public void findAll() {
+        List<AuthModuleTO> authModuleTOs = authModuleService.list();
+        assertNotNull(authModuleTOs);
+        assertFalse(authModuleTOs.isEmpty());
+        assertTrue(authModuleTOs.size() >= 10);
+    }
+
+    @Test
+    public void listByType() {
+        List<AuthModuleTO> authModuleTOs = authModuleService.list();
+        assertNotNull(authModuleTOs);
+        assertFalse(authModuleTOs.isEmpty());
+
+        assertTrue(authModuleTOs.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), LDAPAuthModuleConf.class)
+                && authModule.getName().equals("DefaultLDAPAuthModule")));
+        assertTrue(authModuleTOs.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), JDBCAuthModuleConf.class)
+                && authModule.getName().equals("DefaultJDBCAuthModule")));
+        assertTrue(authModuleTOs.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), GoogleMfaAuthModuleConf.class)
+                && authModule.getName().equals("DefaultGoogleMfaAuthModule")));
+        assertTrue(authModuleTOs.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), OIDCAuthModuleConf.class)
+                && authModule.getName().equals("DefaultOIDCAuthModule")));
+        assertTrue(authModuleTOs.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), SAML2IdPAuthModuleConf.class)
+                && authModule.getName().equals("DefaultSAML2IdPAuthModule")));
+        assertTrue(authModuleTOs.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), JaasAuthModuleConf.class)
+                && authModule.getName().equals("DefaultJaasAuthModule")));
+        assertTrue(authModuleTOs.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), StaticAuthModuleConf.class)
+                && authModule.getName().equals("DefaultStaticAuthModule")));
+        assertTrue(authModuleTOs.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), SyncopeAuthModuleConf.class)
+                && authModule.getName().equals("DefaultSyncopeAuthModule")));
+        assertTrue(authModuleTOs.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), U2FAuthModuleConf.class)
+                && authModule.getName().equals("DefaultU2FAuthModule")));
+        assertTrue(authModuleTOs.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), RadiusAuthModuleConf.class)
+                && authModule.getName().equals("DefaultRadiusAuthModule")));
+    }
+
+    @Test
+    public void getLDAPAuthModule() {
+        AuthModuleTO authModuleTO = authModuleService.read("be456831-593d-4003-b273-4c3fb61700df");
+
+        assertNotNull(authModuleTO);
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getName()));
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getDescription()));
+        assertTrue(isSpecificConf(authModuleTO.getConf(), LDAPAuthModuleConf.class));
+        assertFalse(isSpecificConf(authModuleTO.getConf(), JDBCAuthModuleConf.class));
+    }
+
+    @Test
+    public void getJDBCAuthModule() {
+        AuthModuleTO authModuleTO = authModuleService.read("4c3ed7e8-7008-11ea-bc55-0242ac130003");
+
+        assertNotNull(authModuleTO);
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getName()));
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getDescription()));
+        assertTrue(isSpecificConf(authModuleTO.getConf(), JDBCAuthModuleConf.class));
+        assertFalse(isSpecificConf(authModuleTO.getConf(), GoogleMfaAuthModuleConf.class));
+    }
+
+    @Test
+    public void getGoogleMfaAuthModule() {
+        AuthModuleTO authModuleTO = authModuleService.read("4c3ed4e6-7008-11ea-bc55-0242ac130003");
+
+        assertNotNull(authModuleTO);
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getName()));
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getDescription()));
+        assertTrue(isSpecificConf(authModuleTO.getConf(), GoogleMfaAuthModuleConf.class));
+        assertFalse(isSpecificConf(authModuleTO.getConf(), OIDCAuthModuleConf.class));
+    }
+
+    @Test
+    public void getOIDCAuthModule() {
+        AuthModuleTO authModuleTO = authModuleService.read("4c3ed8f6-7008-11ea-bc55-0242ac130003");
+
+        assertNotNull(authModuleTO);
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getName()));
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getDescription()));
+        assertTrue(isSpecificConf(authModuleTO.getConf(), OIDCAuthModuleConf.class));
+        assertFalse(isSpecificConf(authModuleTO.getConf(), SAML2IdPAuthModuleConf.class));
+    }
+
+    @Test
+    public void getSAML2IdPAuthModule() {
+        AuthModuleTO authModuleTO = authModuleService.read("4c3ed9d2-7008-11ea-bc55-0242ac130003");
+
+        assertNotNull(authModuleTO);
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getName()));
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getDescription()));
+        assertTrue(isSpecificConf(authModuleTO.getConf(), SAML2IdPAuthModuleConf.class));
+        assertFalse(isSpecificConf(authModuleTO.getConf(), JaasAuthModuleConf.class));
+    }
+
+    @Test
+    public void getJaasAuthModule() {
+        AuthModuleTO authModuleTO = authModuleService.read("4c3edbbc-7008-11ea-bc55-0242ac130003");
+
+        assertNotNull(authModuleTO);
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getName()));
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getDescription()));
+        assertTrue(isSpecificConf(authModuleTO.getConf(), JaasAuthModuleConf.class));
+        assertFalse(isSpecificConf(authModuleTO.getConf(), StaticAuthModuleConf.class));
+    }
+
+    @Test
+    public void getStaticAuthModule() {
+        AuthModuleTO authModuleTO = authModuleService.read("4c3edc98-7008-11ea-bc55-0242ac130003");
+
+        assertNotNull(authModuleTO);
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getName()));
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getDescription()));
+        assertTrue(isSpecificConf(authModuleTO.getConf(), StaticAuthModuleConf.class));
+        assertFalse(isSpecificConf(authModuleTO.getConf(), SyncopeAuthModuleConf.class));
+    }
+
+    @Test
+    public void getSyncopeAuthModule() {
+        AuthModuleTO authModuleTO = authModuleService.read("4c3edd60-7008-11ea-bc55-0242ac130003");
+
+        assertNotNull(authModuleTO);
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getName()));
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getDescription()));
+        assertTrue(isSpecificConf(authModuleTO.getConf(), SyncopeAuthModuleConf.class));
+        assertFalse(isSpecificConf(authModuleTO.getConf(), RadiusAuthModuleConf.class));
+    }
+
+    @Test
+    public void getRadiusAuthModule() {
+        AuthModuleTO authModuleTO = authModuleService.read("07c528f3-63b4-4dc1-a4da-87f35b8bdec8");
+
+        assertNotNull(authModuleTO);
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getName()));
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getDescription()));
+        assertTrue(isSpecificConf(authModuleTO.getConf(), RadiusAuthModuleConf.class));
+        assertFalse(isSpecificConf(authModuleTO.getConf(), U2FAuthModuleConf.class));
+    }
+
+    @Test
+    public void getU2FAuthModule() {
+        AuthModuleTO authModuleTO = authModuleService.read("f6e1288d-50d9-45fe-82ee-597c42242205");
+
+        assertNotNull(authModuleTO);
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getName()));
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getDescription()));
+        assertTrue(isSpecificConf(authModuleTO.getConf(), U2FAuthModuleConf.class));
+        assertFalse(isSpecificConf(authModuleTO.getConf(), LDAPAuthModuleConf.class));
+    }
+
+    @Test
+    public void create() throws IOException {
+        EnumSet.allOf(AuthModuleSupportedType.class).forEach(type -> testCreate(type));
+    }
+
+    @Test
+    public void updateGoogleMfaAuthModule() {
+        AuthModuleTO googleMfaAuthModuleTO = authModuleService.read("4c3ed4e6-7008-11ea-bc55-0242ac130003");
+        assertNotNull(googleMfaAuthModuleTO);
+
+        AuthModuleTO newGoogleMfaAuthModuleTO = buildAuthModuleTO(AuthModuleSupportedType.GOOGLE_MFA);
+        newGoogleMfaAuthModuleTO = createAuthModule(newGoogleMfaAuthModuleTO);
+        assertNotNull(newGoogleMfaAuthModuleTO);
+
+        AuthModuleConf conf = googleMfaAuthModuleTO.getConf();
+        assertNotNull(conf);
+        GoogleMfaAuthModuleConf.class.cast(conf).setLabel("newLabel");
+        newGoogleMfaAuthModuleTO.setConf(conf);
+
+        // update new auth module
+        authModuleService.update(newGoogleMfaAuthModuleTO);
+        newGoogleMfaAuthModuleTO = authModuleService.read(newGoogleMfaAuthModuleTO.getKey());
+        assertNotNull(newGoogleMfaAuthModuleTO);
+
+        conf = newGoogleMfaAuthModuleTO.getConf();
+        assertEquals("newLabel", GoogleMfaAuthModuleConf.class.cast(conf).getLabel());
+    }
+
+    @Test
+    public void updateLDAPAuthModule() {
+        AuthModuleTO ldapAuthModuleTO = authModuleService.read("be456831-593d-4003-b273-4c3fb61700df");
+        assertNotNull(ldapAuthModuleTO);
+
+        AuthModuleTO newLdapAuthModuleTO = buildAuthModuleTO(AuthModuleSupportedType.LDAP);
+        newLdapAuthModuleTO = createAuthModule(newLdapAuthModuleTO);
+        assertNotNull(newLdapAuthModuleTO);
+
+        AuthModuleConf conf = ldapAuthModuleTO.getConf();
+        assertNotNull(conf);
+        LDAPAuthModuleConf.class.cast(conf).setSubtreeSearch(false);
+        newLdapAuthModuleTO.setConf(conf);
+
+        // update new auth module
+        authModuleService.update(newLdapAuthModuleTO);
+        newLdapAuthModuleTO = authModuleService.read(newLdapAuthModuleTO.getKey());
+        assertNotNull(newLdapAuthModuleTO);
+
+        conf = newLdapAuthModuleTO.getConf();
+        assertFalse(LDAPAuthModuleConf.class.cast(conf).isSubtreeSearch());
+    }
+
+    @Test
+    public void updateSAML2IdPAuthModule() {
+        AuthModuleTO saml2IdpAuthModuleTO = authModuleService.read("4c3ed9d2-7008-11ea-bc55-0242ac130003");
+        assertNotNull(saml2IdpAuthModuleTO);
+
+        AuthModuleTO newsaml2IdpAuthModuleTO = buildAuthModuleTO(AuthModuleSupportedType.SAML2_IDP);
+        newsaml2IdpAuthModuleTO = createAuthModule(newsaml2IdpAuthModuleTO);
+        assertNotNull(newsaml2IdpAuthModuleTO);
+
+        AuthModuleConf conf = saml2IdpAuthModuleTO.getConf();
+        assertNotNull(conf);
+        SAML2IdPAuthModuleConf.class.cast(conf).setServiceProviderEntityId("newEntityId");
+        newsaml2IdpAuthModuleTO.setConf(conf);
+
+        // update new auth module
+        authModuleService.update(newsaml2IdpAuthModuleTO);
+        newsaml2IdpAuthModuleTO = authModuleService.read(newsaml2IdpAuthModuleTO.getKey());
+        assertNotNull(newsaml2IdpAuthModuleTO);
+
+        conf = newsaml2IdpAuthModuleTO.getConf();
+        assertEquals("newEntityId", SAML2IdPAuthModuleConf.class.cast(conf).getServiceProviderEntityId());
+    }
+
+    @Test
+    public void updateOIDCAuthModule() {
+        AuthModuleTO oidcAuthModuleTO = authModuleService.read("4c3ed8f6-7008-11ea-bc55-0242ac130003");
+        assertNotNull(oidcAuthModuleTO);
+
+        AuthModuleTO newOIDCAuthModuleTO = buildAuthModuleTO(AuthModuleSupportedType.OIDC);
+        newOIDCAuthModuleTO = createAuthModule(newOIDCAuthModuleTO);
+        assertNotNull(newOIDCAuthModuleTO);
+
+        AuthModuleConf conf = oidcAuthModuleTO.getConf();
+        assertNotNull(conf);
+        OIDCAuthModuleConf.class.cast(conf).setResponseType("newCode");
+        newOIDCAuthModuleTO.setConf(conf);
+
+        // update new auth module
+        authModuleService.update(newOIDCAuthModuleTO);
+        newOIDCAuthModuleTO = authModuleService.read(newOIDCAuthModuleTO.getKey());
+        assertNotNull(newOIDCAuthModuleTO);
+
+        conf = newOIDCAuthModuleTO.getConf();
+        assertEquals("newCode", OIDCAuthModuleConf.class.cast(conf).getResponseType());
+    }
+
+    @Test
+    public void updateJDBCAuthModule() {
+        AuthModuleTO jdbcAuthModuleTO = authModuleService.read("4c3ed7e8-7008-11ea-bc55-0242ac130003");
+        assertNotNull(jdbcAuthModuleTO);
+
+        AuthModuleTO newJDBCAuthModuleTO = buildAuthModuleTO(AuthModuleSupportedType.JDBC);
+        newJDBCAuthModuleTO = createAuthModule(newJDBCAuthModuleTO);
+        assertNotNull(newJDBCAuthModuleTO);
+
+        AuthModuleConf conf = jdbcAuthModuleTO.getConf();
+        assertNotNull(conf);
+        JDBCAuthModuleConf.class.cast(conf).setFieldPassword("uPassword");
+        newJDBCAuthModuleTO.setConf(conf);
+
+        // update new auth module
+        authModuleService.update(newJDBCAuthModuleTO);
+        newJDBCAuthModuleTO = authModuleService.read(newJDBCAuthModuleTO.getKey());
+        assertNotNull(newJDBCAuthModuleTO);
+
+        conf = newJDBCAuthModuleTO.getConf();
+        assertEquals("uPassword", JDBCAuthModuleConf.class.cast(conf).getFieldPassword());
+    }
+
+    @Test
+    public void updateJaasAuthModule() {
+        AuthModuleTO jaasAuthModuleTO = authModuleService.read("4c3edbbc-7008-11ea-bc55-0242ac130003");
+        assertNotNull(jaasAuthModuleTO);
+
+        AuthModuleTO newJaasAuthModuleTO = buildAuthModuleTO(AuthModuleSupportedType.JAAS);
+        newJaasAuthModuleTO = createAuthModule(newJaasAuthModuleTO);
+        assertNotNull(newJaasAuthModuleTO);
+
+        AuthModuleConf conf = jaasAuthModuleTO.getConf();
+        assertNotNull(conf);
+        JaasAuthModuleConf.class.cast(conf).setRealm("SYNCOPE_NEW");
+        newJaasAuthModuleTO.setConf(conf);
+
+        // update new auth module
+        authModuleService.update(newJaasAuthModuleTO);
+        newJaasAuthModuleTO = authModuleService.read(newJaasAuthModuleTO.getKey());
+        assertNotNull(newJaasAuthModuleTO);
+
+        conf = newJaasAuthModuleTO.getConf();
+        assertEquals("SYNCOPE_NEW", JaasAuthModuleConf.class.cast(conf).getRealm());
+    }
+
+    @Test
+    public void updateStaticAuthModule() {
+        AuthModuleTO staticAuthModuleTO = authModuleService.read("4c3edc98-7008-11ea-bc55-0242ac130003");
+        assertNotNull(staticAuthModuleTO);
+
+        AuthModuleTO newStaticAuthModuleTO = buildAuthModuleTO(AuthModuleSupportedType.STATIC);
+        newStaticAuthModuleTO = createAuthModule(newStaticAuthModuleTO);
+        assertNotNull(newStaticAuthModuleTO);
+
+        AuthModuleConf conf = staticAuthModuleTO.getConf();
+        assertNotNull(conf);
+        assertEquals(1, StaticAuthModuleConf.class.cast(conf).getUsers().size());
+        StaticAuthModuleConf.class.cast(conf).getUsers().put("user3", "user3Password123");
+        newStaticAuthModuleTO.setConf(conf);
+
+        // update new auth module
+        authModuleService.update(newStaticAuthModuleTO);
+        newStaticAuthModuleTO = authModuleService.read(newStaticAuthModuleTO.getKey());
+        assertNotNull(newStaticAuthModuleTO);
+
+        conf = newStaticAuthModuleTO.getConf();
+        assertEquals(2, StaticAuthModuleConf.class.cast(conf).getUsers().size());
+    }
+
+    @Test
+    public void updateRadiusAuthModule() {
+        AuthModuleTO radiusAuthModuleTO = authModuleService.read("07c528f3-63b4-4dc1-a4da-87f35b8bdec8");
+        assertNotNull(radiusAuthModuleTO);
+
+        AuthModuleTO newRadiusAuthModuleTO = buildAuthModuleTO(AuthModuleSupportedType.RADIUS);
+        newRadiusAuthModuleTO = createAuthModule(newRadiusAuthModuleTO);
+        assertNotNull(newRadiusAuthModuleTO);
+
+        AuthModuleConf conf = radiusAuthModuleTO.getConf();
+        assertNotNull(conf);
+        RadiusAuthModuleConf.class.cast(conf).setSocketTimeout(45);
+        newRadiusAuthModuleTO.setConf(conf);
+
+        // update new auth module
+        authModuleService.update(newRadiusAuthModuleTO);
+        newRadiusAuthModuleTO = authModuleService.read(newRadiusAuthModuleTO.getKey());
+        assertNotNull(newRadiusAuthModuleTO);
+
+        conf = newRadiusAuthModuleTO.getConf();
+        assertEquals(45, RadiusAuthModuleConf.class.cast(conf).getSocketTimeout());
+    }
+
+    @Test
+    public void updateU2fAuthModule() {
+        AuthModuleTO u2fAuthModuleTO = authModuleService.read("f6e1288d-50d9-45fe-82ee-597c42242205");
+        assertNotNull(u2fAuthModuleTO);
+
+        AuthModuleTO newU2fAuthModuleTO = buildAuthModuleTO(AuthModuleSupportedType.U2F);
+        newU2fAuthModuleTO = createAuthModule(newU2fAuthModuleTO);
+        assertNotNull(newU2fAuthModuleTO);
+
+        AuthModuleConf conf = u2fAuthModuleTO.getConf();
+        assertNotNull(conf);
+        U2FAuthModuleConf.class.cast(conf).setExpireDevices(24);
+        newU2fAuthModuleTO.setConf(conf);
+
+        // update new auth module
+        authModuleService.update(newU2fAuthModuleTO);
+        newU2fAuthModuleTO = authModuleService.read(newU2fAuthModuleTO.getKey());
+        assertNotNull(newU2fAuthModuleTO);
+
+        conf = newU2fAuthModuleTO.getConf();
+        assertEquals(24, U2FAuthModuleConf.class.cast(conf).getExpireDevices());
+    }
+
+    @Test
+    public void updateSyncopeAuthModule() {
+        AuthModuleTO syncopeAuthModuleTO = authModuleService.read("4c3edd60-7008-11ea-bc55-0242ac130003");
+        assertNotNull(syncopeAuthModuleTO);
+
+        AuthModuleTO newSyncopeAuthModuleTO = buildAuthModuleTO(AuthModuleSupportedType.SYNCOPE);
+        newSyncopeAuthModuleTO = createAuthModule(newSyncopeAuthModuleTO);
+        assertNotNull(newSyncopeAuthModuleTO);
+
+        AuthModuleConf conf = syncopeAuthModuleTO.getConf();
+        assertNotNull(conf);
+        SyncopeAuthModuleConf.class.cast(conf).setDomain("Two");
+        newSyncopeAuthModuleTO.setConf(conf);
+
+        // update new auth module
+        authModuleService.update(newSyncopeAuthModuleTO);
+        newSyncopeAuthModuleTO = authModuleService.read(newSyncopeAuthModuleTO.getKey());
+        assertNotNull(newSyncopeAuthModuleTO);
+
+        conf = newSyncopeAuthModuleTO.getConf();
+        assertEquals("Two", SyncopeAuthModuleConf.class.cast(conf).getDomain());
+    }
+
+    @Test
+    public void delete() throws IOException {
+        EnumSet.allOf(AuthModuleSupportedType.class).forEach(type -> testDelete(type));
+    }
+
+    private void testCreate(final AuthModuleSupportedType type) {
+        AuthModuleTO authModuleTO = createAuthModule(buildAuthModuleTO(type));
+        assertNotNull(authModuleTO);
+        assertTrue(authModuleTO.getName().contains(
+                "Test" + type + "AuthenticationModule"));
+        assertTrue(authModuleTO.getDescription().contains(
+                "A test " + type + " Authentication Module"));
+    }
+
+    private void testDelete(final AuthModuleSupportedType type) {
+        AuthModuleTO authModuleTO = buildAuthModuleTO(type);
+        AuthModuleTO read = createAuthModule(authModuleTO);
+        assertNotNull(read);
+        authModuleService.delete(read.getKey());
+        try {
+            authModuleService.read(read.getKey());
+            fail("This should not happen");
+        } catch (SyncopeClientException e) {
+            assertNotNull(e);
+        }
+    }
+
+    private boolean isSpecificConf(final AuthModuleConf conf, final Class<? extends AuthModuleConf> clazz) {
+        return ClassUtils.isAssignable(clazz, conf.getClass());
+    }
+}