Firebase user registration / authentication

This section describes the user registration / login authentication method using the Google Cloud Firebase ID token.

Add Manager

In the SOULs API, basic CRUD files are

app/graphql/mutations/base

Files that are defined in the directory, but that operate on objects other than CRUD

app/graphql/mutations/managers

Define it in the directory.

Let's create $MANAGER_NAME_manager directory using the souls g manager

$ souls g manager $MANAGER_NAME --mutation=MUTATION_NAME

souls g manager , --mutation option to define the Mutation filename.

Now let's create a manager that logs in to the User

$ souls g manager user --mutation=sign_in_user
Created file! : ./app/graphql/mutations/managers/user_manager/sign_in_user.rb
🎉  Done!

Firebase ID Token Authentification

The SOULs framework uses Firebase ID token authentication for login authentication.

Please refer to the link below for Firebase ID tokens.

Validate your Firebase ID token

RubyGem firebase-id-token

Configuration

In the API config directory,

firebase-id-token.rb .

your-firebase-project-id

Enter your Firebase ID in.

apps/api/config/firebase_id_token.rb
FirebaseIdToken.configure do |config|
  config.project_ids = ['your-firebase-project-id']
end

User login Mutation

With Firebase Authentification,

Register if the user does not exist, log in if the user exists,

Define the process.

app/graphql/mutations/managers/user_manager/sign_in_user.rb
module Mutations
  module Managers::UserManager
    class SignInUser < BaseMutation
      field :status, String, null: false
      field :token, String, null: true
      field :user_role, String, null: true
      field :username, String, null: true
      argument :token, String, required: false

      def resolve(token:)
        fb_auth(token: token)
        begin
          user = ::User.find_by_uid(@payload["sub"])
          user.update(icon_url: @payload["picture"], username: @payload["name"])
          token_base = JsonWebToken.encode(user_id: user.id)
          {
            status: "ログイン成功!",
            username: user.username,
            token: token_base
          }
        rescue StandardError
          user =
            ::User.new(
              uid: @payload["sub"],
              email: @payload["email"],
              icon_url: @payload["picture"],
              username: @payload["name"],
              user_role: 4
            )
          if user.save
            token = JsonWebToken.encode(user_id: user.id)
            {
              status: "ユーザー新規登録完了!",
              username: user.username,
              token: token,
              user_role: user.user_role
            }
          else
            { status: user.errors.full_messages }
          end
        end
      end
    end
  end
end

The SOULs API uses the fb_auth method for user login authentication.

When the request is sent and goes inside resolve fb_auth method is executed first.

If login and user registration are successful

Return the token

We token to authenticate the user.

apps/api/app/graphql/mutations/base_mutation.rb
module Mutations
  class BaseMutation < GraphQL::Schema::RelayClassicMutation
    argument_class Types::BaseArgument
    field_class Types::BaseField
    input_object_class Types::BaseInputObject
    object_class Types::BaseObject

    def fb_auth(token:)
      FirebaseIdToken::Certificates.request!
      sleep(3) if ENV["RACK_ENV"] == "development"
      @payload = FirebaseIdToken::Signature.verify(token)
      raise(ArgumentError, "Invalid or Missing Token") if @payload.blank?

      @payload
    end
,
,

Use GraphQL Context

The SOULs API stores user information in GraphQL context after login.

GraphQL endpoints are defined in the app.rb

token obtained earlier in the header HTTP_AUTHORIZATION and send the request.

apps/api/app.rb
class SOULsApi < Sinatra::Base
  ## 中略
  post endpoint do
    token = request.env["HTTP_AUTHORIZATION"].split("Bearer ")[1] if request.env["HTTP_AUTHORIZATION"]

    user = token ? login_auth(token: token) : nil
    context = { user: user }
    result = SOULsApiSchema.execute(params[:query], variables: params[:variables], context: context)
    json(result)
  rescue StandardError => e
    message = { error: e }
    json(message)
  end

  def login_auth(token:)
    decoded_token = JsonWebToken.decode(token)
    user_id = decoded_token[:user_id]
    user = User.find(user_id)
    raise(StandardError, "Invalid or Missing Token") if user.blank?

    user
  rescue StandardError => e
    message = { error: e }
    json(message)
  end

If the login is successful, the user information will be stored in the context

Use Pundit Policy

Policy manages access rights to each class. It is defined in a directory in ./app/policies/ The SOULs framework uses the following two gems to manage user permissions.

· Pundit · Role_model

You can use Policies to manage the methods that you can access with your privileges.

Now let's add code that uses Policy restrictions.

$ souls g manager user --mutation=add_user_role
 Created file! : ./app/graphql/mutations/managers/user_manager/add_user_role.rb
🎉  Done!
app/graphql/mutations/managers/user_manager/add_user_role.rb
module Mutations
  module Managers::UserManager
    class AddUserRole < BaseMutation
      argument :target_user_id, String, required: true
      argument :user_roles, [String], required: true

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

      def resolve(args)
        check_user_permissions(context[:user], context[:user], :update_user_role?)
        _, user_id = SOULsApiSchema.from_global_id(args[:target_user_id])
        target_user = ::User.find(user_id)
        target_user.roles << args[:user_roles].map(&:to_sym)
        return { user: target_user } if target_user.save

        raise
      rescue StandardError => e
        GraphQL::ExecutionError.new(e.to_s)
      end
    end
  end
end

First called in resolve

app/graphql/mutations/managers/user_manager/add_user_role.rb
check_user_permissions(context[ :user ], context[ :user ], :update_user_role? )

This method

It is defined in app/graphql/muitations/base_mutation.rb

app/graphql/muitations/base_mutation.rb
def check_user_permissions(user, obj, method)
  raise(StandardError, "Invalid or Missing Token") unless user

  policy_class = obj.class.name + "Policy"
  policy_clazz = policy_class.constantize.new(user, obj)
  permission = policy_clazz.public_send(method)
  raise(Pundit::NotAuthorizedError, "permission error!") unless permission
end

The first argument is context[:user] ,

Target object as the second argument,

Target method in the third argument

Is specified.