Skip to content

Latest commit

 

History

History
154 lines (112 loc) · 4.11 KB

authentication.md

File metadata and controls

154 lines (112 loc) · 4.11 KB

Authentication

Sign in

First of all, as we do with blogs, let's create the UserType:

rails g graphql:object UserType id:ID! email:String! first_name:String! last_name:String!

This creates the file app/graphql/types/user_type.rb.

Signing users in will be as simple as verifying the email and password and returning a token that can be used in subsequent requests.

The steps to add this new mutation would be very similar to the ones we added before.

First, create a resolver for the sign in mutation app/graphql/mutations/sign_in.rb:

module Mutations
  class SignIn < GraphQL::Schema::Mutation
    argument :email, String, required: true
    argument :password, String, required: true

    field :user, Types::UserType, null: true
    field :token, String, null: true

    def resolve(email:, password:)
      user = User.find_by(email: email)

      return unless user
      return unless user.authenticate(password)

      { user: user, token: user.auth_token }
    end
  end
end

Let's expose it:

module Types
  class MutationType < BaseObject
    field :create_blog, mutation: Mutations::CreateBlog, description: 'Create new blog'
    field :sign_in, mutation: Mutations::SignIn, description: 'Sign in user'
  end
end

As we did with the other queries and mutations, you can try it out using GraphiQL.

Authenticating requests

Now we can authenticate subsequent requests. The GraphQL server should be able to get the token from the headers on each request, detect what user it relates to, and pass this information down to the mutations.

The best way to pass information down to the mutations is using the context object.

We'll have to change the app/controllers/graphql_controller.rb and update the context:

class GraphqlController < ApplicationController
  def execute
    variables = ensure_hash(params[:variables])
    query = params[:query]
    operation_name = params[:operationName]
    result = GraphqlWorkshopSchema.execute(query, variables: variables, context: context, operation_name: operation_name)
    render json: result
  rescue => e
    raise e unless Rails.env.development?
    handle_error_in_development e
  end

  private

  ...

  def context
    {
      current_user: current_user
    }
  end

  def current_user
    User.find_by(auth_token: request.headers['AuthToken'])
  end

We're now ready to authenticate the query we built before.

Authenticating blogs query

We need to handle the context inside the query in order to check the request being done is authenticated.

Add the following to app/graphql/types/query_type.rb:

module Types
  class QueryType < BaseObject
    field :all_blogs, [BlogType], null: false, description: 'Return all the blogs'

    # This method is invoked, when `all_blogs` fields is being resolved
    def all_blogs
      raise GraphQL::ExecutionError, "Can't continue with this query" unless context[:current_user]

      Blog.all
    end
  end
end

When a GraphQL::ExecutionError is raised, GraphQL-Ruby rescues it. Its message will be added to the errors key and GraphQL-Ruby will automatically add the line, column and path to it.

The error might look like:

{
  "errors" => [
    {
      "message" => "Can't continue with this query",
      "locations" => [
        {
          "line" => 2,
          "column" => 3,
        }
      ],
      "path" => [
        "allBlogs"
      ],
    }
  ]
}

To try it out we're going to stop using GraphiQL because it doesn't have support to add new headers to the requests.

Postman is a good option too, let's check it out in this link. Take into consideration that you need to add the following in app/controllers/graphql_controller.rb:

class GraphqlController < ApplicationController
  skip_before_action :verify_authenticity_token, raise: false

  def execute
    ...
  end

  ...
end

Extra mile

The way we built it, the authentication token is returned as part of the GraphQL response, in the data key. If we want to return the auth token as part of the response headers, can we? How would it be?