Trouble setting up Ash Authentication with Google

I’m newer to Elixir/Phoenix in general, but decided to give Ash a go because I really like the philosophy. However, I’m running into trouble setting up Google Oauth with Ash Authentication.

Actually, I set up magic link first using Igniter, but then realized in production, I can’t send emails yet, so I added password authentication. (This seemed to break magic link :frowning_face:)

Now I’m trying to add Google, as I want to build a prototype to read emails. After following instructions, the Sign in with Google button shows up, but clicking it just returns me back to /sign-in page.

There are no errors in the console… and this is where I’m a bit stuck with the Ash philosophy. If I’m declaring everything, how do I trace the code to find out what’s wrong? Am I just guessing which attributes are missing?

Here is my User resource:

defmodule Playground.Accounts.User do
  use Ash.Resource,
    otp_app: :playground,
    domain: Playground.Accounts,
    authorizers: [Ash.Policy.Authorizer],
    extensions: [AshAuthentication],
    data_layer: AshSqlite.DataLayer

  authentication do
    add_ons do
      log_out_everywhere do
        apply_on_password_change?(true)
      end

      confirmation :confirm_new_user do
        monitor_fields [:email]
        confirm_on_create? true
        confirm_on_update? false

        auto_confirm_actions [
          :register_with_google,
          :sign_in_with_magic_link,
          :reset_password_with_token
        ]

        sender Playground.Accounts.User.Senders.SendNewUserConfirmationEmail
      end
    end

    tokens do
      enabled? true
      token_resource Playground.Accounts.Token
      signing_secret Playground.Secrets
      store_all_tokens? true
      require_token_presence_for_authentication? true
    end

    strategies do
      magic_link do
        identity_field :email
        registration_enabled? true

        sender Playground.Accounts.User.Senders.SendMagicLinkEmail
      end

      password :password do
        identity_field :email

        resettable do
          sender Playground.Accounts.User.Senders.SendPasswordResetEmail
          # these configurations will be the default in a future release
          password_reset_action_name :reset_password_with_token
          request_password_reset_action_name :request_password_reset_token
        end
      end

      google do
        client_id Playground.Secrets
        redirect_uri Playground.Secrets
        client_secret Playground.Secrets
      end
    end
  end

  sqlite do
    table "users"
    repo Playground.Repo
  end

  actions do
    defaults [:read]

    read :get_by_subject do
      description "Get a user by the subject claim in a JWT"
      argument :subject, :string, allow_nil?: false
      get? true
      prepare AshAuthentication.Preparations.FilterBySubject
    end

    read :get_by_email do
      description "Looks up a user by their email"
      get? true

      argument :email, :ci_string do
        allow_nil? false
      end

      filter expr(email == ^arg(:email))
    end

    create :sign_in_with_magic_link do
      description "Sign in or register a user with magic link."

      argument :token, :string do
        description "The token from the magic link that was sent to the user"
        allow_nil? false
      end

      upsert? true
      upsert_identity :unique_email
      upsert_fields [:email, :confirmed_at]

      # Uses the information from the token to create or sign in the user
      change AshAuthentication.Strategy.MagicLink.SignInChange

      metadata :token, :string do
        allow_nil? false
      end
    end

    action :request_magic_link do
      argument :email, :ci_string do
        allow_nil? false
      end

      run AshAuthentication.Strategy.MagicLink.Request
    end

    update :change_password do
      # Use this action to allow users to change their password by providing
      # their current password and a new password.

      require_atomic? false
      accept []
      argument :current_password, :string, sensitive?: true, allow_nil?: false
      argument :password, :string, sensitive?: true, allow_nil?: false
      argument :password_confirmation, :string, sensitive?: true, allow_nil?: false

      validate confirm(:password, :password_confirmation)

      validate {AshAuthentication.Strategy.Password.PasswordValidation,
                strategy_name: :password, password_argument: :current_password}

      change {AshAuthentication.Strategy.Password.HashPasswordChange, strategy_name: :password}
    end

    read :sign_in_with_password do
      description "Attempt to sign in using a email and password."
      get? true

      argument :email, :ci_string do
        description "The email to use for retrieving the user."
        allow_nil? false
      end

      argument :password, :string do
        description "The password to check for the matching user."
        allow_nil? false
        sensitive? true
      end

      # validates the provided email and password and generates a token
      prepare AshAuthentication.Strategy.Password.SignInPreparation

      metadata :token, :string do
        description "A JWT that can be used to authenticate the user."
        allow_nil? false
      end
    end

    read :sign_in_with_token do
      # In the generated sign in components, we validate the
      # email and password directly in the LiveView
      # and generate a short-lived token that can be used to sign in over
      # a standard controller action, exchanging it for a standard token.
      # This action performs that exchange. If you do not use the generated
      # liveviews, you may remove this action, and set
      # `sign_in_tokens_enabled? false` in the password strategy.

      description "Attempt to sign in using a short-lived sign in token."
      get? true

      argument :token, :string do
        description "The short-lived sign in token."
        allow_nil? false
        sensitive? true
      end

      # validates the provided sign in token and generates a token
      prepare AshAuthentication.Strategy.Password.SignInWithTokenPreparation

      metadata :token, :string do
        description "A JWT that can be used to authenticate the user."
        allow_nil? false
      end
    end

    create :register_with_password do
      description "Register a new user with a email and password."

      argument :email, :ci_string do
        allow_nil? false
      end

      argument :password, :string do
        description "The proposed password for the user, in plain text."
        allow_nil? false
        constraints min_length: 8
        sensitive? true
      end

      argument :password_confirmation, :string do
        description "The proposed password for the user (again), in plain text."
        allow_nil? false
        sensitive? true
      end

      # Sets the email from the argument
      change set_attribute(:email, arg(:email))

      # Hashes the provided password
      change AshAuthentication.Strategy.Password.HashPasswordChange

      # Generates an authentication token for the user
      change AshAuthentication.GenerateTokenChange

      # validates that the password matches the confirmation
      validate AshAuthentication.Strategy.Password.PasswordConfirmationValidation

      metadata :token, :string do
        description "A JWT that can be used to authenticate the user."
        allow_nil? false
      end
    end

    action :request_password_reset_token do
      description "Send password reset instructions to a user if they exist."

      argument :email, :ci_string do
        allow_nil? false
      end

      # creates a reset token and invokes the relevant senders
      run {AshAuthentication.Strategy.Password.RequestPasswordReset, action: :get_by_email}
    end

    update :reset_password_with_token do
      argument :reset_token, :string do
        allow_nil? false
        sensitive? true
      end

      argument :password, :string do
        description "The proposed password for the user, in plain text."
        allow_nil? false
        constraints min_length: 8
        sensitive? true
      end

      argument :password_confirmation, :string do
        description "The proposed password for the user (again), in plain text."
        allow_nil? false
        sensitive? true
      end

      # validates the provided reset token
      validate AshAuthentication.Strategy.Password.ResetTokenValidation

      # validates that the password matches the confirmation
      validate AshAuthentication.Strategy.Password.PasswordConfirmationValidation

      # Hashes the provided password
      change AshAuthentication.Strategy.Password.HashPasswordChange

      # Generates an authentication token for the user
      change AshAuthentication.GenerateTokenChange
    end

    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 if you have the `identity_resource` configuration enabled.
      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)

      change after_action(fn _changeset, user, _context ->
               case user.confirmed_at do
                 nil -> {:error, "Unconfirmed user exists already"}
                 _ -> {:ok, user}
               end
             end)
    end
  end

  policies do
    bypass AshAuthentication.Checks.AshAuthenticationInteraction do
      authorize_if always()
    end

    policy always() do
      forbid_if always()
    end
  end

  attributes do
    uuid_primary_key :id

    attribute :email, :ci_string do
      allow_nil? false
      public? true
    end

    attribute :hashed_password, :string do
      allow_nil? true
      sensitive? true
    end
  end

  identities do
    identity :unique_email, [:email]
  end
end

Much appreciate any insights.

The first step would be looking at your Phoenix server logs - what’s happening when you click sign in? Is it redirecting somewhere and then redirecting back to your app?

The second part would probably be to turn on debugging for authentication failures in development - see AshAuthentication.Debug — ash_authentication v4.5.2 for details.

As to magic link being broken, we’d probably need some more information about how exactly it’s broken before we can start to diagnose how to fix it!

Ah, the debug would be very helpful! Thank you for that.

There’s a lot of documentation for Ash and it can be a bit overwhelming, but I’m getting there. :slight_smile:

Looks like the Magic Link issue is only when trying to log in to accounts that aren’t confirmed. Getting the following error:

[error] ** (Ash.Error.Unknown) 
Bread Crumbs:
  > Exception raised in: Playground.Accounts.User.sign_in_with_magic_link

Unknown Error

* ** (CaseClauseError) no case clause matching: {:ok, []}
  (ash_sqlite 0.2.3) lib/data_layer.ex:1213: AshSqlite.DataLayer.upsert/3
  (ash 3.4.64) lib/ash/actions/create/create.ex:377: anonymous fn/6 in Ash.Actions.Create.commit/3
  (ash 3.4.64) lib/ash/changeset/changeset.ex:4021: Ash.Changeset.run_around_actions/2
  (ash 3.4.64) lib/ash/changeset/changeset.ex:3699: anonymous fn/2 in Ash.Changeset.transaction_hooks/2
  (ash 3.4.64) lib/ash/changeset/changeset.ex:3609: Ash.Changeset.with_hooks/3
  (ash 3.4.64) lib/ash/actions/create/create.ex:260: Ash.Actions.Create.commit/3
  (ash 3.4.64) lib/ash/actions/create/create.ex:132: Ash.Actions.Create.do_run/4
  (ash 3.4.64) lib/ash/actions/create/create.ex:50: Ash.Actions.Create.run/4
  (ash_authentication 4.5.1) lib/ash_authentication/strategies/magic_link/actions.ex:62: AshAuthentication.Strategy.MagicLink.Actions.sign_in/3
  (ash_authentication 4.5.1) lib/ash_authentication/strategies/magic_link/plug.ex:44: AshAuthentication.Strategy.MagicLink.Plug.sign_in/2
  (ash_authentication 4.5.1) lib/ash_authentication/plug/dispatcher.ex:29: AshAuthentication.Plug.Dispatcher.call/2
  (phoenix 1.7.18) lib/phoenix/router/route.ex:42: Phoenix.Router.Route.call/2
  (phoenix 1.7.18) lib/phoenix/router.ex:484: Phoenix.Router.__call__/5
  (playground 0.1.0) lib/playground_web/endpoint.ex:1: PlaygroundWeb.Endpoint.plug_builder_call/2
  (playground 0.1.0) deps/plug/lib/plug/debugger.ex:136: PlaygroundWeb.Endpoint."call (overridable 3)"/2
  (playground 0.1.0) lib/playground_web/endpoint.ex:1: PlaygroundWeb.Endpoint.call/2
  (phoenix 1.7.18) lib/phoenix/endpoint/sync_code_reload_plug.ex:22: Phoenix.Endpoint.SyncCodeReloadPlug.do_call/4
  (bandit 1.6.6) lib/bandit/pipeline.ex:129: Bandit.Pipeline.call_plug!/2
  (bandit 1.6.6) lib/bandit/pipeline.ex:40: Bandit.Pipeline.run/4
  (bandit 1.6.6) lib/bandit/http1/handler.ex:12: Bandit.HTTP1.Handler.handle_data/3
    (ash_sqlite 0.2.3) lib/data_layer.ex:1213: AshSqlite.DataLayer.upsert/3
    (ash 3.4.64) lib/ash/actions/create/create.ex:377: anonymous fn/6 in Ash.Actions.Create.commit/3
    (ash 3.4.64) lib/ash/changeset/changeset.ex:4021: Ash.Changeset.run_around_actions/2
    (ash 3.4.64) lib/ash/changeset/changeset.ex:3699: anonymous fn/2 in Ash.Changeset.transaction_hooks/2
    (ash 3.4.64) lib/ash/changeset/changeset.ex:3609: Ash.Changeset.with_hooks/3
    (ash 3.4.64) lib/ash/actions/create/create.ex:260: Ash.Actions.Create.commit/3
    (ash 3.4.64) lib/ash/actions/create/create.ex:132: Ash.Actions.Create.do_run/4
    (ash 3.4.64) lib/ash/actions/create/create.ex:50: Ash.Actions.Create.run/4
    (ash_authentication 4.5.1) lib/ash_authentication/strategies/magic_link/actions.ex:62: AshAuthentication.Strategy.MagicLink.Actions.sign_in/3
    (ash_authentication 4.5.1) lib/ash_authentication/strategies/magic_link/plug.ex:44: AshAuthentication.Strategy.MagicLink.Plug.sign_in/2
    (ash_authentication 4.5.1) lib/ash_authentication/plug/dispatcher.ex:29: AshAuthentication.Plug.Dispatcher.call/2
    (phoenix 1.7.18) lib/phoenix/router/route.ex:42: Phoenix.Router.Route.call/2
    (phoenix 1.7.18) lib/phoenix/router.ex:484: Phoenix.Router.__call__/5
    (playground 0.1.0) lib/playground_web/endpoint.ex:1: PlaygroundWeb.Endpoint.plug_builder_call/2
    (playground 0.1.0) deps/plug/lib/plug/debugger.ex:136: PlaygroundWeb.Endpoint."call (overridable 3)"/2
    (playground 0.1.0) lib/playground_web/endpoint.ex:1: PlaygroundWeb.Endpoint.call/2
    (phoenix 1.7.18) lib/phoenix/endpoint/sync_code_reload_plug.ex:22: Phoenix.Endpoint.SyncCodeReloadPlug.do_call/4
    (bandit 1.6.6) lib/bandit/pipeline.ex:129: Bandit.Pipeline.call_plug!/2
    (bandit 1.6.6) lib/bandit/pipeline.ex:40: Bandit.Pipeline.run/4
    (bandit 1.6.6) lib/bandit/http1/handler.ex:12: Bandit.HTTP1.Handler.handle_data/3

I found the Confirmation Tutorial. I’m guessing this is the issue, although the error is a bit cryptic.

Will try one of the ways to handle magic link confirmation after lunch!

This definitely looks like a bug. Please open a bug showing that stack trace on ash_sqlite

Done!

Alright, going back to debugging Google Signin…

Turned on debugging for authentication failures, but no errors.
Looks like it just hits the /auth/user/google route and redirects back to the sign in page.

[info] GET /auth/user/google
[debug] Processing with AshAuthentication.Phoenix.StrategyRouter
  Parameters: %{}
  Pipelines: [:browser]
[info] Sent 302 in 286µs
[info] GET /sign-in
[debug] Processing with AshAuthentication.Phoenix.SignInLive.sign_in/2
  Parameters: %{}
  Pipelines: [:browser]
[info] Sent 200 in 5ms
[info] CONNECTED TO Phoenix.LiveView.Socket in 16µs
  Transport: :websocket
  Serializer: Phoenix.Socket.V2.JSONSerializer
  Parameters: %{"_csrf_token" => "QVwzNQcrHnQoBhkoGCFHcVYZcEhTCHMH6qzDkrX6aBNb5Q34oX4qf8Co", "_live_referer" => "undefined", "_mount_attempts" => "0", "_mounts" => "0", "_track_static" => %{"0" => "http://localhost:4000/assets/app.css", "1" => "http://localhost:4000/assets/app.js"}, "vsn" => "2.0.0"}
[debug] MOUNT AshAuthentication.Phoenix.SignInLive
  Parameters: %{}
  Session: %{"_csrf_token" => "w-IqlYFBIDWJ-ptE9AD9500h", "auth_routes_prefix" => "/auth", "context" => nil, "gettext_fn" => nil, "otp_app" => nil, "overrides" => [PlaygroundWeb.AuthOverrides, AshAuthentication.Phoenix.Overrides.Default], "path" => "/sign-in", "register_path" => "/register", "reset_path" => "/reset", "tenant" => nil}
[debug] Replied in 179µs
[debug] HANDLE PARAMS in AshAuthentication.Phoenix.SignInLive
  Parameters: %{}
[debug] Replied in 37µs

Not sure if this is how it’s supposed to show up, but the button is grey compared to the other signin buttons.

Alright, so that first issue you mentioned should be fixed, will be released next week.

Could I see how your router is set up? Are you getting into your AuthController at all? i.e in the failure callback?

You guys are amazing! Thank you.

My router.ex:

defmodule PlaygroundWeb.Router do
  use PlaygroundWeb, :router

  use AshAuthentication.Phoenix.Router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_live_flash
    plug :put_root_layout, html: {PlaygroundWeb.Layouts, :root}
    plug :protect_from_forgery
    plug :put_secure_browser_headers
    plug :load_from_session
  end

  pipeline :api do
    plug :accepts, ["json"]
    plug :load_from_bearer
  end

  scope "/", PlaygroundWeb do
    pipe_through :browser

    get "/", PageController, :home

    ash_authentication_live_session :authenticated_routes,
      on_mount: {PlaygroundWeb.LiveUserAuth, :live_user_required} do
      # in each liveview, add one of the following at the top of the module:
      #
      # If an authenticated user must be present:
      # on_mount {PlaygroundWeb.LiveUserAuth, :live_user_required}
      #
      # If an authenticated user *may* be present:
      # on_mount {PlaygroundWeb.LiveUserAuth, :live_user_optional}
      #
      # If an authenticated user must *not* be present:
      # on_mount {PlaygroundWeb.LiveUserAuth, :live_no_user}

      live "/tts", TTSLive
      live "/scraper", ScraperLive
      live "/domain", DynamicDomainLive

      live "/chat", ChatLive
      live "/voice", VoiceLive
    end
  end

  scope "/", PlaygroundWeb do
    pipe_through :browser

    auth_routes AuthController, Playground.Accounts.User, path: "/auth"
    sign_out_route AuthController

    # Remove these if you'd like to use your own authentication views
    sign_in_route register_path: "/register",
                  reset_path: "/reset",
                  auth_routes_prefix: "/auth",
                  on_mount: [{PlaygroundWeb.LiveUserAuth, :live_no_user}],
                  overrides: [
                    PlaygroundWeb.AuthOverrides,
                    AshAuthentication.Phoenix.Overrides.Default
                  ]

    # Remove this if you do not want to use the reset password feature
    reset_route auth_routes_prefix: "/auth",
                overrides: [
                  PlaygroundWeb.AuthOverrides,
                  AshAuthentication.Phoenix.Overrides.Default
                ]

    ash_authentication_live_session :user_account_routes,
      on_mount: {PlaygroundWeb.LiveUserAuth, :live_user_required} do
      live "/account", AccountLive
    end
  end

  # Other scopes may use custom stacks.
  # scope "/api", PlaygroundWeb do
  #   pipe_through :api
  # end

  # Enable LiveDashboard and Swoosh mailbox preview in development
  if Application.compile_env(:playground, :dev_routes) do
    # If you want to use the LiveDashboard in production, you should put
    # it behind authentication and allow only admins to access it.
    # If your application does not have an admins-only section yet,
    # you can use Plug.BasicAuth to set up some basic authentication
    # as long as you are also using SSL (which you should anyway).
    import Phoenix.LiveDashboard.Router

    scope "/dev" do
      pipe_through :browser

      live_dashboard "/dashboard", metrics: PlaygroundWeb.Telemetry
      forward "/mailbox", Plug.Swoosh.MailboxPreview
    end
  end
end

Here’s the log:

[info] GET /auth/user/google
[debug] Processing with AshAuthentication.Phoenix.StrategyRouter
  Parameters: %{}
  Pipelines: [:browser]
[info] Sent 302 in 315µs
[info] GET /sign-in
[debug] Processing with AshAuthentication.Phoenix.SignInLive.sign_in/2
  Parameters: %{}
  Pipelines: [:browser]
[info] Sent 200 in 5ms
[info] CONNECTED TO Phoenix.LiveView.Socket in 16µs
  Transport: :websocket
  Serializer: Phoenix.Socket.V2.JSONSerializer
  Parameters: %{"_csrf_token" => "dBYwBBd4P2tyCRMkPGZbBgQ-HQc0GyN77AoltME8KAalN-vKjgSTulGM", "_live_referer" => "undefined", "_mount_attempts" => "0", "_mounts" => "0", "_track_static" => %{"0" => "http://localhost:4000/assets/app.css", "1" => "http://localhost:4000/assets/app.js"}, "vsn" => "2.0.0"}
[debug] MOUNT AshAuthentication.Phoenix.SignInLive
  Parameters: %{}
  Session: %{"_csrf_token" => "CW_hc5zS9HrHrK-MnYNSAwd6", "auth_routes_prefix" => "/auth", "context" => nil, "gettext_fn" => nil, "otp_app" => nil, "overrides" => [PlaygroundWeb.AuthOverrides, AshAuthentication.Phoenix.Overrides.Default], "path" => "/sign-in", "register_path" => "/register", "reset_path" => "/reset", "tenant" => nil}
[debug] Replied in 226µs
[debug] HANDLE PARAMS in AshAuthentication.Phoenix.SignInLive
  Parameters: %{}
[debug] Replied in 45µs

I’m… not sure if it goes into AuthController. It looks like it hits the /auth/user/google route and just redirects.

Here’s my mix phx.routes:

  GET   /                                      PlaygroundWeb.PageController :home
  GET   /tts                                   PlaygroundWeb.TTSLive nil
  GET   /scraper                               PlaygroundWeb.ScraperLive nil
  GET   /domain                                PlaygroundWeb.DynamicDomainLive nil
  GET   /chat                                  PlaygroundWeb.ChatLive nil
  GET   /voice                                 PlaygroundWeb.VoiceLive nil
  *     /auth                                  AshAuthentication.Phoenix.StrategyRouter [path: "/auth", as: :auth, controller: PlaygroundWeb.AuthController, not_found_plug: nil, resources: [Playground.Accounts.User]]
  GET   /sign-out                              PlaygroundWeb.AuthController :sign_out
  GET   /sign-in                               AshAuthentication.Phoenix.SignInLive :sign_in
  GET   /reset                                 AshAuthentication.Phoenix.SignInLive :reset
  GET   /register                              AshAuthentication.Phoenix.SignInLive :register
  GET   /password-reset/:token                 AshAuthentication.Phoenix.ResetLive :reset
  GET   /account                               PlaygroundWeb.AccountLive nil
  GET   /dev/dashboard/css-:md5                Phoenix.LiveDashboard.Assets :css
  GET   /dev/dashboard/js-:md5                 Phoenix.LiveDashboard.Assets :js
  GET   /dev/dashboard                         Phoenix.LiveDashboard.PageLive :home
  GET   /dev/dashboard/:page                   Phoenix.LiveDashboard.PageLive :page
  GET   /dev/dashboard/:node/:page             Phoenix.LiveDashboard.PageLive :page
  *     /dev/mailbox                           Plug.Swoosh.MailboxPreview []
  WS    /live/websocket                        Phoenix.LiveView.Socket
  GET   /live/longpoll                         Phoenix.LiveView.Socket
  POST  /live/longpoll                         Phoenix.LiveView.Socket

  GET  /auth/user/confirm_new_user            AshAuthentication.AddOn.Confirmation
  GET  /auth/user/google                      AshAuthentication.Strategy.OAuth2
  POST  /auth/user/google/callback             AshAuthentication.Strategy.OAuth2
  GET  /auth/user/password/sign_in_with_token  AshAuthentication.Strategy.Password
  POST  /auth/user/password/register           AshAuthentication.Strategy.Password
  POST  /auth/user/password/sign_in            AshAuthentication.Strategy.Password
  POST  /auth/user/password/reset_request      AshAuthentication.Strategy.Password
  POST  /auth/user/password/reset              AshAuthentication.Strategy.Password
  POST  /auth/user/magic_link/request          AshAuthentication.Strategy.MagicLink
  GET  /auth/user/magic_link                  AshAuthentication.Strategy.MagicLink

The first thing to do would be to drop some dbg or IO.inspect calls in the AuthController success and failure callbacks to see if its getting there, and if its getting into the failure callback to see what the error is :smiley:

Will do. Thanks for being patient with a new Elixir developer. :slight_smile:

Of course! Welcome to Elixir :heart: We’ll get you sorted :people_hugging:

Well, after struggling a bit, looks like Claude figured it out for me. :laughing:

I added IO.inspect statements to AuthController failure callback and it said the secrets weren’t set.

Turns out I was using Application.fetch_env! with a ! and it wasn’t returning the secret in a {:ok, <secret>} format.

1 Like