Google Tutorial

View Source

This is a quick tutorial on how to configure Google authentication.

First you'll need a registered application in Google Cloud, in order to get your OAuth 2.0 Client credentials.

  1. On the Cloud's console Quick access section select APIs & Services, then Credentials
  2. Click on + CREATE CREDENTIALS and from the dropdown select OAuth client ID
  3. From the google developers console, we will need: client_id & client_secret
  4. Enter your callback uri under Authorized redirect URIs. E.g. http://localhost:4000/auth/user/google/callback.

Next we configure our resource to use google credentials:

defmodule MyApp.Accounts.User do
  use Ash.Resource,
    extensions: [AshAuthentication],
    domain: MyApp.Accounts

  attributes do
    ...
  end

  authentication do
    strategies do
      google do
        client_id MyApp.Secrets
        redirect_uri MyApp.Secrets
        client_secret MyApp.Secrets
        identity_resource MyApp.Accounts.UserIdentity
      end
    end
  end
end

Please check the guide on how to properly configure your Secrets.

The user identity resource

OAuth2-based strategies require an identity_resource - a resource that stores the provider's iss (issuer) and sub (subject) claims for each linked account. Matching a returning user by their email address (or any other claim) is not safe: per the OpenID Connect specification only the iss/sub combination uniquely and stably identifies an end-user. The identity resource is where those values live.

Returning users are matched by their iss/sub. Because Google reliably verifies email ownership, trust_email_verified? defaults to true for this strategy, so a new Google identity whose verified email matches an existing local account is linked to it automatically.

Add a UserIdentity resource using the AshAuthentication.UserIdentity extension. There is no need to define any attributes - the extension generates them for you.

defmodule MyApp.Accounts.UserIdentity do
  use Ash.Resource,
    data_layer: AshPostgres.DataLayer,
    extensions: [AshAuthentication.UserIdentity],
    domain: MyApp.Accounts

  user_identity do
    user_resource MyApp.Accounts.User
  end

  # Configure your data layer as appropriate for your application.
  postgres do
    table "user_identities"
    repo MyApp.Repo
  end
end

Don't forget to add it to your domain, and to generate and run migrations for the new resource.

mix ash.codegen add_user_identities
mix ash.migrate

Then we need to define an action that will handle the oauth2 flow, for the google case it is :register_with_google it will handle both cases for our resource, user registration & login.

defmodule MyApp.Accounts.User do
  require Ash.Resource.Change.Builtins
  use Ash.Resource,
    extensions: [AshAuthentication],
    domain: MyApp.Accounts

  # ...
  actions do
    create :register_with_google do
      argument :user_info, :map, allow_nil?: false
      argument :oauth_tokens, :map, allow_nil?: false
      upsert? true
      upsert_identity :unique_email

      change AshAuthentication.GenerateTokenChange

      # Required: persists the provider's `iss`/`sub` identity claims.
      change AshAuthentication.Strategy.OAuth2.IdentityChange

      change fn changeset, _ ->
        user_info = Ash.Changeset.get_argument(changeset, :user_info)

        Ash.Changeset.change_attributes(changeset, Map.take(user_info, ["email"]))
      end

      # Required if you're using the password & confirmation strategies
      upsert_fields []
      change set_attribute(:confirmed_at, &DateTime.utc_now/0)
    end
  end

  # ...

end

Ensure you set the hashed_password to allow_nil? if you are also using the password strategy.

defmodule MyApp.Accounts.User do
  # ...
  attributes do
    # ...
    attribute :hashed_password, :string, allow_nil?: true, sensitive?: true
  end
  # ...
end

And generate and run migrations in that case.

mix ash.codegen make_hashed_password_nullable
mix ash.migrate