Skip to content

Instantly share code, notes, and snippets.

@ericstumper
Created June 10, 2016 11:18
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save ericstumper/412a17321e25eab524a2bbbd57a033d4 to your computer and use it in GitHub Desktop.
Save ericstumper/412a17321e25eab524a2bbbd57a033d4 to your computer and use it in GitHub Desktop.
Guardian Authentication with Absinthe GraphQL in Elixir
defmodule Languafy.Web.Context do
@behaviour Plug
import Plug.Conn
alias Languafy.User
def init(opts), do: opts
def call(conn, _) do
case build_context(conn) do
{:ok, context} ->
put_private(conn, :absinthe, %{context: context})
{:error, reason} ->
conn
_ ->
conn
end
end
@doc """
Return the current user context based on the authorization header
"""
defp build_context(conn) do
with ["Bearer " <> token] <- get_req_header(conn, "authorization"),
{:ok, current_user} <- authorize(token) do
{:ok, %{current_user: current_user}}
end
end
defp authorize(token) do
case Guardian.decode_and_verify(token) do
{:ok, claims} -> return_user(claims)
{:error, reason} -> {:error, reason}
end
end
defp return_user(claims) do
case Guardian.serializer.from_token(Map.get(claims, "sub")) do
{:ok, resource} -> {:ok, resource}
{:error, reason} -> {:error, reason}
end
end
end
defmodule Languafy.Router do
use Languafy.Web, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
end
pipeline :graphql do
plug Plug.Parsers,
parsers: [:urlencoded, :multipart, :json],
pass: ["*/*"],
json_decoder: Poison
plug Languafy.Web.Context
end
if Mix.env == :dev do
scope "/graphiql" do
forward "/", Absinthe.Plug.GraphiQL, schema: Languafy.Schema
end
end
scope "/graphql" do
pipe_through :graphql
forward "/", Absinthe.Plug, schema: Languafy.Schema
end
scope "/", Languafy do
pipe_through :browser # Use the default browser stack
get "*path", PageController, :index
end
end
defmodule Languafy.Schema do
use Absinthe.Schema
import_types Languafy.Schema.Types
query do
@desc "Get an App User by ID"
field :user, type: :user do
arg :id, non_null(:id)
resolve &Languafy.UserResolver.find/2
end
@desc "Get current App User"
field :current_user, type: :user do
resolve &Languafy.Resolver.User.current/2
end
@desc "Get all courses"
field :courses, list_of(:course) do
resolve &Languafy.Resolver.Course.all/2
end
end
mutation do
@desc "Create an App User"
field :user, type: :user do
arg :first_name, non_null(:string)
arg :last_name, non_null(:string)
arg :email, non_null(:string)
arg :password, non_null(:string)
resolve &Languafy.Resolver.User.create/2
end
@desc "Login User"
field :session, :token do
arg :email, non_null(:string)
arg :password, non_null(:string)
resolve &Languafy.Resolver.Session.create/2
end
end
end
defmodule Languafy.Resolver.Session do
alias Languafy.Repo
alias Languafy.User
def create(args, _info) do
user = Repo.get_by(User, email: args[:email])
case authenticate(user, args[:password]) do
true -> create_token(user)
_ -> {:error, "User could not be authenticated."}
end
end
defp authenticate(user, password) do
case user do
nil -> false
_ -> Comeonin.Bcrypt.checkpw(password, user.password_hash)
end
end
defp create_token(user) do
case Guardian.encode_and_sign(user, :token) do
nil -> {:error, "An Error occured creating the token"}
{:ok, token, full_claims} -> {:ok, %{token: token}}
end
end
end
defmodule Languafy.Schema.Types do
use Absinthe.Schema.Notation
@desc "An App User"
object :user do
field :id, :id
field :first_name, :string
field :last_name, :string
field :email, :string
field :courses, list_of(:course)
end
@desc "A Languafy Course"
object :course do
field :id, :id
field :title, :string
field :description, :string
field :users, list_of(:user)
end
@desc "A JWT Token"
object :token do
field :token, :string
end
end
defmodule Languafy.Resolver.User do
alias Languafy.User
def find(%{id: id}, _info) do
case Languafy.Repo.get(User, id) do
nil -> {:error, "User id #{id} not found"}
user -> {:ok, user}
end
end
def current(_, %{context: %{current_user: current_user}}) do
{:ok, current_user}
end
def current(_, _) do
{:error, "Access denied"}
end
def create(args, _info) do
%User{}
|> User.registration_changeset(args)
|> Languafy.Repo.insert
end
end
@benwilson512
Copy link

@ericstumper
Copy link
Author

That would definitely make sense!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment