This project was forked from coryodaniel/speakeasy and its maintenance is focused on our internal usage at Avenue. Feel free to use it as is, and reach out to us through an issue or pull-request. We'll gladly consider your suggestions and contributions.
Speakeasy is authentication agnostic middleware based authorization for Absinthe GraphQL powered by Bodyguard.
Speakeasy can be installed
by adding speakeasy
to your list of dependencies in mix.exs
:
def deps do
[
{:speakeasy, github: "avenueplace/speakeasy", tag: "0.4"}
]
end
Configuration can be done in each Absinthe middleware call, but you can set global defaults as well.
config :speakeasy,
user_key: :current_user, # the key the current user will be under in the GraphQL context
authn_error_message: :unauthenticated # default authentication failure message
Note: no authz_error_message
is provided because it is set from Bodyguard.
tl;dr: A full example authentication, authorizing, loading, and resolving an Absinthe schema:
This example assumes:
- You are authorizing a standard phoenix context
- You already have a bodyguard policy
- Your
:current_user
is already in the Absinthe context or you are usingSpeakeasy.Plug
defmodule MyApp.Schema.PostTypes do
use Absinthe.Schema.Notation
alias Spectra.Posts
object :post do
field(:id, non_null(:id))
field(:name, non_null(:string))
end
object :post_mutations do
@desc "Create post"
field :create_post, type: :post do
arg(:name, non_null(:string))
middleware(Speakeasy.Authn)
middleware(Speakeasy.Authz, {Posts, :create_post})
middleware(Speakeasy.Resolve, &Posts.create_post/2)
middleware(MyApp.Middleware.ChangesetErrors) # :D
end
@desc "Update post"
field :update_post, type: :post do
arg(:name, non_null(:string))
middleware(Speakeasy.Authn)
middleware(Speakeasy.Authz, {Posts, :update_post})
middleware(Speakeasy.Resolve, &Posts.update_post/3)
middleware(MyApp.Middleware.ChangesetErrors) # :D
end
end
object :post_queries do
@desc "Get posts"
field :posts, list_of(:post) do
middleware(Speakeasy.Authn)
middleware(Speakeasy.Resolve, fn(attrs, user) -> MyApp.Posts.search(attrs, user) end)
end
@desc "Get post"
field :post, type: :post do
arg(:id, non_null(:string))
middleware(Speakeasy.Authn)
middleware(Speakeasy.LoadResourceByID, &Posts.get_post/1)
middleware(Speakeasy.Authz, {Posts, :get_post})
middleware(Speakeasy.Resolve)
end
end
end
And of course you can use Absinthe's resolve function as well:
@desc "Get post"
field :post, type: :post do
arg(:id, non_null(:string))
middleware(Speakeasy.Authn)
middleware(Speakeasy.LoadResourceByID, &Posts.get_post/1)
middleware(Speakeasy.Authz, {Posts, :get_post})
resolve(fn(_parent, _args, ctx) ->
{:ok, ctx[:speakeasy].resource}
end)
end
Differences from coryodaniel/speakeasy
The main difference in this fork is the added support of "#13 - Allow Authn to bypass nil users, given an option".
NOTE: At the time of this fork,
#13
was open. As we need to support this internally, we opted to fork and release a tagged version on GitHub. If and when#13
(or an equivalent fix) is merged, we highly recommend using the main version of Speakeasy.
This PR adds a new require: boolean
option to Speakeasy.Authn
, allowing for
the current_user
to be nil
when require
is set to false
. The main
purposes is to facilitate the usage of API calls that can be made both
authenticated and unauthenticated.
Example:
object :post_mutations do
@desc "Create post"
field :create_post, type: :post do
arg(:name, non_null(:string))
middleware(Speakeasy.Authn, require: false)
end
end
Note that the current_user
value then passed to Speakeasy.Authz
,
Speakeasy.Resolve
and subsequent middlewares can be nil
and you should take
steps to verify this.
Speakeasy is a collection of Absinthe middleware:
-
Speakeasy.Authn - Resolution middleware for Absinthe.
-
Speakeasy.LoadResource - Loads a resource into the speakeasy context.
-
Speakeasy.LoadResourceById - A convenience middleware to
LoadResource
using the:id
in the Absinthe arguments. -
Speakeasy.LoadResourceBy - A convenience middleware to
LoadResource
using a value from the attrs with the given key in the Absinthe arguments. -
Speakeasy.AuthZ - Authorization middleware for Absinthe.
-
Speakeasy.Resolve - Resolution middleware for Absinthe.
Speakeasy includes a Plug for loading the current user into the Absinthe context. It isn't required if you already have a method for loading the user into your Absinthe context.
defmodule MyAppWeb.Router do
use MyAppWeb, :router
pipeline :graphql do
plug(Speakeasy.Plug, load_user: &MyApp.Users.whoami/1, user_key: :current_user)
end
end