Skip to content

Social Authentication, when user already exists #154

@pelmenept

Description

@pelmenept

Hi! First thank you very much for the library, pretty much the only authentication for phoenix.

I am experiencing some difficulties with authentication flow using social providers when user already exists. Let me know if I am wrong, but pretty much steps to reproduce:

I have below extensions installed:

  1. PowInvitation
  2. PowEmailconfirmation
  3. PowAssent

In my app, users can invite other users. This flow seems to be working correctly. Once user invites another one, they receive email. Here is where it breaks for me:

User has 2 ways of continuing after confirming email:

  1. Enter password, and log in -> works perfectly fine
  2. Click on "sign in with google", get couple redirects, and get logged in -> works perfectly fine.

As long as users use the same method to login, the way they logged in first, there are no problems.

  1. If user confirmed email using google authentication, they are still able to "reset password" and then login. This user now can login using both google or password login methods.
  2. Hovewer user that has confirmed email and set up password, is not able to login using google authentication anymore. The error that I am getting with this approach "failed to insert into users table due to constraint". Which seems pow_assent is trying to insert user, and then insert user_identity, which does not work, since user already exists.

After some digging, the flow for authentication or upsert is done in PowAssent.Plug:

defp maybe_authenticate(
         %{
           private: %{
             pow_assent_callback_state: {:ok, :strategy},
             pow_assent_callback_params: params
           }
         } = conn
       ) do
    user_identity_params = Map.fetch!(params, :user_identity)

    case Pow.Plug.current_user(conn) do
      nil ->
        conn
        |> authenticate(user_identity_params)
        |> case do
          {:ok, conn} -> conn
          {:error, conn} -> conn
        end

      _user ->
        conn
    end
  end

Up until maybe_authenticate call everything works correctly. But "authenticate" call only tries to pull user from user_identities and not from users. Thus user is not found and nil is returned. Since authentication failed, assent tries to insert user, but failes, since user exists in "users" table but not in "user_identities" table.

authenticate code:

  @spec authenticate(Conn.t(), map()) :: {:ok, Conn.t()} | {:error, Conn.t()}
  def authenticate(conn, %{"provider" => provider, "uid" => uid}) do
    config = fetch_config(conn)

    provider
    |> Operations.get_user_by_provider_uid(uid, config)
    |> case do
      nil -> {:error, conn}
      user -> {:ok, Plug.create(conn, user)}
    end
  end

Thank you very much, please let me know if I am wrong somewhere.

Metadata

Metadata

Assignees

No one assigned

    Labels

    duplicateThis issue or pull request already exists

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions