pow-auth / pow_assent

Multi-provider authentication for your Pow enabled app
https://powauth.com
MIT License
321 stars 50 forks source link

Google redirects with get #157

Closed sveredyuk closed 4 years ago

sveredyuk commented 4 years ago

Hi, I faced a very strange problem. Seems like google redirect me back with GET instead of POST. Ofc it works when I change post to get in the router. But why?

[debug] ** (Phoenix.Router.NoRouteError) no route found for GET /api/oauth/google/callback (DemoWeb.Router)

My router.ex

defmodule DemoWeb.Router do
  use DemoWeb, :router
  use Plug.ErrorHandler
  use Sentry.Plug

  pipeline :api do
    plug(:accepts, ["json"])
    plug(DemoWeb.APIAuthPlug, otp_app: :demo)
  end

  pipeline :api_protected do
    plug Pow.Plug.RequireAuthenticated, error_handler: DemoWeb.APIAuthErrorHandler
  end

  scope "/api", DemoWeb.API, as: :api do
    pipe_through :api

    resources "/registration", RegistrationController, singleton: true, only: [:create]
    resources "/session", SessionController, singleton: true, only: [:create, :delete]
    post "/session/renew", SessionController, :renew

    get "/oauth/:provider/new", AuthorizationController, :new
    post "/oauth/:provider/callback", AuthorizationController, :callback
  end

  scope "/api" do
    pipe_through [:api, :api_protected]

    forward("/graphql", Absinthe.Plug, schema: DemoWeb.Schema)
  end

  if Mix.env() == :dev do
    forward("/sent_emails", Bamboo.SentEmailViewerPlug)
  end
end

My controller:

defmodule DemoWeb.API.AuthorizationController do
  use DemoWeb, :controller

  alias Plug.Conn
  alias PowAssent.Plug

  @spec new(Conn.t(), map()) :: Conn.t()
  def new(conn, %{"provider" => provider}) do
    conn
    |> Plug.authorize_url(provider, redirect_uri(conn))
    |> case do
      {:ok, url, conn} ->
        json(conn, %{data: %{url: url, session_params: conn.private[:pow_assent_session_params]}})

      {:error, _error} ->
        conn
        |> put_status(500)
        |> json(%{error: %{status: 500, message: "An unexpected error occurred"}})
    end
  end

  defp redirect_uri(conn) do
    "http://localhost:4000/api/oauth/#{conn.params["provider"]}/callback"
  end

  @spec callback(Conn.t(), map()) :: Conn.t()
  def callback(conn, %{"provider" => provider} = params) do
    session_params = Map.fetch!(params, "session_params")
    params         = Map.drop(params, ["provider", "session_params"])
    conn
    |> Conn.put_private(:pow_assent_session_params, session_params)
    |> Plug.callback_upsert(provider, params, redirect_uri(conn))
    |> case do
      {:ok, conn} ->
        json(conn, %{data: %{token: conn.private[:api_access_token], renew_token: conn.private[:api_renewal_token]}})

      {:error, conn} ->
        conn
        |> put_status(500)
        |> json(%{error: %{status: 500, message: "An unexpected error occurred"}})
    end
  end
end
danschultzer commented 4 years ago

It's described in the docs here:

https://client.example.com/auth/:provider/callback is the client side URI where the user will be redirected back to after authorization. The client should then send a POST request from the client to the callback URI in the API with the both the params received from the provider, and the session_params stored in the client.

You're redirecting to the callback endpoint instead of the client side callback URI. If this is in a native app then you would normally open up the the app uri with the query params, and then post to the callback endpoint.

sveredyuk commented 4 years ago

@danhunsaker I am stupid. Thanks a lot.