Skip to content
This repository has been archived by the owner on Jul 25, 2024. It is now read-only.

How to do authentication and authorization in a Phoenix application with GraphQL

Sean Abrahams edited this page Mar 17, 2016 · 11 revisions

This guide assumes you have a Phoenix application, are using graphql-elixir, and want to perform authorization so that user's only have access to the data they should have access to.

In order to accomplish this we need to do our authorization check in our GraphQL resolve functions:

defmodule TestSchema do
  def schema do
    %GraphQL.Schema{
      query: %GraphQL.Type.ObjectType{
        name: "Hello",
        fields: %{
          greeting: %{
            type: %GraphQL.Type.String{},
            args: %{
              name: %{
                type: %GraphQL.Type.String{}
              }
            },
            resolve: fn(obj, input, context) -> 
              ### PERFORM AUTHORIZATION HERE
              context[:root_value][:current_user]
            end
          }
        }
      }
    }
  end
end

We need access to the currently logged in user from these functions.

The way this is done is by taking the currently logged in user from our Phoenix session and passing it to GraphQL in a way that will make it accessible via the context argument shown above.

1. Setup plug_graphql

2. If you've haven't already setup authentication for your Phoenix app you will need to create a new plug to set the current_user in conn's assigns map. This plug assumes that you've already set the user's session via something like put_session(conn, :user_id, user.id) in one of your controllers when they logged in.

lib/plugs/authenticate.ex:

defmodule YourApp.Plugs.Authenticate do
  import Plug.Conn
  import Phoenix.Controller

  def init(default), do: default

  def call(conn, _default) do
    # Make sure you've already `put_session(conn, :user_id, user.id)` somewhere
    # else in your app. We assume your app already handles setting the user
    # session.
    user_id = get_session(conn, :user_id)
    if user_id do
      user = YourApp.Repo.get!(YourApp.User, user_id)
      assign(conn, :current_user, user)
    else
      # TODO: Add support for returning a JSON error for JSON requests
      conn
        |> put_flash(:error, 'You need to be signed in to view this page.')
        |> redirect(to: "/")
    end
  end
end

3. Update web/router.ex to use the Plug and config GraphQL.Plug.Endpoint with the function that will accept conn and return root_value.

  pipeline :api do
    plug :accepts, ["json"]
    plug :fetch_session
    plug YourApp.Plugs.Authenticate ### Require users to be logged in to access API
  end

  scope "/graphql" do
    pipe_through :api

    get "/", GraphQL.Plug.Endpoint, schema: {GraphQL.Schema.Root, :schema}, root_value: {GraphQL.Schema.Root, :root_value}
    post "/", GraphQL.Plug.Endpoint, schema: {GraphQL.Schema.Root, :schema}, root_value: {GraphQL.Schema.Root, :root_value}
  end

4. Create a function that accepts conn and returns a Map for root_value. Note that whatever we return here will be made available in our resolve/3 functions. So if you wanted access to something more than just the current_user, you can add it to the Map you return here:

defmodule GraphQL.Schema.Root do
  ... other code

  def root_value(conn) do
    # The map we return here will be made available to our resolve/3 functions.
    # We can include any data we want, however right now we're only interested in
    # the currently logged in user.
    %{current_user: conn.assigns[:current_user]}
  end
end

5. Use it

plug_graphql will then make it available in your GraphQL resolve functions (3rd argument) which you can then use for authorization.

For example:

defmodule TestSchema do
  def schema do
    %GraphQL.Schema{
      query: %GraphQL.Type.ObjectType{
        name: "Hello",
        fields: %{
          greeting: %{
            type: %GraphQL.Type.String{},
            args: %{
              name: %{
                type: %GraphQL.Type.String{}
              }
            },
            resolve: fn(obj, input, context) -> 
              ### HERE
              context[:root_value][:current_user]
            end
          }
        }
      }
    }
  end
end
Clone this wiki locally