danschultzer / phoenix_oauth2_provider

Get an OAuth 2 provider running in your phoenix with controllers, views and models in just two minutes
MIT License
84 stars 41 forks source link

Client credentials grant doesn't propagate user into the conn value #26

Closed kitplummer closed 5 years ago

kitplummer commented 5 years ago

the same as if I use Authorization Code grant. The token gets issued as I'd expected, but if I:

user = ExOauth2Provider.Plug.current_resource_owner(conn)

user is nil. I do see the OauthAccessToken struct, and resource_owner and resource_owner_id are nil too.

Am I wrong in expecting that a token granted by either means should work in a controller the same? Really hoping I don't need the Authorize step - just need an API token.

Sorry for squeekin on this stuff. Really appreciate the help, and the effort you've put into the project(s).

danschultzer commented 5 years ago

The preload happens when you run the conn through the ExOauth2Provider.Plug.VerifyHeader plug, with ExOauth2Provider.authenticate_token/2:

  defp load_resource_owner({:ok, access_token}, config) do
    repo         = Config.repo(config)
    access_token = repo.preload(access_token, :resource_owner)

    {:ok, access_token}
  end

ExOauth2Provider.Plug.current_resource_owner/1 pulls this from the preloaded assoc:

  def current_resource_owner(conn, the_key \\ :default) do
    conn
    |> current_access_token(the_key)
    |> case do
      nil          -> nil
      access_token -> access_token.resource_owner
    end
  end

If resource_owner_id isn't set, it may be because it's an application wide token. Are you using the client credentials flow? In that case, there'll be no resource owner, and you should instead pull the application id to get that as a resource.

kitplummer commented 5 years ago

Ah yes. Just using the Client Credentials flow...so that makes sense. Thanks again, much appreciated!

kitplummer commented 5 years ago

So...I've been unable to cleanly get to the user - through the chain of resources: token[application.id]-> get_application_uid() -> application[owner_id] -> get_user() -> get_the_thing_i_actually_am_controlling() - because of the empty OauthAccessToken:

%Plug.Conn{
  adapter: {Plug.Cowboy.Conn, :...},
  assigns: %{current_user: nil},
  before_send: [#Function<0.35161479/1 in Plug.Session.before_send/2>,
   #Function<0.66442134/1 in Plug.Telemetry.call/2>,
   #Function<0.50969320/1 in Phoenix.LiveReloader.before_send_inject_reloader/2>],
  body_params: %{},
  cookies: %{},
  halted: false,
  host: "localhost",
  method: "GET",
  owner: #PID<0.749.0>,
  params: %{},
  path_info: ["api", "v1", "queries"],
  path_params: %{},
  port: 4000,
  private: %{
    LeiApiWeb.Router => {[], %{}},
    :ex_oauth2_provider_default_access_token => {:ok,
     %LeiApi.OauthAccessTokens.OauthAccessToken{
       __meta__: #Ecto.Schema.Metadata<:loaded, "oauth_access_tokens">,
       application: #Ecto.Association.NotLoaded<association :application is not loaded>,
       application_id: 1,
       expires_in: 7200,
       id: 7,
       inserted_at: ~N[2019-08-09 16:21:24],
       previous_refresh_token: "",
       refresh_token: nil,
       resource_owner: nil,
       resource_owner_id: nil,
       revoked_at: nil,
       scopes: "",
       token: "d510a15b5e450d52df74778a0c0eff08bf83c90878f8c8516e2c6e54100bbf19",
       updated_at: ~N[2019-08-09 16:21:24]
     }},
    :phoenix_action => :index,
    :phoenix_controller => LeiApiWeb.API.V1.QueryApiController,
    :phoenix_endpoint => LeiApiWeb.Endpoint,
    :phoenix_format => "json",
    :phoenix_layout => {LeiApiWeb.LayoutView, :app},
    :phoenix_router => LeiApiWeb.Router,
    :phoenix_view => LeiApiWeb.API.V1.QueryApiView,
    :plug_session => %{},
    :plug_session_fetch => :done,
    :pow_config => [
      mod: Pow.Plug.Session,
      plug: Pow.Plug.Session,
      otp_app: :lei_api
    ]
  },

Maybe I just grabbing at straws (easier implementation on my side) but even if it is an "application-wide" token - wouldn't it still belong to a resource_owner and then make sense to be able to get the User from the conn? If I'm just being daft I'll keep trudging.

danschultzer commented 5 years ago

No, typically an application wide token would give access to all resource owners under that application. There is no single user that has authorized anything when using client credentials, it's the whole application that has been given access to. It should only be used when you control both client and server.

If you need to retrieve a user, then it would be the owner of the application, so you should preload the application, and then the owner.

kitplummer commented 5 years ago

Ok, thanks.