Bug: feat(users): add schema and apis for org authentication methods



Setup table and end points for org authentication methods
- This will be used to support SSO login
- Orgs can also setup multiple other authentication methods
diff --git a/config/config.example.toml b/config/config.example.toml
index 332ab4bc00b..9bdf0b80723 100644
--- a/config/config.example.toml
+++ b/config/config.example.toml
@@ -644,4 +644,7 @@ enabled = false
 global_tenant = { schema = "public", redis_key_prefix = "" }
 
 [multitenancy.tenants]
-public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default"} # schema -> Postgres db schema, redis_key_prefix -> redis key distinguisher, base_url -> url of the tenant 
\ No newline at end of file
+public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default"} # schema -> Postgres db schema, redis_key_prefix -> redis key distinguisher, base_url -> url of the tenant 
+
+[user_auth_methods]
+encryption_key = "" # Encryption key used for encrypting data in user_authentication_methods table
diff --git a/config/deployments/env_specific.toml b/config/deployments/env_specific.toml
index 162444c9098..ccb26042f56 100644
--- a/config/deployments/env_specific.toml
+++ b/config/deployments/env_specific.toml
@@ -259,4 +259,7 @@ enabled = false
 global_tenant = { schema = "public", redis_key_prefix = "" }
 
 [multitenancy.tenants]
-public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default"}
\ No newline at end of file
+public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default"}
+
+[user_auth_methods]
+encryption_key = "user_auth_table_encryption_key" # Encryption key used for encrypting data in user_authentication_methods table
diff --git a/config/development.toml b/config/development.toml
index c2f87720678..29f693f6bf2 100644
--- a/config/development.toml
+++ b/config/development.toml
@@ -654,3 +654,6 @@ global_tenant = { schema = "public", redis_key_prefix = "" }
 
 [multitenancy.tenants]
 public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default"}
+
+[user_auth_methods]
+encryption_key = "A8EF32E029BC3342E54BF2E172A4D7AA43E8EF9D2C3A624A9F04E2EF79DC698F"
diff --git a/config/docker_compose.toml b/config/docker_compose.toml
index 328dd30520b..1b0538d1af0 100644
--- a/config/docker_compose.toml
+++ b/config/docker_compose.toml
@@ -507,4 +507,7 @@ enabled = false
 global_tenant = { schema = "public", redis_key_prefix = "" }
 
 [multitenancy.tenants]
-public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default"}
\ No newline at end of file
+public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default"}
+
+[user_auth_methods]
+encryption_key = "A8EF32E029BC3342E54BF2E172A4D7AA43E8EF9D2C3A624A9F04E2EF79DC698F"
diff --git a/crates/api_models/src/events/user.rs b/crates/api_models/src/events/user.rs
index 287bafaace8..e5d217cd8ea 100644
--- a/crates/api_models/src/events/user.rs
+++ b/crates/api_models/src/events/user.rs
@@ -11,14 +11,15 @@ use crate::user::{
         GetMetaDataRequest, GetMetaDataResponse, GetMultipleMetaDataPayload, SetMetaDataRequest,
     },
     AcceptInviteFromEmailRequest, AuthorizeResponse, BeginTotpResponse, ChangePasswordRequest,
-    ConnectAccountRequest, CreateInternalUserRequest, DashboardEntryResponse,
-    ForgotPasswordRequest, GetUserDetailsResponse, GetUserRoleDetailsRequest,
-    GetUserRoleDetailsResponse, InviteUserRequest, ListUsersResponse, ReInviteUserRequest,
-    RecoveryCodes, ResetPasswordRequest, RotatePasswordRequest, SendVerifyEmailRequest,
-    SignInResponse, SignUpRequest, SignUpWithMerchantIdRequest, SwitchMerchantIdRequest,
-    TokenOrPayloadResponse, TokenResponse, TwoFactorAuthStatusResponse,
-    UpdateUserAccountDetailsRequest, UserFromEmailRequest, UserMerchantCreate, VerifyEmailRequest,
-    VerifyRecoveryCodeRequest, VerifyTotpRequest,
+    ConnectAccountRequest, CreateInternalUserRequest, CreateUserAuthenticationMethodRequest,
+    DashboardEntryResponse, ForgotPasswordRequest, GetUserAuthenticationMethodsRequest,
+    GetUserDetailsResponse, GetUserRoleDetailsRequest, GetUserRoleDetailsResponse,
+    InviteUserRequest, ListUsersResponse, ReInviteUserRequest, RecoveryCodes, ResetPasswordRequest,
+    RotatePasswordRequest, SendVerifyEmailRequest, SignInResponse, SignUpRequest,
+    SignUpWithMerchantIdRequest, SwitchMerchantIdRequest, TokenOrPayloadResponse, TokenResponse,
+    TwoFactorAuthStatusResponse, UpdateUserAccountDetailsRequest,
+    UpdateUserAuthenticationMethodRequest, UserFromEmailRequest, UserMerchantCreate,
+    VerifyEmailRequest, VerifyRecoveryCodeRequest, VerifyTotpRequest,
 };
 
 impl ApiEventMetric for DashboardEntryResponse {
@@ -77,7 +78,10 @@ common_utils::impl_misc_api_event_type!(
     BeginTotpResponse,
     VerifyRecoveryCodeRequest,
     VerifyTotpRequest,
-    RecoveryCodes
+    RecoveryCodes,
+    GetUserAuthenticationMethodsRequest,
+    CreateUserAuthenticationMethodRequest,
+    UpdateUserAuthenticationMethodRequest
 );
 
 #[cfg(feature = "dummy_connector")]
diff --git a/crates/api_models/src/user.rs b/crates/api_models/src/user.rs
index a61b9fd7dff..6d567e7dca1 100644
--- a/crates/api_models/src/user.rs
+++ b/crates/api_models/src/user.rs
@@ -280,3 +280,78 @@ pub struct VerifyRecoveryCodeRequest {
 pub struct RecoveryCodes {
     pub recovery_codes: Vec<Secret<String>>,
 }
+
+#[derive(Debug, serde::Deserialize, serde::Serialize)]
+#[serde(tag = "auth_type")]
+#[serde(rename_all = "snake_case")]
+pub enum AuthConfig {
+    OpenIdConnect {
+        private_config: OpenIdConnectPrivateConfig,
+        public_config: OpenIdConnectPublicConfig,
+    },
+    MagicLink,
+    Password,
+}
+
+#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
+pub struct OpenIdConnectPrivateConfig {
+    pub base_url: String,
+    pub client_id: Secret<String>,
+    pub client_secret: Secret<String>,
+    pub private_key: Option<Secret<String>>,
+}
+
+#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
+pub struct OpenIdConnectPublicConfig {
+    pub name: OpenIdProvider,
+}
+
+#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
+#[serde(rename_all = "snake_case")]
+pub enum OpenIdProvider {
+    Okta,
+}
+
+#[derive(Debug, serde::Deserialize, serde::Serialize)]
+pub struct OpenIdConnect {
+    pub name: OpenIdProvider,
+    pub base_url: String,
+    pub client_id: String,
+    pub client_secret: Secret<String>,
+    pub private_key: Option<Secret<String>>,
+}
+
+#[derive(Debug, serde::Deserialize, serde::Serialize)]
+pub struct CreateUserAuthenticationMethodRequest {
+    pub owner_id: String,
+    pub owner_type: common_enums::Owner,
+    pub auth_method: AuthConfig,
+    pub allow_signup: bool,
+}
+
+#[derive(Debug, serde::Deserialize, serde::Serialize)]
+pub struct UpdateUserAuthenticationMethodRequest {
+    pub id: String,
+    // TODO: When adding more fields make config and new fields option
+    pub auth_method: AuthConfig,
+}
+
+#[derive(Debug, serde::Deserialize, serde::Serialize)]
+pub struct GetUserAuthenticationMethodsRequest {
+    pub auth_id: String,
+}
+
+#[derive(Debug, serde::Deserialize, serde::Serialize)]
+pub struct UserAuthenticationMethodResponse {
+    pub id: String,
+    pub auth_id: String,
+    pub auth_method: AuthMethodDetails,
+    pub allow_signup: bool,
+}
+
+#[derive(Debug, serde::Deserialize, serde::Serialize)]
+pub struct AuthMethodDetails {
+    #[serde(rename = "type")]
+    pub auth_type: common_enums::UserAuthType,
+    pub name: Option<OpenIdProvider>,
+}
diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs
index f3a3b02155d..decbf9fd12a 100644
--- a/crates/common_enums/src/enums.rs
+++ b/crates/common_enums/src/enums.rs
@@ -2768,3 +2768,45 @@ pub enum TokenPurpose {
     AcceptInvite,
     UserInfo,
 }
+
+#[derive(
+    Clone,
+    Copy,
+    Debug,
+    Default,
+    Eq,
+    PartialEq,
+    serde::Deserialize,
+    serde::Serialize,
+    strum::Display,
+    strum::EnumString,
+)]
+#[router_derive::diesel_enum(storage_type = "text")]
+#[strum(serialize_all = "snake_case")]
+#[serde(rename_all = "snake_case")]
+pub enum UserAuthType {
+    OpenIdConnect,
+    MagicLink,
+    #[default]
+    Password,
+}
+
+#[derive(
+    Clone,
+    Copy,
+    Debug,
+    Eq,
+    PartialEq,
+    serde::Deserialize,
+    serde::Serialize,
+    strum::Display,
+    strum::EnumString,
+)]
+#[router_derive::diesel_enum(storage_type = "text")]
+#[strum(serialize_all = "snake_case")]
+#[serde(rename_all = "snake_case")]
+pub enum Owner {
+    Organization,
+    Tenant,
+    Internal,
+}
diff --git a/crates/diesel_models/src/lib.rs b/crates/diesel_models/src/lib.rs
index d7d10569f7f..36dec7dde5c 100644
--- a/crates/diesel_models/src/lib.rs
+++ b/crates/diesel_models/src/lib.rs
@@ -44,6 +44,7 @@ pub mod routing_algorithm;
 #[allow(unused_qualifications)]
 pub mod schema;
 pub mod user;
+pub mod user_authentication_method;
 pub mod user_key_store;
 pub mod user_role;
 
diff --git a/crates/diesel_models/src/query.rs b/crates/diesel_models/src/query.rs
index 335c2db916d..9fbdf53554f 100644
--- a/crates/diesel_models/src/query.rs
+++ b/crates/diesel_models/src/query.rs
@@ -36,5 +36,6 @@ pub mod reverse_lookup;
 pub mod role;
 pub mod routing_algorithm;
 pub mod user;
+pub mod user_authentication_method;
 pub mod user_key_store;
 pub mod user_role;
diff --git a/crates/diesel_models/src/query/user_authentication_method.rs b/crates/diesel_models/src/query/user_authentication_method.rs
new file mode 100644
index 00000000000..08ea6556b82
--- /dev/null
+++ b/crates/diesel_models/src/query/user_authentication_method.rs
@@ -0,0 +1,60 @@
+use diesel::{associations::HasTable, ExpressionMethods};
+
+use crate::{
+    query::generics, schema::user_authentication_methods::dsl, user_authentication_method::*,
+    PgPooledConn, StorageResult,
+};
+
+impl UserAuthenticationMethodNew {
+    pub async fn insert(self, conn: &PgPooledConn) -> StorageResult<UserAuthenticationMethod> {
+        generics::generic_insert(conn, self).await
+    }
+}
+
+impl UserAuthenticationMethod {
+    pub async fn list_user_authentication_methods_for_auth_id(
+        conn: &PgPooledConn,
+        auth_id: &str,
+    ) -> StorageResult<Vec<Self>> {
+        generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
+            conn,
+            dsl::auth_id.eq(auth_id.to_owned()),
+            None,
+            None,
+            Some(dsl::last_modified_at.asc()),
+        )
+        .await
+    }
+
+    pub async fn list_user_authentication_methods_for_owner_id(
+        conn: &PgPooledConn,
+        owner_id: &str,
+    ) -> StorageResult<Vec<Self>> {
+        generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
+            conn,
+            dsl::owner_id.eq(owner_id.to_owned()),
+            None,
+            None,
+            Some(dsl::last_modified_at.asc()),
+        )
+        .await
+    }
+
+    pub async fn update_user_authentication_method(
+        conn: &PgPooledConn,
+        id: &str,
+        user_authentication_method_update: UserAuthenticationMethodUpdate,
+    ) -> StorageResult<Self> {
+        generics::generic_update_with_unique_predicate_get_result::<
+            <Self as HasTable>::Table,
+            _,
+            _,
+            _,
+        >(
+            conn,
+            dsl::id.eq(id.to_owned()),
+            OrgAuthenticationMethodUpdateInternal::from(user_authentication_method_update),
+        )
+        .await
+    }
+}
diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs
index 08d8f423b72..5d796444d7f 100644
--- a/crates/diesel_models/src/schema.rs
+++ b/crates/diesel_models/src/schema.rs
@@ -1172,6 +1172,29 @@ diesel::table! {
     }
 }
 
+diesel::table! {
+    use diesel::sql_types::*;
+    use crate::enums::diesel_exports::*;
+
+    user_authentication_methods (id) {
+        #[max_length = 64]
+        id -> Varchar,
+        #[max_length = 64]
+        auth_id -> Varchar,
+        #[max_length = 64]
+        owner_id -> Varchar,
+        #[max_length = 64]
+        owner_type -> Varchar,
+        #[max_length = 64]
+        auth_type -> Varchar,
+        private_config -> Nullable<Bytea>,
+        public_config -> Nullable<Jsonb>,
+        allow_signup -> Bool,
+        created_at -> Timestamp,
+        last_modified_at -> Timestamp,
+    }
+}
+
 diesel::table! {
     use diesel::sql_types::*;
     use crate::enums::diesel_exports::*;
@@ -1270,6 +1293,7 @@ diesel::allow_tables_to_appear_in_same_query!(
     reverse_lookup,
     roles,
     routing_algorithm,
+    user_authentication_methods,
     user_key_store,
     user_roles,
     users,
diff --git a/crates/diesel_models/src/user_authentication_method.rs b/crates/diesel_models/src/user_authentication_method.rs
new file mode 100644
index 00000000000..2b4ed23c1d8
--- /dev/null
+++ b/crates/diesel_models/src/user_authentication_method.rs
@@ -0,0 +1,65 @@
+use diesel::{AsChangeset, Identifiable, Insertable, Queryable};
+use time::PrimitiveDateTime;
+
+use crate::{encryption::Encryption, enums, schema::user_authentication_methods};
+
+#[derive(Clone, Debug, Identifiable, Queryable)]
+#[diesel(table_name = user_authentication_methods)]
+pub struct UserAuthenticationMethod {
+    pub id: String,
+    pub auth_id: String,
+    pub owner_id: String,
+    pub owner_type: enums::Owner,
+    pub auth_type: enums::UserAuthType,
+    pub private_config: Option<Encryption>,
+    pub public_config: Option<serde_json::Value>,
+    pub allow_signup: bool,
+    pub created_at: PrimitiveDateTime,
+    pub last_modified_at: PrimitiveDateTime,
+}
+
+#[derive(router_derive::Setter, Clone, Debug, Insertable, router_derive::DebugAsDisplay)]
+#[diesel(table_name = user_authentication_methods)]
+pub struct UserAuthenticationMethodNew {
+    pub id: String,
+    pub auth_id: String,
+    pub owner_id: String,
+    pub owner_type: enums::Owner,
+    pub auth_type: enums::UserAuthType,
+    pub private_config: Option<Encryption>,
+    pub public_config: Option<serde_json::Value>,
+    pub allow_signup: bool,
+    pub created_at: PrimitiveDateTime,
+    pub last_modified_at: PrimitiveDateTime,
+}
+
+#[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay)]
+#[diesel(table_name = user_authentication_methods)]
+pub struct OrgAuthenticationMethodUpdateInternal {
+    pub private_config: Option<Encryption>,
+    pub public_config: Option<serde_json::Value>,
+    pub last_modified_at: PrimitiveDateTime,
+}
+
+pub enum UserAuthenticationMethodUpdate {
+    UpdateConfig {
+        private_config: Option<Encryption>,
+        public_config: Option<serde_json::Value>,
+    },
+}
+
+impl From<UserAuthenticationMethodUpdate> for OrgAuthenticationMethodUpdateInternal {
+    fn from(value: UserAuthenticationMethodUpdate) -> Self {
+        let last_modified_at = common_utils::date_time::now();
+        match value {
+            UserAuthenticationMethodUpdate::UpdateConfig {
+                private_config,
+                public_config,
+            } => Self {
+                private_config,
+                public_config,
+                last_modified_at,
+            },
+        }
+    }
+}
diff --git a/crates/router/src/configs/secrets_transformers.rs b/crates/router/src/configs/secrets_transformers.rs
index 2f90833905e..1ad65c363b0 100644
--- a/crates/router/src/configs/secrets_transformers.rs
+++ b/crates/router/src/configs/secrets_transformers.rs
@@ -220,6 +220,22 @@ impl SecretsHandler for settings::Secrets {
     }
 }
 
+#[async_trait::async_trait]
+impl SecretsHandler for settings::UserAuthMethodSettings {
+    async fn convert_to_raw_secret(
+        value: SecretStateContainer<Self, SecuredSecret>,
+        secret_management_client: &dyn SecretManagementInterface,
+    ) -> CustomResult<SecretStateContainer<Self, RawSecret>, SecretsManagementError> {
+        let user_auth_methods = value.get_inner();
+
+        let encryption_key = secret_management_client
+            .get_secret(user_auth_methods.encryption_key.clone())
+            .await?;
+
+        Ok(value.transition_state(|_| Self { encryption_key }))
+    }
+}
+
 /// # Panics
 ///
 /// Will panic even if kms decryption fails for at least one field
@@ -302,6 +318,14 @@ pub(crate) async fn fetch_raw_secrets(
     .await
     .expect("Failed to decrypt payment method auth configs");
 
+    #[allow(clippy::expect_used)]
+    let user_auth_methods = settings::UserAuthMethodSettings::convert_to_raw_secret(
+        conf.user_auth_methods,
+        secret_management_client,
+    )
+    .await
+    .expect("Failed to decrypt user_auth_methods configs");
+
     Settings {
         server: conf.server,
         master_database,
@@ -368,5 +392,6 @@ pub(crate) async fn fetch_raw_secrets(
         unmasked_headers: conf.unmasked_headers,
         saved_payment_methods: conf.saved_payment_methods,
         multitenancy: conf.multitenancy,
+        user_auth_methods,
     }
 }
diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs
index daba006a56c..9e345315e98 100644
--- a/crates/router/src/configs/settings.rs
+++ b/crates/router/src/configs/settings.rs
@@ -123,6 +123,7 @@ pub struct Settings<S: SecretState> {
     pub unmasked_headers: UnmaskedHeaders,
     pub multitenancy: Multitenancy,
     pub saved_payment_methods: EligiblePaymentMethods,
+    pub user_auth_methods: SecretStateContainer<UserAuthMethodSettings, S>,
 }
 
 #[derive(Debug, Deserialize, Clone, Default)]
@@ -615,6 +616,11 @@ pub struct ConnectorRequestReferenceIdConfig {
     pub merchant_ids_send_payment_id_as_connector_request_id: HashSet<String>,
 }
 
+#[derive(Debug, Deserialize, Clone, Default)]
+pub struct UserAuthMethodSettings {
+    pub encryption_key: Secret<String>,
+}
+
 impl Settings<SecuredSecret> {
     pub fn new() -> ApplicationResult<Self> {
         Self::with_config_path(None)
diff --git a/crates/router/src/core/errors/user.rs b/crates/router/src/core/errors/user.rs
index 865bc1d4124..28b80e16cc0 100644
--- a/crates/router/src/core/errors/user.rs
+++ b/crates/router/src/core/errors/user.rs
@@ -80,6 +80,12 @@ pub enum UserErrors {
     TwoFactorAuthNotSetup,
     #[error("TOTP secret not found")]
     TotpSecretNotFound,
+    #[error("User auth method already exists")]
+    UserAuthMethodAlreadyExists,
+    #[error("Invalid user auth method operation")]
+    InvalidUserAuthMethodOperation,
+    #[error("Auth config parsing error")]
+    AuthConfigParsingError,
 }
 
 impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorResponse> for UserErrors {
@@ -204,6 +210,15 @@ impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorRespon
             Self::TotpSecretNotFound => {
                 AER::BadRequest(ApiError::new(sub_code, 42, self.get_error_message(), None))
             }
+            Self::UserAuthMethodAlreadyExists => {
+                AER::BadRequest(ApiError::new(sub_code, 43, self.get_error_message(), None))
+            }
+            Self::InvalidUserAuthMethodOperation => {
+                AER::BadRequest(ApiError::new(sub_code, 44, self.get_error_message(), None))
+            }
+            Self::AuthConfigParsingError => {
+                AER::BadRequest(ApiError::new(sub_code, 45, self.get_error_message(), None))
+            }
         }
     }
 }
@@ -247,6 +262,9 @@ impl UserErrors {
             Self::TwoFactorAuthRequired => "Two factor auth required",
             Self::TwoFactorAuthNotSetup => "Two factor auth not setup",
             Self::TotpSecretNotFound => "TOTP secret not found",
+            Self::UserAuthMethodAlreadyExists => "User auth method already exists",
+            Self::InvalidUserAuthMethodOperation => "Invalid user auth method operation",
+            Self::AuthConfigParsingError => "Auth config parsing error",
         }
     }
 }
diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs
index 7e79e9e2e89..6803b79d6ce 100644
--- a/crates/router/src/core/user.rs
+++ b/crates/router/src/core/user.rs
@@ -1,11 +1,13 @@
 use std::collections::HashMap;
 
 use api_models::user::{self as user_api, InviteMultipleUserResponse};
+use common_utils::ext_traits::ValueExt;
 #[cfg(feature = "email")]
 use diesel_models::user_role::UserRoleUpdate;
 use diesel_models::{
     enums::{TotpStatus, UserStatus},
     user as storage_user,
+    user_authentication_method::{UserAuthenticationMethodNew, UserAuthenticationMethodUpdate},
     user_role::UserRoleNew,
 };
 use error_stack::{report, ResultExt};
@@ -884,7 +886,7 @@ pub async fn resend_invite(
             if e.current_context().is_db_not_found() {
                 e.change_context(UserErrors::InvalidRoleOperation)
                     .attach_printable(format!(
-                        "User role with user_id = {} and org_id = {} is not found",
+                        "User role with user_id = {} and merchant_id = {} is not found",
                         user.get_user_id(),
                         user_from_token.merchant_id
                     ))
@@ -1982,3 +1984,184 @@ pub async fn check_two_factor_auth_status(
         },
     ))
 }
+
+pub async fn create_user_authentication_method(
+    state: SessionState,
+    req: user_api::CreateUserAuthenticationMethodRequest,
+) -> UserResponse<()> {
+    let user_auth_encryption_key = hex::decode(
+        state
+            .conf
+            .user_auth_methods
+            .get_inner()
+            .encryption_key
+            .clone()
+            .expose(),
+    )
+    .change_context(UserErrors::InternalServerError)
+    .attach_printable("Failed to decode DEK")?;
+
+    let (private_config, public_config) = match req.auth_method {
+        user_api::AuthConfig::OpenIdConnect {
+            ref private_config,
+            ref public_config,
+        } => {
+            let private_config_value = serde_json::to_value(private_config.clone())
+                .change_context(UserErrors::AuthConfigParsingError)
+                .attach_printable("Failed to convert auth config to json")?;
+
+            let encrypted_config = domain::types::encrypt::<serde_json::Value, masking::WithType>(
+                private_config_value.into(),
+                &user_auth_encryption_key,
+            )
+            .await
+            .change_context(UserErrors::InternalServerError)
+            .attach_printable("Failed to encrypt auth config")?;
+
+            Ok::<_, error_stack::Report<UserErrors>>((
+                Some(encrypted_config.into()),
+                Some(
+                    serde_json::to_value(public_config.clone())
+                        .change_context(UserErrors::AuthConfigParsingError)
+                        .attach_printable("Failed to convert auth config to json")?,
+                ),
+            ))
+        }
+        _ => Ok((None, None)),
+    }?;
+
+    let auth_methods = state
+        .store
+        .list_user_authentication_methods_for_owner_id(&req.owner_id)
+        .await
+        .change_context(UserErrors::InternalServerError)
+        .attach_printable("Failed to get list of auth methods for the owner id")?;
+
+    let auth_id = auth_methods
+        .first()
+        .map(|auth_method| auth_method.auth_id.clone())
+        .unwrap_or(uuid::Uuid::new_v4().to_string());
+
+    let now = common_utils::date_time::now();
+    state
+        .store
+        .insert_user_authentication_method(UserAuthenticationMethodNew {
+            id: uuid::Uuid::new_v4().to_string(),
+            auth_id,
+            owner_id: req.owner_id,
+            owner_type: req.owner_type,
+            auth_type: req.auth_method.foreign_into(),
+            private_config,
+            public_config,
+            allow_signup: req.allow_signup,
+            created_at: now,
+            last_modified_at: now,
+        })
+        .await
+        .to_duplicate_response(UserErrors::UserAuthMethodAlreadyExists)?;
+
+    Ok(ApplicationResponse::StatusOk)
+}
+
+pub async fn update_user_authentication_method(
+    state: SessionState,
+    req: user_api::UpdateUserAuthenticationMethodRequest,
+) -> UserResponse<()> {
+    let user_auth_encryption_key = hex::decode(
+        state
+            .conf
+            .user_auth_methods
+            .get_inner()
+            .encryption_key
+            .clone()
+            .expose(),
+    )
+    .change_context(UserErrors::InternalServerError)
+    .attach_printable("Failed to decode DEK")?;
+
+    let (private_config, public_config) = match req.auth_method {
+        user_api::AuthConfig::OpenIdConnect {
+            ref private_config,
+            ref public_config,
+        } => {
+            let private_config_value = serde_json::to_value(private_config.clone())
+                .change_context(UserErrors::AuthConfigParsingError)
+                .attach_printable("Failed to convert auth config to json")?;
+
+            let encrypted_config = domain::types::encrypt::<serde_json::Value, masking::WithType>(
+                private_config_value.into(),
+                &user_auth_encryption_key,
+            )
+            .await
+            .change_context(UserErrors::InternalServerError)
+            .attach_printable("Failed to encrypt auth config")?;
+
+            Ok::<_, error_stack::Report<UserErrors>>((
+                Some(encrypted_config.into()),
+                Some(
+                    serde_json::to_value(public_config.clone())
+                        .change_context(UserErrors::AuthConfigParsingError)
+                        .attach_printable("Failed to convert auth config to json")?,
+                ),
+            ))
+        }
+        _ => Ok((None, None)),
+    }?;
+
+    state
+        .store
+        .update_user_authentication_method(
+            &req.id,
+            UserAuthenticationMethodUpdate::UpdateConfig {
+                private_config,
+                public_config,
+            },
+        )
+        .await
+        .change_context(UserErrors::InvalidUserAuthMethodOperation)?;
+    Ok(ApplicationResponse::StatusOk)
+}
+
+pub async fn list_user_authentication_methods(
+    state: SessionState,
+    req: user_api::GetUserAuthenticationMethodsRequest,
+) -> UserResponse<Vec<user_api::UserAuthenticationMethodResponse>> {
+    let user_authentication_methods = state
+        .store
+        .list_user_authentication_methods_for_auth_id(&req.auth_id)
+        .await
+        .change_context(UserErrors::InternalServerError)?;
+
+    Ok(ApplicationResponse::Json(
+        user_authentication_methods
+            .into_iter()
+            .map(|auth_method| {
+                let auth_name = match (auth_method.auth_type, auth_method.public_config) {
+                    (common_enums::UserAuthType::OpenIdConnect, Some(config)) => {
+                        let open_id_public_config: user_api::OpenIdConnectPublicConfig = config
+                            .parse_value("OpenIdConnectPublicConfig")
+                            .change_context(UserErrors::InternalServerError)
+                            .attach_printable("unable to parse generic data value")?;
+
+                        Ok(Some(open_id_public_config.name))
+                    }
+                    (common_enums::UserAuthType::OpenIdConnect, None) => {
+                        Err(UserErrors::InternalServerError)
+                            .attach_printable("No config found for open_id_connect auth_method")
+                    }
+                    _ => Ok(None),
+                }?;
+
+                Ok(user_api::UserAuthenticationMethodResponse {
+                    id: auth_method.id,
+                    auth_id: auth_method.auth_id,
+                    auth_method: user_api::AuthMethodDetails {
+                        name: auth_name,
+                        auth_type: auth_method.auth_type,
+                    },
+                    allow_signup: auth_method.allow_signup,
+                })
+            })
+            .collect::<UserResult<_>>()?,
+    ))
+}
diff --git a/crates/router/src/db.rs b/crates/router/src/db.rs
index b1a00c33dfb..2bf80827e8a 100644
--- a/crates/router/src/db.rs
+++ b/crates/router/src/db.rs
@@ -32,6 +32,7 @@ pub mod reverse_lookup;
 pub mod role;
 pub mod routing_algorithm;
 pub mod user;
+pub mod user_authentication_method;
 pub mod user_key_store;
 pub mod user_role;
 
@@ -117,6 +118,7 @@ pub trait StorageInterface:
     + user::sample_data::BatchSampleDataInterface
     + health_check::HealthCheckDbInterface
     + role::RoleInterface
+    + user_authentication_method::UserAuthenticationMethodInterface
     + authentication::AuthenticationInterface
     + 'static
 {
diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs
index ca36a53418c..2964fd78bce 100644
--- a/crates/router/src/db/kafka_store.rs
+++ b/crates/router/src/db/kafka_store.rs
@@ -33,6 +33,7 @@ use super::{
     dashboard_metadata::DashboardMetadataInterface,
     role::RoleInterface,
     user::{sample_data::BatchSampleDataInterface, UserInterface},
+    user_authentication_method::UserAuthenticationMethodInterface,
     user_key_store::UserKeyStoreInterface,
     user_role::UserRoleInterface,
 };
@@ -2874,3 +2875,43 @@ impl UserKeyStoreInterface for KafkaStore {
             .await
     }
 }
+
+#[async_trait::async_trait]
+impl UserAuthenticationMethodInterface for KafkaStore {
+    async fn insert_user_authentication_method(
+        &self,
+        user_authentication_method: storage::UserAuthenticationMethodNew,
+    ) -> CustomResult<storage::UserAuthenticationMethod, errors::StorageError> {
+        self.diesel_store
+            .insert_user_authentication_method(user_authentication_method)
+            .await
+    }
+
+    async fn list_user_authentication_methods_for_auth_id(
+        &self,
+        auth_id: &str,
+    ) -> CustomResult<Vec<storage::UserAuthenticationMethod>, errors::StorageError> {
+        self.diesel_store
+            .list_user_authentication_methods_for_auth_id(auth_id)
+            .await
+    }
+
+    async fn list_user_authentication_methods_for_owner_id(
+        &self,
+        owner_id: &str,
+    ) -> CustomResult<Vec<storage::UserAuthenticationMethod>, errors::StorageError> {
+        self.diesel_store
+            .list_user_authentication_methods_for_owner_id(owner_id)
+            .await
+    }
+
+    async fn update_user_authentication_method(
+        &self,
+        id: &str,
+        user_authentication_method_update: storage::UserAuthenticationMethodUpdate,
+    ) -> CustomResult<storage::UserAuthenticationMethod, errors::StorageError> {
+        self.diesel_store
+            .update_user_authentication_method(id, user_authentication_method_update)
+            .await
+    }
+}
diff --git a/crates/router/src/db/user_authentication_method.rs b/crates/router/src/db/user_authentication_method.rs
new file mode 100644
index 00000000000..5b9aa5da8c9
--- /dev/null
+++ b/crates/router/src/db/user_authentication_method.rs
@@ -0,0 +1,197 @@
+use diesel_models::user_authentication_method::{self as storage};
+use error_stack::report;
+use router_env::{instrument, tracing};
+
+use super::MockDb;
+use crate::{
+    connection,
+    core::errors::{self, CustomResult},
+    services::Store,
+};
+
+#[async_trait::async_trait]
+pub trait UserAuthenticationMethodInterface {
+    async fn insert_user_authentication_method(
+        &self,
+        user_authentication_method: storage::UserAuthenticationMethodNew,
+    ) -> CustomResult<storage::UserAuthenticationMethod, errors::StorageError>;
+
+    async fn list_user_authentication_methods_for_auth_id(
+        &self,
+        auth_id: &str,
+    ) -> CustomResult<Vec<storage::UserAuthenticationMethod>, errors::StorageError>;
+
+    async fn list_user_authentication_methods_for_owner_id(
+        &self,
+        owner_id: &str,
+    ) -> CustomResult<Vec<storage::UserAuthenticationMethod>, errors::StorageError>;
+
+    async fn update_user_authentication_method(
+        &self,
+        id: &str,
+        user_authentication_method_update: storage::UserAuthenticationMethodUpdate,
+    ) -> CustomResult<storage::UserAuthenticationMethod, errors::StorageError>;
+}
+
+#[async_trait::async_trait]
+impl UserAuthenticationMethodInterface for Store {
+    #[instrument(skip_all)]
+    async fn insert_user_authentication_method(
+        &self,
+        user_authentication_method: storage::UserAuthenticationMethodNew,
+    ) -> CustomResult<storage::UserAuthenticationMethod, errors::StorageError> {
+        let conn = connection::pg_connection_write(self).await?;
+        user_authentication_method
+            .insert(&conn)
+            .await
+            .map_err(|error| report!(errors::StorageError::from(error)))
+    }
+
+    #[instrument(skip_all)]
+    async fn list_user_authentication_methods_for_auth_id(
+        &self,
+        auth_id: &str,
+    ) -> CustomResult<Vec<storage::UserAuthenticationMethod>, errors::StorageError> {
+        let conn = connection::pg_connection_write(self).await?;
+        storage::UserAuthenticationMethod::list_user_authentication_methods_for_auth_id(
+            &conn, auth_id,
+        )
+        .await
+        .map_err(|error| report!(errors::StorageError::from(error)))
+    }
+
+    #[instrument(skip_all)]
+    async fn list_user_authentication_methods_for_owner_id(
+        &self,
+        owner_id: &str,
+    ) -> CustomResult<Vec<storage::UserAuthenticationMethod>, errors::StorageError> {
+        let conn = connection::pg_connection_write(self).await?;
+        storage::UserAuthenticationMethod::list_user_authentication_methods_for_owner_id(
+            &conn, owner_id,
+        )
+        .await
+        .map_err(|error| report!(errors::StorageError::from(error)))
+    }
+
+    #[instrument(skip_all)]
+    async fn update_user_authentication_method(
+        &self,
+        id: &str,
+        user_authentication_method_update: storage::UserAuthenticationMethodUpdate,
+    ) -> CustomResult<storage::UserAuthenticationMethod, errors::StorageError> {
+        let conn = connection::pg_connection_write(self).await?;
+        storage::UserAuthenticationMethod::update_user_authentication_method(
+            &conn,
+            id,
+            user_authentication_method_update,
+        )
+        .await
+        .map_err(|error| report!(errors::StorageError::from(error)))
+    }
+}
+
+#[async_trait::async_trait]
+impl UserAuthenticationMethodInterface for MockDb {
+    async fn insert_user_authentication_method(
+        &self,
+        user_authentication_method: storage::UserAuthenticationMethodNew,
+    ) -> CustomResult<storage::UserAuthenticationMethod, errors::StorageError> {
+        let mut user_authentication_methods = self.user_authentication_methods.lock().await;
+        let existing_auth_id = user_authentication_methods
+            .iter()
+            .find(|uam| uam.owner_id == user_authentication_method.owner_id)
+            .map(|uam| uam.auth_id.clone());
+
+        let auth_id = existing_auth_id.unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
+        let user_authentication_method = storage::UserAuthenticationMethod {
+            id: uuid::Uuid::new_v4().to_string(),
+            auth_id,
+            owner_id: user_authentication_method.auth_id,
+            owner_type: user_authentication_method.owner_type,
+            auth_type: user_authentication_method.auth_type,
+            public_config: user_authentication_method.public_config,
+            private_config: user_authentication_method.private_config,
+            allow_signup: user_authentication_method.allow_signup,
+            created_at: user_authentication_method.created_at,
+            last_modified_at: user_authentication_method.last_modified_at,
+        };
+
+        user_authentication_methods.push(user_authentication_method.clone());
+        Ok(user_authentication_method)
+    }
+
+    async fn list_user_authentication_methods_for_auth_id(
+        &self,
+        auth_id: &str,
+    ) -> CustomResult<Vec<storage::UserAuthenticationMethod>, errors::StorageError> {
+        let user_authentication_methods = self.user_authentication_methods.lock().await;
+
+        let user_authentication_methods_list: Vec<_> = user_authentication_methods
+            .iter()
+            .filter(|auth_method_inner| auth_method_inner.auth_id == auth_id)
+            .cloned()
+            .collect();
+        if user_authentication_methods_list.is_empty() {
+            return Err(errors::StorageError::ValueNotFound(format!(
+                "No user authentication method found for auth_id = {}",
+                auth_id
+            ))
+            .into());
+        }
+
+        Ok(user_authentication_methods_list)
+    }
+
+    async fn list_user_authentication_methods_for_owner_id(
+        &self,
+        owner_id: &str,
+    ) -> CustomResult<Vec<storage::UserAuthenticationMethod>, errors::StorageError> {
+        let user_authentication_methods = self.user_authentication_methods.lock().await;
+
+        let user_authentication_methods_list: Vec<_> = user_authentication_methods
+            .iter()
+            .filter(|auth_method_inner| auth_method_inner.owner_id == owner_id)
+            .cloned()
+            .collect();
+        if user_authentication_methods_list.is_empty() {
+            return Err(errors::StorageError::ValueNotFound(format!(
+                "No user authentication method found for owner_id = {}",
+                owner_id
+            ))
+            .into());
+        }
+
+        Ok(user_authentication_methods_list)
+    }
+
+    async fn update_user_authentication_method(
+        &self,
+        id: &str,
+        user_authentication_method_update: storage::UserAuthenticationMethodUpdate,
+    ) -> CustomResult<storage::UserAuthenticationMethod, errors::StorageError> {
+        let mut user_authentication_methods = self.user_authentication_methods.lock().await;
+        user_authentication_methods
+            .iter_mut()
+            .find(|auth_method_inner| auth_method_inner.id == id)
+            .map(|auth_method_inner| {
+                *auth_method_inner = match user_authentication_method_update {
+                    storage::UserAuthenticationMethodUpdate::UpdateConfig {
+                        private_config,
+                        public_config,
+                    } => storage::UserAuthenticationMethod {
+                        private_config,
+                        public_config,
+                        last_modified_at: common_utils::date_time::now(),
+                        ..auth_method_inner.to_owned()
+                    },
+                };
+                auth_method_inner.to_owned()
+            })
+            .ok_or(
+                errors::StorageError::ValueNotFound(format!(
+                    "No authentication method available for the id = {id}"
+                ))
+                .into(),
+            )
+    }
+}
diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs
index 2e7a6c4f61e..d4aebc3adc4 100644
--- a/crates/router/src/routes/app.rs
+++ b/crates/router/src/routes/app.rs
@@ -1377,6 +1377,18 @@ impl User {
                 ),
         );
 
+        route = route.service(
+            web::scope("/auth")
+                .service(
+                    web::resource("")
+                        .route(web::post().to(create_user_authentication_method))
+                        .route(web::put().to(update_user_authentication_method)),
+                )
+                .service(
+                    web::resource("/list").route(web::get().to(list_user_authentication_methods)),
+                ),
+        );
+
         #[cfg(feature = "email")]
         {
             route = route
diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs
index a64343757b5..72017411783 100644
--- a/crates/router/src/routes/lock_utils.rs
+++ b/crates/router/src/routes/lock_utils.rs
@@ -223,7 +223,10 @@ impl From<Flow> for ApiIdentifier {
             | Flow::RecoveryCodeVerify
             | Flow::RecoveryCodesGenerate
             | Flow::TerminateTwoFactorAuth
-            | Flow::TwoFactorAuthStatus => Self::User,
+            | Flow::TwoFactorAuthStatus
+            | Flow::CreateUserAuthenticationMethod
+            | Flow::UpdateUserAuthenticationMethod
+            | Flow::ListUserAuthenticationMethods => Self::User,
 
             Flow::ListRoles
             | Flow::GetRole
diff --git a/crates/router/src/routes/user.rs b/crates/router/src/routes/user.rs
index 5325cbe437b..f297e41e4e0 100644
--- a/crates/router/src/routes/user.rs
+++ b/crates/router/src/routes/user.rs
@@ -749,3 +749,60 @@ pub async fn check_two_factor_auth_status(
     ))
     .await
 }
+
+pub async fn create_user_authentication_method(
+    state: web::Data<AppState>,
+    req: HttpRequest,
+    json_payload: web::Json<user_api::CreateUserAuthenticationMethodRequest>,
+) -> HttpResponse {
+    let flow = Flow::CreateUserAuthenticationMethod;
+
+    Box::pin(api::server_wrap(
+        flow,
+        state.clone(),
+        &req,
+        json_payload.into_inner(),
+        |state, _, req_body, _| user_core::create_user_authentication_method(state, req_body),
+        &auth::AdminApiAuth,
+        api_locking::LockAction::NotApplicable,
+    ))
+    .await
+}
+
+pub async fn update_user_authentication_method(
+    state: web::Data<AppState>,
+    req: HttpRequest,
+    json_payload: web::Json<user_api::UpdateUserAuthenticationMethodRequest>,
+) -> HttpResponse {
+    let flow = Flow::UpdateUserAuthenticationMethod;
+
+    Box::pin(api::server_wrap(
+        flow,
+        state.clone(),
+        &req,
+        json_payload.into_inner(),
+        |state, _, req_body, _| user_core::update_user_authentication_method(state, req_body),
+        &auth::AdminApiAuth,
+        api_locking::LockAction::NotApplicable,
+    ))
+    .await
+}
+
+pub async fn list_user_authentication_methods(
+    state: web::Data<AppState>,
+    req: HttpRequest,
+    query: web::Query<user_api::GetUserAuthenticationMethodsRequest>,
+) -> HttpResponse {
+    let flow = Flow::ListUserAuthenticationMethods;
+
+    Box::pin(api::server_wrap(
+        flow,
+        state.clone(),
+        &req,
+        query.into_inner(),
+        |state, _, req, _| user_core::list_user_authentication_methods(state, req),
+        &auth::NoAuth,
+        api_locking::LockAction::NotApplicable,
+    ))
+    .await
+}
diff --git a/crates/router/src/types/storage.rs b/crates/router/src/types/storage.rs
index f5626c267d2..5a358c272b1 100644
--- a/crates/router/src/types/storage.rs
+++ b/crates/router/src/types/storage.rs
@@ -35,6 +35,7 @@ pub mod reverse_lookup;
 pub mod role;
 pub mod routing_algorithm;
 pub mod user;
+pub mod user_authentication_method;
 pub mod user_role;
 
 use std::collections::HashMap;
@@ -62,7 +63,7 @@ pub use self::{
     file::*, fraud_check::*, gsm::*, locker_mock_up::*, mandate::*, merchant_account::*,
     merchant_connector_account::*, merchant_key_store::*, payment_link::*, payment_method::*,
     process_tracker::*, refund::*, reverse_lookup::*, role::*, routing_algorithm::*, user::*,
-    user_role::*,
+    user_authentication_method::*, user_role::*,
 };
 use crate::types::api::routing;
 
diff --git a/crates/router/src/types/storage/user_authentication_method.rs b/crates/router/src/types/storage/user_authentication_method.rs
new file mode 100644
index 00000000000..87856f38052
--- /dev/null
+++ b/crates/router/src/types/storage/user_authentication_method.rs
@@ -0,0 +1 @@
+pub use diesel_models::user_authentication_method::*;
diff --git a/crates/router/src/utils/user.rs b/crates/router/src/utils/user.rs
index 3792da49e84..25c851549d1 100644
--- a/crates/router/src/utils/user.rs
+++ b/crates/router/src/utils/user.rs
@@ -13,7 +13,10 @@ use crate::{
         authentication::{AuthToken, UserFromToken},
         authorization::roles::RoleInfo,
     },
-    types::domain::{self, MerchantAccount, UserFromStorage},
+    types::{
+        domain::{self, MerchantAccount, UserFromStorage},
+        transformers::ForeignFrom,
+    },
 };
 
 pub mod dashboard_metadata;
@@ -200,3 +203,13 @@ pub fn get_redis_connection(state: &SessionState) -> UserResult<Arc<RedisConnect
         .change_context(UserErrors::InternalServerError)
         .attach_printable("Failed to get redis connection")
 }
+
+impl ForeignFrom<user_api::AuthConfig> for common_enums::UserAuthType {
+    fn foreign_from(from: user_api::AuthConfig) -> Self {
+        match from {
+            user_api::AuthConfig::OpenIdConnect { .. } => Self::OpenIdConnect,
+            user_api::AuthConfig::Password => Self::Password,
+            user_api::AuthConfig::MagicLink => Self::MagicLink,
+        }
+    }
+}
diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs
index 51f762e3713..3a7cb3ba2a0 100644
--- a/crates/router_env/src/logger/types.rs
+++ b/crates/router_env/src/logger/types.rs
@@ -420,6 +420,12 @@ pub enum Flow {
     TerminateTwoFactorAuth,
     // Check 2FA status
     TwoFactorAuthStatus,
+    // Create user authentication method
+    CreateUserAuthenticationMethod,
+    // Update user authentication method
+    UpdateUserAuthenticationMethod,
+    // List user authentication methods
+    ListUserAuthenticationMethods,
     /// List initial webhook delivery attempts
     WebhookEventInitialDeliveryAttemptList,
     /// List delivery attempts for a webhook event
diff --git a/crates/storage_impl/src/mock_db.rs b/crates/storage_impl/src/mock_db.rs
index 0ada6513ff0..3434bcb67dd 100644
--- a/crates/storage_impl/src/mock_db.rs
+++ b/crates/storage_impl/src/mock_db.rs
@@ -58,6 +58,8 @@ pub struct MockDb {
     pub authentications: Arc<Mutex<Vec<store::authentication::Authentication>>>,
     pub roles: Arc<Mutex<Vec<store::role::Role>>>,
     pub user_key_store: Arc<Mutex<Vec<store::user_key_store::UserKeyStore>>>,
+    pub user_authentication_methods:
+        Arc<Mutex<Vec<store::user_authentication_method::UserAuthenticationMethod>>>,
 }
 
 impl MockDb {
@@ -102,6 +104,7 @@ impl MockDb {
             authentications: Default::default(),
             roles: Default::default(),
             user_key_store: Default::default(),
+            user_authentication_methods: Default::default(),
         })
     }
 }
diff --git a/migrations/2024-06-10-084722_create_user_authentication_methods_table/down.sql b/migrations/2024-06-10-084722_create_user_authentication_methods_table/down.sql
new file mode 100644
index 00000000000..e12da6d51ab
--- /dev/null
+++ b/migrations/2024-06-10-084722_create_user_authentication_methods_table/down.sql
@@ -0,0 +1,4 @@
+-- This file should undo anything in `up.sql`
+DROP INDEX IF EXISTS auth_id_index;
+DROP INDEX IF EXISTS owner_id_index;
+DROP TABLE IF EXISTS user_authentication_methods;
diff --git a/migrations/2024-06-10-084722_create_user_authentication_methods_table/up.sql b/migrations/2024-06-10-084722_create_user_authentication_methods_table/up.sql
new file mode 100644
index 00000000000..4bdb835eeb1
--- /dev/null
+++ b/migrations/2024-06-10-084722_create_user_authentication_methods_table/up.sql
@@ -0,0 +1,16 @@
+-- Your SQL goes here
+CREATE TABLE IF NOT EXISTS user_authentication_methods (
+    id VARCHAR(64) PRIMARY KEY,
+    auth_id VARCHAR(64) NOT NULL,
+    owner_id VARCHAR(64) NOT NULL,
+    owner_type VARCHAR(64) NOT NULL,
+    auth_type VARCHAR(64) NOT NULL,
+    private_config bytea,
+    public_config JSONB,
+    allow_signup BOOLEAN NOT NULL,
+    created_at TIMESTAMP NOT NULL DEFAULT now(),
+    last_modified_at TIMESTAMP NOT NULL DEFAULT now()
+);
+
+CREATE INDEX IF NOT EXISTS auth_id_index ON user_authentication_methods (auth_id);
+CREATE INDEX IF NOT EXISTS owner_id_index ON user_authentication_methods (owner_id);

## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [ ] Bugfix
- [x] New feature
- [ ] Enhancement
- [ ] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
The PR:
- Sets up schema for user authentication methods
- adds queries to interact with schema
- adds endpoints to create, update and list authentication methods

### Additional Changes

- [x] This PR modifies the API contract
- [x] This PR modifies the database schema
- [x] This PR modifies application configuration/environment variables

<!--
Provide links to the files with corresponding changes.

Following are the paths where you can find config files:
1. `config`
2. `crates/router/src/configs`
3. `loadtest/config`
-->
1. `config/config.example.toml`
2. `config/deployments/env_specific.toml`
3. `config/development.toml`
4. `config/docker_compose.toml`

## Motivation and Context
Closes [#4998](https://github.com/juspay/hyperswitch/issues/4998)

## How did you test it?
To create authentication method, ( auth is admin_api_key only):
```
curl --location 'http://localhost:8080/user/auth' \
--header 'Content-Type: application/json' \
--header 'api-key: test_admin' \
--header 'Cookie: Cookie_1=value' \
--data '
{
"owner_id": "pw",
    "owner_type": "tenant",
    "auth_method": {
        "auth_type": "open_id_connect",
        "private_config": {
            "base_url": "hello.com",
            "client_id": "something",
            "client_secret": "something",
            "private_key": "something"
        },
        "public_config": {
            "name": "okta"
        }
    },
    "allow_signup": true
    }'
```
Response: 200 OK if auth method got created successfully.

To update
```
curl --location --request PUT 'http://localhost:8080/user/auth' \
--header 'Content-Type: application/json' \
--header 'api-key: test_admin' \
--header 'Cookie: Cookie_1=value' \
--data '{
    "id": "65ff02eb-f3dc-4afc-b8ac-cef2a30c4d12",
    "auth_method": {
        "auth_type": "open_id_connect",
        "private_config": {
            "base_url": "hello.com",
            "client_id": "something",
            "client_secret": "something_update",
            "private_key": "something_update"
        },
        "public_config": {
            "name": "okta"
        }
    }
}
'
```
Response will be 200 OK for success full auth config update

To get list of authentication methods
```
curl --location 'http://localhost:8080/user/auth/list?auth_id=cbd4e678-6c09-4211-8611-74700aa1f695' \
--header 'Cookie: Cookie_1=value' \
--data ''
```
Response
```
[
    {
        "id": "6b543648-4d47-498d-bcc2-68132dbf8521",
        "auth_id": "cbd4e678-6c09-4211-8611-74700aa1f695",
        "auth_method": {
            "type": "open_id_connect",
            "name": "okta"
        },
        "allow_signup": true
    },
    {
        "id": "1364c40d-1c5a-4dc9-ae28-ce908f537e83",
        "auth_id": "cbd4e678-6c09-4211-8611-74700aa1f695",
        "auth_method": {
            "type": "password",
            "name": null
        },
        "allow_signup": true
    }
]
```

## Checklist
<!-- Put an `x` in the boxes that apply -->

- [x] I formatted the code `cargo +nightly fmt --all`
- [x] I addressed lints thrown by `cargo clippy`
- [x] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
