-
Notifications
You must be signed in to change notification settings - Fork 45
How to do authentication and authorization in a Phoenix application with GraphQL
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