ash_authentication icon indicating copy to clipboard operation
ash_authentication copied to clipboard

Google authentication uid required Error

Open drikanius opened this issue 1 year ago • 3 comments

Hello.

I'm trying to implement Google authentication, but I'm getting this error:

%Ash.Error.Invalid{
   errors: [
     %Ash.Error.Changes.Required{
       field: :uid,
       type: :attribute,
       resource: Core.Accounts.UserIdentity,
       changeset: nil,
       query: nil,
       error_context: [],
       vars: [],
       path: [],
       stacktrace: #Stacktrace<>,
       class: :invalid
     }
   ],
...

I tried two different approaches, directly using google DSL and the other with oauth2 DSL. Both result in the same error.

strategy (google DSL):

    strategies do
      password :password do
        identity_field :email
        sign_in_tokens_enabled? true

        resettable do
          sender Senders.SendPasswordResetEmail
        end
      end

      google :google do
        client_id "12..."
        redirect_uri "http://localhost:4000/"
        client_secret "ab..."
        base_url "https://www.googleapis.com"

        identity_resource Core.Accounts.UserIdentity
      end
    end

action register_with_google:

    create :register_with_google do
      alias Actions.RegisterWithGoogle.Changes

      argument :user_info, :map, allow_nil?: false
      argument :oauth_tokens, :map, allow_nil?: false
      upsert? true
      upsert_identity :unique_email

      change AshAuthentication.GenerateTokenChange
      change AshAuthentication.Strategy.OAuth2.IdentityChange

      change Changes.FillUserInfo
    end    

FillUserInfo:

defmodule Core.Accounts.User.Actions.RegisterWithGoogle.Changes.FillUserInfo do
  @moduledoc false

  use Ash.Resource.Change

  def change(changeset, _opts, _context), do: do_change(changeset)

  defp do_change(changeset) do
    user_info = Ash.Changeset.get_argument(changeset, :user_info)
    normalized_name = user_info[:name] |> String.downcase()

    changeset
    |> Ash.Changeset.change_attribute(:email, user_info[:email])
    |> Ash.Changeset.change_attribute(:first_name, user_info[:given_name])
    |> Ash.Changeset.change_attribute(:surname, user_info[:family_name])
    |> Ash.Changeset.change_attribute(:normalized_full_name, normalized_name)
  end
end

My user identity:

defmodule Core.Accounts.UserIdentity do
  @moduledoc false

  use Ash.Resource,
    data_layer: AshPostgres.DataLayer,
    extensions: [AshAuthentication.UserIdentity]

  user_identity do
    api Core.Accounts
    user_resource Core.Accounts.User
  end

  postgres do
    table "user_identities"
    repo Core.Repo
  end
end

As I said, I also tried using oauth2 and what changes is that I don't have the default attributes that I had to enter.

oauth2 :google do
  client_id "12..."
  redirect_uri "http://localhost:4000/"
  client_secret "AB..."
  base_url "https://www.googleapis.com"
  authorize_url "https://accounts.google.com/o/oauth2/v2/auth"
  token_url "/oauth2/v4/token"
  user_url "/oauth2/v3/userinfo"

  identity_resource Core.Accounts.UserIdentity
end

I would be grateful for any suggestions on how to fix this problem.

Dependency                  Current  Latest   Status               
ash                         2.17.17  2.17.19  Update not possible  
ash_authentication          3.12.0   3.12.0   Up-to-date           
ash_authentication_phoenix  1.9.0    1.9.0    Up-to-date           
ash_geo                     0.2.0    0.2.0    Up-to-date           
ash_postgres                1.3.65   1.3.66   Update possible      
ash_query_builder           0.6.3    0.6.3    Up-to-date           
ash_rbac                    0.4.0    0.4.0    Up-to-date

drikanius avatar Jan 02 '24 21:01 drikanius

Just some more information about this error.

The issue is that the user_info that we send as an argument is generated by the library ElixirAuthGoogle

This means that when I run this code: {:ok, user_info} = ElixirAuthGoogle.get_user_profile(token.access_token)

It will generate a map with atom keys, not string keys.

Then, it will fail inside lib/ash_authentication/user_identity/upsert_identity_change.ex:37: AshAuthentication.UserIdentity.UpsertIdentityChange.change/3 since Map.take expects only string keys.

Would it be OK to change that line to:

    uid =
      user_info
      # uid is a convention
      # sub is supposedly from the spec
      # id is from what has been seen from Google
      |> Map.take(["uid", "sub", "id", :uid, :sub, :id])
      |> Map.values()
      |> Enum.reject(&is_nil/1)
      |> List.first()

This way it doesn't matter if the map is atom or string, it will work both ways.

sezaru avatar Jan 02 '24 22:01 sezaru

I think that should be fine. Can you open a PR with that?

zachdaniel avatar Jan 03 '24 19:01 zachdaniel

The fix is merged in master. @drikanius can you please try again when you get a chance?

Fix: https://github.com/team-alembic/ash_authentication/pull/556

yasoob avatar Jan 25 '24 20:01 yasoob