In my GraphQL schema, I am using an authentication middleware similar to the one in the documentation. This middleware is set for all mutation fields.
In some mutation fields, I also want to use the Absinthe.Relay.Node.ParseIDs middleware.
In case a user is not authenticated and submits a mutation with an input that does not pass the ParseIDs middleware, the server returns the error message:
In argument \"input\": In field \"userId\": Could not decode ID value `foobar'
but I was expecting:
unauthenticated
After reading the Absinthe.Middleware documentation, I understand that both the authentication and ParseIDs middlewares are run anyway.
All middleware on a field are always run, make sure to pattern match on the state if you care.
When no user is authenticated, is there a way to receive the error message “unauthenticated” regardless of the input provided?
I only get the ParseIDs error message if no user is authenticated and the userId is invalid. If no user is authenticated and the userId is valid, then I get the error message from the authentication middleware.
$ mix test test/myapp_web/schema_accounts_test.exs:163
Excluding tags: [:test]
Including tags: [line: "163"]
1) test given no user is signed in, changeUserUsername should return an error with code 'UNAUTHENTICATED' (MyappWeb.SchemaAccountsTest)
test/myapp_web/schema_accounts_test.exs:163
match (=) failed
code: assert %{"data" => %{"changeUserUsername" => nil}, "errors" => [%{"message" => "Cannot process the query because no principal is authenticated.", "path" => ["changeUserUsername"], "extensions" => %{"code" => "UNAUTHENTICATED"}}]} = result
right: %{
"data" => %{"changeUserUsername" => nil},
"errors" => [
%{
"locations" => [%{"column" => 0, "line" => 2}],
"message" => "In argument \"input\": In field \"userId\": Could not decode ID value `foobar'",
"path" => ["changeUserUsername"]
}
]
}
stacktrace:
test/myapp_web/schema_accounts_test.exs:177: (test)
Finished in 1.2 seconds
43 tests, 1 failure, 42 excluded
Randomized with seed 365933
The authentication middleware is setup in the schema:
defmodule MyappWeb.Schema do
@moduledoc false
use Absinthe.Schema
use Absinthe.Relay.Schema,
flavor: :modern
import_types(MyappWeb.Schema.Types.Common)
import_types(MyappWeb.Schema.Types.Accounts)
import_types(MyappWeb.Schema.Types.Authentication)
import_types(MyappWeb.Schema.Common)
import_types(MyappWeb.Schema.Accounts)
import_types(MyappWeb.Schema.Authentication)
query do
import_fields(:common_queries)
import_fields(:authentication_queries)
end
mutation do
import_fields(:accounts_commands)
import_fields(:authentication_commands)
end
def middleware(
middleware,
%Absinthe.Type.Field{identifier: field_identifier},
%Absinthe.Type.Object{identifier: object_identifier}
)
when field_identifier not in [:sign_in_user, :sign_out_user] and
object_identifier in [:query, :subscription, :mutation] do
[MyappWeb.Middleware.EnsureAuthenticated | middleware]
end
def middleware(middleware, _field, _object) do
middleware
end
end
Here is my authentication middleware:
defmodule MyappWeb.Middleware.EnsureAuthenticated do
@behaviour Absinthe.Middleware
def call(resolution, _opts) do
case resolution.context do
%{principal: _principal} ->
resolution
_ ->
resolution
|> Absinthe.Resolution.put_result({:error, unauthenticated()})
end
end
defp unauthenticated() do
%{
message: "Cannot process the query because no principal is authenticated.",
extensions: %{
code: "UNAUTHENTICATED"
}
}
end
end
defmodule MyappWeb.Schema.Accounts do
@moduledoc false
use Absinthe.Schema.Notation
use Absinthe.Relay.Schema.Notation, :modern
alias MyappWeb.Resolvers.Accounts
object :accounts_commands do
@desc "Change the username of a user."
field(:change_user_username, type: :change_user_username_payload) do
arg(:input, non_null(:change_user_username_input))
middleware(Absinthe.Relay.Node.ParseIDs, input: [user_id: :user])
resolve(&Accounts.ChangeUserUsername.resolve/3)
end
end
end
For information, there is no problem when I also use Absinthe.Relay.Mutation.Notation.Modern and setup the ParseIDs middleware this way:
defmodule MyappWeb.Schema.Accounts do
@moduledoc false
use Absinthe.Schema.Notation
use Absinthe.Relay.Schema.Notation, :modern
alias MyappWeb.Resolvers.Accounts
object :accounts_commands do
@desc "Change the username of a user."
payload field(:change_user_username) do
@desc "Input type of ChangeUserUsername."
input do
@desc "The ID of the user."
field(:user_id, non_null(:id))
@desc "The username of the user."
field(:username, non_null(:string))
end
@desc "Return type of ChangeUserUsername."
output do
@desc "Indicates if the command succeeded."
field(:success, non_null(:boolean))
@desc "Error details."
field(:error, :error)
end
middleware(Absinthe.Relay.Node.ParseIDs, user_id: :user)
resolve(&Accounts.ChangeUserUsername.resolve/3)
end
end
end