Table of Contents
Snippets
Here is a series of snippets that you can use however you see fit.
Important This section is under construction. More snippets will be be added.
Devise + JWT
An authenticate mutation using Devise and JWT tokens.
# frozen_string_literal: true
module GraphQL::Mutations
class Authenticate < GraphQL::Mutation
desc <<~DESC
Authenticate a User using email and password.
This mutation will return a token string or null. You can check the
errors and look for the `auth` extension to know the reason:
`{ ..., "extensions": { auth: "not_found" } }`.
Make sure to provide the headers `JWT-AUDIENCE`.
Other possible values of `auth`:
- `not_found` => Wrong e-mail or password
- `disabled` => User locked or disabled
- `unconfirmed` => User hasn't confirmed their e-mail
- `missing_audience` => No JWT_AUDIENCE provided
DESC
argument :email, :string, null: false
argument :password, :string, null: false
returns :string
delegate :payload_for_user, to: 'Warden::JWTAuth::PayloadUserHelper'
attr_reader :error_type
def perform
return generate_token if authenticated?
request.report_error('Unable to authenticate', auth: @error_type)
end
# Check if everything is right to authenticate
def authenticated?
return false if authentication_blocked?
resource.update_tracked_fields(fake_request)
resource.save
end
# Return the user available for authentication
def resource
return @resource if defined?(@resource)
@resource = User.find_by(email: arg(:email)&.downcase)
end
private
# Check and set a possible reason for authentication being blocked
def authentication_blocked?
return @error_type = :not_found unless valid_resource?
return if authenticable?
@error_type = resource.confirmed? ? :disabled : :unconfirmed
end
# Check if the the resource is valid by its email and password
def valid_resource?
resource.present? &&
resource.send(:valid_password?, arg(:password))
end
# Check if the resource is allowed to authenticate
def authenticable?
resource.valid_for_authentication? &&
resource.active_for_authentication?
end
# A fake request object so that the trackable information can be updated
def fake_request
OpenStruct.new(remote_ip: context.remote_ip)
end
# Generate a proper JWT Token
def generate_token
aud = context.jwt_audience
payload = payload_for_user(resource, :user).merge('aud' => aud)
token = Warden::JWTAuth::TokenEncoder.new.call(payload)
resource.on_jwt_dispatch(token, payload)
token
rescue ActiveRecord::RecordInvalid => error
if aud.blank?
args = { auth: :missing_audience }
message = 'Please provide the JWT-AUDIENCE header'
elsif !Rails.env.production?
message = "Something went wrong: #{error.message}"
else
message = 'Something went wrong while trying to save the token'
end
request.report_error(message, *args)
end
end
end