Search code examples
ruby-on-railsrubyangularjsomniauthomniauth-linkedin

Omniauth: "Missing Template Error" - Ruby on Rails


I am building an API which authenticates via LinkedIn and then returns our tokens to the client.

The client will be written in AngularJS and will run separately.

Auth URL: http://example.com/users/auth/linkedin

I get the following error after authentication:

Missing template users/omniauth_callbacks/linkedin, 
devise/omniauth_callbacks/linkedin, devise/linkedin,
application/linkedin with {
    :locale=>[:en],
    :formats=>[:html],
    :variants=>[],
    :handlers=>[:erb, :builder, :raw, :ruby, :coffee, :jbuilder]
}.

Searched in: * "/usr/local/lib/ruby/gems/2.2.0/gems/web-console-2.0.0/lib/action_dispatch/templates" * "/usr/local/Cellar/ruby/2.2.0/lib/ruby/gems/2.2.0/gems/devise-3.4.1/app/views"

I use the following special gems:

# Authentication
gem 'devise'
gem 'devise-token_authenticatable'
gem 'omniauth'
gem 'omniauth-oauth'
gem 'omniauth-linkedin'

# API Wrappers
gem 'linkedin'

The Source Code

config/routes.rb

# filename: routes.rb
# encoding: utf-8

Rails.application.routes.draw do

  #
  # API v1 Endpoints
  #

  scope '/api' do
    ## Authentication
    devise_for :users,
      :skip => [:sessions, :password, :registrations, :confirmation],
      :controllers => {
        :omniauth_callbacks => "users/omniauth_callbacks",
        :registrations      => "users/registrations",
        :sessions           => "users/sessions"
      }

    ## User specific routes
    scope '/users' do
      devise_scope :user do
        post '/check'   => 'users/users#is_user', as: 'is_user'
        post '/current' => 'users/sessions#get_current_user', as: 'current_user'
      end
    end
  end
end

app/controllers/application_controller.rb

# filename: application_controller.rb
# encoding: utf-8

class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :exception

  before_filter :set_cors_headers
  before_filter :cors_preflight

  def set_cors_headers
    headers['Access-Control-Allow-Origin']  = AppConfig.client['origin']
    headers['Access-Control-Allow-Methods'] = 'GET,POST,PUT,DELETE,OPTIONS'
    headers['Access-Control-Allow-Headers'] = '*'
    headers['Access-Control-Max-Age']       = "3628800"
  end

  def cors_preflight
    head(:ok) if request.method == :options
  end
end

app/controllers/users/omniauth_callbacks_controller.rb

# filename: omniauth_callbacks_controller.rb
# encoding: utf-8

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  ## Sign in / up with LinkedIn
  def linkedin
    auth_hash = request.env["omniauth.auth"]

    auth = Authorization.find_by_provider_and_uid("linkedin", auth_hash['uid'])

    if auth
      ## User already exists
      user = auth.user
    else
      ## User already signed in
      unless current_user
        unless user = User.find_by_email(auth_hash['info']['email'])
          user = User.create({
            first_name: auth_hash['info']['first_name'],
            last_name:  auth_hash['info']['last_name'],
            email:      auth_hash['info']['email'],
            password:   Devise.friendly_token[0,8]
          })
        end
      ## Sign user in
      else
        user = current_user
      end

      # Create an authorization for the current user
      unless auth = user.authorizations.find_by_provider(auth_hash["provider"])
        auth = user.authorizations.build(provider: auth_hash["provider"])
        user.authorizations << auth
      end

      auth.update_attributes({
          uid:    auth_hash['uid'],
          token:  auth_hash['credentials']['token'],
          secret: auth_hash['credentials']['secret'],
        })

      # Return user
      user
    end
  end
end

app/controllers/users/registrations_controller.rb

# filename: registrations_controller.rb
# encoding: utf-8

class Users::RegistrationsController < Devise::RegistrationsControllerú
  skip_before_filter :verify_authenticity_token

  respond_to :json

  def create
    # Create the user
    build_resource(sign_up_params)

    # Try to save them
    if resource.save
      sign_in resource
      render status: 200,
        json: {
          success: true,
          info: "Registered",
          data: {
            user: resource,
            auth_token: current_user.authentication_token
          }
        }
    else
      # Otherwise fail
      render status: :unprocessable_entity,
        json: {
          success: false,
          info: resource.errors,
          data: {}
        }
  end

end

app/controllers/users/sessions_controller.rb

# filename: omniauth_callbacks_controller.rb
# encoding: utf-8

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  ## Sign in / up with LinkedIn
  def linkedin
    auth_hash = request.env["omniauth.auth"]

    auth = Authorization.find_by_provider_and_uid("linkedin", auth_hash['uid'])

    if auth
      ## User already exists
      user = auth.user
    else
      ## User already signed in
      unless current_user
        unless user = User.find_by_email(auth_hash['info']['email'])
          user = User.create({
            first_name: auth_hash['info']['first_name'],
            last_name:  auth_hash['info']['last_name'],
            email:      auth_hash['info']['email'],
            password:   Devise.friendly_token[0,8]
          })
        end
      ## Sign user in
      else
        user = current_user
      end

      # Create an authorization for the current user
      unless auth = user.authorizations.find_by_provider(auth_hash["provider"])
        auth = user.authorizations.build(provider: auth_hash["provider"])
        user.authorizations << auth
      end

      auth.update_attributes({
          uid:    auth_hash['uid'],
          token:  auth_hash['credentials']['token'],
          secret: auth_hash['credentials']['secret'],
        })

      # Return user
      user
    end
  end
end

 app/controllers/users/users_controller.rb

# filename: users_controller.rb
# encoding: utf-8

class Users::UsersController < Devise::SessionsController
  protect_from_forgery with: :exception, except: [:is_user]

  respond_to :json

  ## Check if user exists by email
  def is_user
    #authenticate_user!
    render status: 200, json: {
        success: !User.find_by_email(params[:email]).blank?
      }
  end

end

Solution

  • Your linkedin method in the Users::OmniauthCallbacksController class doesn't render or redirect explicitly so it is attempting to do the implicit rendering of the linkedin template (usually linkedin.html.erb.

    Based on your code you probably want to render user.to_json or something to that effect in your code so the angular api receives something it can work with.