Authentication Using GraphQL Ruby

image

I often get asked questions about how to implement an authentication system for use in a frontend app relying wholly on GraphQL. There are divided opinions on whether you should keep authentication separate from the GraphQL API. To be honest, I don’t see a reason to do that. If you do authentication through a controller reached through a REST API or through the GraphQL Schema, it hardly makes any difference and if the frontend relies heavily on GraphQL, I don’t see a reason not to have the authentication part with it too.

Separating GraphQL Schema

The first thing to handling authentication through GraphQL is serve two different Schemas based on whether the user is making authenticated requests or a non-authenticated one. It is also the simplest one to take care of, so let’s get this out of the way.

If you are using graphql-ruby, you might already have something along the lines of the following in your GraphQLController:

def execute_query
  schema.execute(:document => document,
                  :variables => @variables,
                  :context => @context,
                  :operation_name => @operation_name)
        .to_json
end

All we need is to provide that schema class based on whether the user is currently logged in. Something like this:

def schema
  @schema ||= if Current.user
                Schema::AuthenticatedSchema
              else
                Schema::PublicSchema
              end
end

Just define the schema classes as separate classes inheriting from GraphQL::Schema and you are done.

Public Schema and Auth Mutations

The first question we need to ask ourselves is what authentication is in terms of our app? For us, it is a mutation that creates a new UserSession on the backend. So the PublicSchema boils down to a single mutation:

module Schema
  class PublicSchema < GraphQL::Schema
    mutation Types::PublicMutationType
  end
end

module Types
  class PublicMutationType < BaseObject
    description "Public Mutation API"

    field :login, :mutation => Mutations::Login
  end
end

You can add more operations if you need to support sign up, account recovery options etc. But once we can figure out the login part, the rest could be derived pretty easily.

module Mutations
  class Login < Base
    graphql_name "Login"

    argument :email, String, :required => false
    argument :password, String, :required => false
    argument :remember_token, String, :required => false

    field :session, UserSessionType, :null => true
    field :errors, [String], :null => true

    def resolve(args)
      if args[:remember_token].present?
        user = User.find_by(:persistence_token => args[:remember_token])
        user_session = UserSession.new(user) if user.present?
      else
        user_session = UserSession.new(args.slice(:email, :password))
      end

      return { :errors => ["unauthorized"] } unless user_session.save
      return unless user_session.user

      { :session => user_session, :name => user.name }
    end
  end
end

Here’s how it can be implemented easily. Note that we are using authlogic as our authentication system. If you are using something else, only the resolve implementation would change.

Published 12 Oct 2020

I build mobile and web applications. Full Stack, Rails, React, Typescript, Kotlin, Swift
Pulkit Goyal on Twitter